diff --git a/.github/workflows/module_assistant_push.yml b/.github/workflows/module_assistant_push.yml deleted file mode 100644 index 347fee39db..0000000000 --- a/.github/workflows/module_assistant_push.yml +++ /dev/null @@ -1,24 +0,0 @@ -name : assistant - -on : - push : - branches : - - 'alpha' - - 'beta' - - 'master' - - -env : - CARGO_TERM_COLOR : always - -jobs : - - # assistant - - test : - uses : Wandalen/wTools/.github/workflows/standard_rust_push.yml@alpha - with : - manifest_path : 'module/move/assistant/Cargo.toml' - module_name : 'assistant' - commit_message : ${{ github.event.head_commit.message }} - commiter_username: ${{ github.event.head_commit.committer.username }} diff --git a/.github/workflows/module_llm_tools_push.yml b/.github/workflows/module_llm_tools_push.yml deleted file mode 100644 index 733d5c0094..0000000000 --- a/.github/workflows/module_llm_tools_push.yml +++ /dev/null @@ -1,24 +0,0 @@ -name : llm_tools - -on : - push : - branches : - - 'alpha' - - 'beta' - - 'master' - - -env : - CARGO_TERM_COLOR : always - -jobs : - - # llm_tools - - test : - uses : Wandalen/wTools/.github/workflows/standard_rust_push.yml@alpha - with : - manifest_path : 'module/move/llm_tools/Cargo.toml' - module_name : 'llm_tools' - commit_message : ${{ github.event.head_commit.message }} - commiter_username: ${{ github.event.head_commit.committer.username }} diff --git a/.gitignore b/.gitignore index 62e8d7dec7..b8205c68fa 100755 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ dist Cargo.lock .DS_Store .idea +.mastermind *.log *.db *.tmp diff --git a/Cargo.toml b/Cargo.toml index 20e07892bb..26039858b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -98,7 +98,7 @@ default-features = false # path = "module/core/type_constructor_derive_pair_meta" [workspace.dependencies.interval_adapter] -version = "~0.28.0" +version = "~0.29.0" path = "module/core/interval_adapter" default-features = false # features = [ "enabled" ] @@ -110,7 +110,7 @@ default-features = false # features = [ "enabled" ] [workspace.dependencies.collection_tools] -version = "~0.17.0" +version = "~0.18.0" path = "module/core/collection_tools" default-features = false @@ -118,31 +118,31 @@ default-features = false ## derive [workspace.dependencies.derive_tools] -version = "~0.33.0" +version = "~0.34.0" path = "module/core/derive_tools" default-features = false # features = [ "enabled" ] [workspace.dependencies.derive_tools_meta] -version = "~0.32.0" +version = "~0.33.0" path = "module/core/derive_tools_meta" default-features = false # features = [ "enabled" ] [workspace.dependencies.reflect_tools] -version = "~0.4.0" +version = "~0.5.0" path = "module/core/reflect_tools" default-features = false # features = [ "enabled" ] [workspace.dependencies.reflect_tools_meta] -version = "~0.4.0" +version = "~0.5.0" path = "module/core/reflect_tools_meta" default-features = false # features = [ "enabled" ] [workspace.dependencies.format_tools] -version = "~0.3.0" +version = "~0.4.0" path = "module/core/format_tools" default-features = false # features = [ "enabled" ] @@ -166,24 +166,24 @@ path = "module/alias/fundamental_data_type" default-features = false [workspace.dependencies.variadic_from] -version = "~0.28.0" +version = "~0.29.0" path = "module/core/variadic_from" default-features = false # features = [ "enabled" ] [workspace.dependencies.clone_dyn] -version = "~0.30.0" +version = "~0.31.0" path = "module/core/clone_dyn" default-features = false # features = [ "enabled" ] [workspace.dependencies.clone_dyn_meta] -version = "~0.28.0" +version = "~0.29.0" path = "module/core/clone_dyn_meta" # features = [ "enabled" ] [workspace.dependencies.clone_dyn_types] -version = "~0.28.0" +version = "~0.30.0" path = "module/core/clone_dyn_types" default-features = false # features = [ "enabled" ] @@ -208,7 +208,7 @@ default-features = false ## iter [workspace.dependencies.iter_tools] -version = "~0.26.0" +version = "~0.28.0" path = "module/core/iter_tools" default-features = false @@ -226,7 +226,7 @@ path = "module/core/for_each" default-features = false [workspace.dependencies.former] -version = "~2.12.0" +version = "~2.15.0" path = "module/core/former" default-features = false @@ -236,31 +236,31 @@ default-features = false # default-features = false [workspace.dependencies.former_meta] -version = "~2.12.0" +version = "~2.14.0" path = "module/core/former_meta" default-features = false [workspace.dependencies.former_types] -version = "~2.14.0" +version = "~2.15.0" path = "module/core/former_types" default-features = false [workspace.dependencies.impls_index] -version = "~0.9.0" +version = "~0.10.0" path = "module/core/impls_index" default-features = false [workspace.dependencies.impls_index_meta] -version = "~0.10.0" +version = "~0.12.0" path = "module/core/impls_index_meta" [workspace.dependencies.mod_interface] -version = "~0.31.0" +version = "~0.33.0" path = "module/core/mod_interface" default-features = false [workspace.dependencies.mod_interface_meta] -version = "~0.30.0" +version = "~0.31.0" path = "module/core/mod_interface_meta" default-features = false @@ -286,7 +286,7 @@ default-features = false ## macro tools [workspace.dependencies.macro_tools] -version = "~0.46.0" +version = "~0.52.0" path = "module/core/macro_tools" default-features = false @@ -317,7 +317,7 @@ path = "module/core/typing_tools" default-features = false [workspace.dependencies.implements] -version = "~0.11.0" +version = "~0.12.0" path = "module/core/implements" default-features = false @@ -327,17 +327,17 @@ path = "module/alias/instance_of" default-features = false [workspace.dependencies.inspect_type] -version = "~0.13.0" +version = "~0.14.0" path = "module/core/inspect_type" default-features = false [workspace.dependencies.is_slice] -version = "~0.12.0" +version = "~0.13.0" path = "module/core/is_slice" default-features = false [workspace.dependencies.asbytes] -version = "~0.1.0" +version = "~0.2.0" path = "module/core/asbytes" default-features = false @@ -379,7 +379,7 @@ path = "module/alias/file_tools" default-features = false [workspace.dependencies.pth] -version = "~0.22.0" +version = "~0.23.0" path = "module/core/pth" default-features = false @@ -409,7 +409,7 @@ version = "~0.4.0" path = "module/alias/wtest" [workspace.dependencies.test_tools] -version = "~0.12.0" +version = "~0.14.0" path = "module/core/test_tools" features = [ "full" ] @@ -621,3 +621,26 @@ version = "0.9.34" [workspace.dependencies.bytemuck] version = "1.21.0" + +## External - parse + +[workspace.dependencies.proc-macro2] +version = "~1.0.78" +default-features = false + +[workspace.dependencies.quote] +version = "~1.0.35" +default-features = false + +[workspace.dependencies.syn] +version = "~2.0.100" +default-features = false + +[workspace.dependencies.const_format] +version = "~0.2.32" +default-features = false + +# proc-macro2 = { version = "~1.0.78", default-features = false, features = [] } +# quote = { version = "~1.0.35", default-features = false, features = [] } +# syn = { version = "~2.0.52", default-features = false, features = [ "full", "extra-traits" ] } # qqq : xxx : optimize set of features +# const_format = { version = "0.2.32", default-features = false, features = [] } diff --git a/License b/License index 616fd389f2..72c80c1308 100644 --- a/License +++ b/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn (c) 2013-2023 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -12,7 +12,6 @@ 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 diff --git a/Makefile b/Makefile index 8828abba83..4bcf528c1b 100644 --- a/Makefile +++ b/Makefile @@ -38,10 +38,10 @@ sync : git.sync # Usage : # make audit -audit : -# This change is made to ignore the RUSTSEC-2024-0421 warning related to the idna crate. -# The issue arises because unitore relies on gluesql, which in turn depends on an outdated version of idna. -# Since the primary logic in unitore is built around gluesql, upgrading idna directly is not feasible. +audit : +# This change is made to ignore the RUSTSEC-2024-0421 warning related to the idna crate. +# The issue arises because unitore relies on gluesql, which in turn depends on an outdated version of idna. +# Since the primary logic in unitore is built around gluesql, upgrading idna directly is not feasible. cargo audit --ignore RUSTSEC-2024-0421 # diff --git a/Readme.md b/Readme.md index 7aa8c81f16..4ac4a7dd54 100644 --- a/Readme.md +++ b/Readme.md @@ -49,7 +49,7 @@ Collection of general purpose tools for solving problems. Fundamentally extend t | [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%2Fstrs_tools_trivial.rs,RUN_POSTFIX=--example%20strs_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=module%2Fcore%2Ftime_tools%2Fexamples%2Ftime_tools_trivial.rs,RUN_POSTFIX=--example%20time_tools_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=module%2Fcore%2Ftyping_tools%2Fexamples%2Ftyping_tools_trivial.rs,RUN_POSTFIX=--example%20typing_tools_trivial/https://github.com/Wandalen/wTools) | -| [asbytes](module/core/asbytes) | [![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_asbytes_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_asbytes_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_asbytes_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_asbytes_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/asbytes) | | +| [asbytes](module/core/asbytes) | [![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_asbytes_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_asbytes_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_asbytes_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_asbytes_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/asbytes) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fasbytes%2Fexamples%2Fasbytes_as_bytes_trivial.rs,RUN_POSTFIX=--example%20asbytes_as_bytes_trivial/https://github.com/Wandalen/wTools) | | [async_tools](module/core/async_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_async_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_async_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_async_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_async_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/async_tools) | | | [format_tools](module/core/format_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_format_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_format_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_format_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_format_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/format_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fformat_tools%2Fexamples%2Fformat_tools_trivial.rs,RUN_POSTFIX=--example%20format_tools_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) | | @@ -69,11 +69,9 @@ Collection of general purpose tools for solving problems. Fundamentally extend t | [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%2Fdeterministic_rand_trivial.rs,RUN_POSTFIX=--example%20deterministic_rand_trivial/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) | | -| [assistant](module/move/assistant) | [![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_assistant_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_assistant_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_assistant_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_assistant_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/assistant) | | | [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.rs,RUN_POSTFIX=--example%20graphs_tools_trivial/https://github.com/Wandalen/wTools) | | [graphs_tools_deprecated](module/move/graphs_tools_deprecated) | [![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_deprecated_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_graphs_tools_deprecated_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_graphs_tools_deprecated_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_graphs_tools_deprecated_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/graphs_tools_deprecated) | [![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_deprecated%2Fexamples%2Fgraphs_tools_trivial.rs,RUN_POSTFIX=--example%20graphs_tools_trivial/https://github.com/Wandalen/wTools) | | [gspread](module/move/gspread) | [![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_gspread_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_gspread_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_gspread_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_gspread_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/gspread) | | -| [llm_tools](module/move/llm_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_llm_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_llm_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_llm_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_llm_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/llm_tools) | | | [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%2Foptimization_tools_trivial.rs,RUN_POSTFIX=--example%20optimization_tools_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) | | | [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) | | diff --git a/debug b/debug deleted file mode 100644 index c233ba713f..0000000000 --- a/debug +++ /dev/null @@ -1,3 +0,0 @@ -[program_tools_v1 0b9968c19] former : property hint - - 1 file changed, 1 insertion(+) -Already up to date. diff --git a/module/alias/cargo_will/License b/module/alias/cargo_will/License index 0804aed8e3..72c80c1308 100644 --- a/module/alias/cargo_will/License +++ b/module/alias/cargo_will/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/alias/file_tools/License b/module/alias/file_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/alias/file_tools/License +++ b/module/alias/file_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/alias/fundamental_data_type/License b/module/alias/fundamental_data_type/License index 0804aed8e3..72c80c1308 100644 --- a/module/alias/fundamental_data_type/License +++ b/module/alias/fundamental_data_type/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/alias/instance_of/License b/module/alias/instance_of/License index c32986cee3..a23529f45b 100644 --- a/module/alias/instance_of/License +++ b/module/alias/instance_of/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/alias/multilayer/License b/module/alias/multilayer/License index c32986cee3..a23529f45b 100644 --- a/module/alias/multilayer/License +++ b/module/alias/multilayer/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/alias/proc_macro_tools/License b/module/alias/proc_macro_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/alias/proc_macro_tools/License +++ b/module/alias/proc_macro_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/alias/proc_macro_tools/examples/proc_macro_tools_trivial.rs b/module/alias/proc_macro_tools/examples/proc_macro_tools_trivial.rs index 2d3cad5ff6..94f456ba1e 100644 --- a/module/alias/proc_macro_tools/examples/proc_macro_tools_trivial.rs +++ b/module/alias/proc_macro_tools/examples/proc_macro_tools_trivial.rs @@ -9,7 +9,7 @@ fn main() let code = qt!( core::option::Option< i8, i16, i32, i64 > ); let tree_type = syn::parse2::< syn::Type >( code ).unwrap(); - let got = typ::type_parameters( &tree_type, 0..=2 ); + let got = typ::type_parameters( &tree_type, &0..=2 ); got.iter().for_each( | e | println!( "{}", qt!( #e ) ) ); /* print : i8 diff --git a/module/alias/proper_tools/License b/module/alias/proper_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/alias/proper_tools/License +++ b/module/alias/proper_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/alias/werror/License b/module/alias/werror/License index 0804aed8e3..72c80c1308 100644 --- a/module/alias/werror/License +++ b/module/alias/werror/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/alias/willbe2/License b/module/alias/willbe2/License index 0804aed8e3..72c80c1308 100644 --- a/module/alias/willbe2/License +++ b/module/alias/willbe2/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/alias/winterval/License b/module/alias/winterval/License index 0804aed8e3..72c80c1308 100644 --- a/module/alias/winterval/License +++ b/module/alias/winterval/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/alias/wproc_macro/License b/module/alias/wproc_macro/License index 0804aed8e3..72c80c1308 100644 --- a/module/alias/wproc_macro/License +++ b/module/alias/wproc_macro/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/alias/wstring_tools/License b/module/alias/wstring_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/alias/wstring_tools/License +++ b/module/alias/wstring_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/alias/wtest/License b/module/alias/wtest/License index c32986cee3..a23529f45b 100644 --- a/module/alias/wtest/License +++ b/module/alias/wtest/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/alias/wtest_basic/License b/module/alias/wtest_basic/License index 0804aed8e3..72c80c1308 100644 --- a/module/alias/wtest_basic/License +++ b/module/alias/wtest_basic/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/blank/brain_tools/License b/module/blank/brain_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/blank/brain_tools/License +++ b/module/blank/brain_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/blank/draw_lang/License b/module/blank/draw_lang/License index 0804aed8e3..72c80c1308 100644 --- a/module/blank/draw_lang/License +++ b/module/blank/draw_lang/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/blank/drawboard/License b/module/blank/drawboard/License index 0804aed8e3..72c80c1308 100644 --- a/module/blank/drawboard/License +++ b/module/blank/drawboard/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/blank/drawql/License b/module/blank/drawql/License index 0804aed8e3..72c80c1308 100644 --- a/module/blank/drawql/License +++ b/module/blank/drawql/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/blank/exe_tools/License b/module/blank/exe_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/blank/exe_tools/License +++ b/module/blank/exe_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/blank/graphtools/License b/module/blank/graphtools/License index 0804aed8e3..72c80c1308 100644 --- a/module/blank/graphtools/License +++ b/module/blank/graphtools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/blank/image_tools/License b/module/blank/image_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/blank/image_tools/License +++ b/module/blank/image_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/blank/math_tools/License b/module/blank/math_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/blank/math_tools/License +++ b/module/blank/math_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/blank/mindx12/License b/module/blank/mindx12/License index 0804aed8e3..72c80c1308 100644 --- a/module/blank/mindx12/License +++ b/module/blank/mindx12/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/blank/mingl/License b/module/blank/mingl/License index 0804aed8e3..72c80c1308 100644 --- a/module/blank/mingl/License +++ b/module/blank/mingl/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/blank/minmetal/License b/module/blank/minmetal/License index 0804aed8e3..72c80c1308 100644 --- a/module/blank/minmetal/License +++ b/module/blank/minmetal/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/blank/minopengl/License b/module/blank/minopengl/License index 0804aed8e3..72c80c1308 100644 --- a/module/blank/minopengl/License +++ b/module/blank/minopengl/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/blank/minvulkan/License b/module/blank/minvulkan/License index 0804aed8e3..72c80c1308 100644 --- a/module/blank/minvulkan/License +++ b/module/blank/minvulkan/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/blank/minwebgl/License b/module/blank/minwebgl/License index 0804aed8e3..72c80c1308 100644 --- a/module/blank/minwebgl/License +++ b/module/blank/minwebgl/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/blank/minwebgpu/License b/module/blank/minwebgpu/License index 0804aed8e3..72c80c1308 100644 --- a/module/blank/minwebgpu/License +++ b/module/blank/minwebgpu/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/blank/minwgpu/License b/module/blank/minwgpu/License index 0804aed8e3..72c80c1308 100644 --- a/module/blank/minwgpu/License +++ b/module/blank/minwgpu/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/blank/paths_tools/License b/module/blank/paths_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/blank/paths_tools/License +++ b/module/blank/paths_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/blank/proper_path_tools/License b/module/blank/proper_path_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/blank/proper_path_tools/License +++ b/module/blank/proper_path_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/blank/rustql/License b/module/blank/rustql/License index 0804aed8e3..72c80c1308 100644 --- a/module/blank/rustql/License +++ b/module/blank/rustql/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/blank/second_brain/License b/module/blank/second_brain/License index 0804aed8e3..72c80c1308 100644 --- a/module/blank/second_brain/License +++ b/module/blank/second_brain/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/blank/w4d/License b/module/blank/w4d/License index 0804aed8e3..72c80c1308 100644 --- a/module/blank/w4d/License +++ b/module/blank/w4d/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/blank/wlang/License b/module/blank/wlang/License index 0804aed8e3..72c80c1308 100644 --- a/module/blank/wlang/License +++ b/module/blank/wlang/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/asbytes/Cargo.toml b/module/core/asbytes/Cargo.toml index 078279a063..5614306486 100644 --- a/module/core/asbytes/Cargo.toml +++ b/module/core/asbytes/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "asbytes" -version = "0.1.0" +version = "0.2.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -11,10 +11,10 @@ documentation = "https://docs.rs/asbytes" repository = "https://github.com/Wandalen/wTools/tree/master/module/core/asbytes" homepage = "https://github.com/Wandalen/wTools/tree/master/module/core/asbytes" description = """ -Unified and ergonomic abstraction for converting various data structures into raw byte slices. +Traits for viewing data as byte slices or consuming data into byte vectors. Relies on bytemuck for POD safety. """ -categories = [ "algorithms", "development-tools" ] -keywords = [ "fundamental", "general-purpose" ] +categories = [ "algorithms", "development-tools", "data-structures" ] # Added data-structures +keywords = [ "fundamental", "general-purpose", "bytes", "pod", "bytemuck" ] # Added keywords [lints] workspace = true @@ -24,21 +24,28 @@ features = [ "full" ] all-features = false [features] -default = [ "enabled", "as_bytes", "derive", "must_cast" ] +default = [ "enabled", "as_bytes", "into_bytes", "derive", "must_cast" ] # Added into_bytes full = [ "default" ] enabled = [] +# Feature for AsBytes trait and its implementations as_bytes = [ "dep:bytemuck" ] +# Feature for IntoBytes trait and its implementations +into_bytes = [ "as_bytes" ] -derive = [ "bytemuck/derive" ] # Provide derive macros for the various traits. -extern_crate_alloc = [ "bytemuck/extern_crate_alloc" ] # Provide utilities for alloc related types such as Box and Vec. -zeroable_maybe_uninit = [ "bytemuck/zeroable_maybe_uninit" ] # and zeroable_atomics: Provide more Zeroable impls. -wasm_simd = [ "bytemuck/wasm_simd" ] # Support more SIMD types. -aarch64_simd = [ "bytemuck/aarch64_simd" ] # Support more SIMD types. -min_const_generics = [ "bytemuck/min_const_generics" ] # Provides appropriate impls for arrays of all lengths instead of just for a select list of array lengths. -must_cast = [ "bytemuck/must_cast" ] # Provides the must_ functions, which will compile error if the requested cast can’t be statically verified. -const_zeroed = [ "bytemuck/const_zeroed" ] # +derive = [ "bytemuck?/derive" ] # Use ? syntax for optional dependency feature +extern_crate_alloc = [ "bytemuck?/extern_crate_alloc" ] # Use ? syntax +zeroable_maybe_uninit = [ "bytemuck?/zeroable_maybe_uninit" ] # Use ? syntax +wasm_simd = [ "bytemuck?/wasm_simd" ] # Use ? syntax +aarch64_simd = [ "bytemuck?/aarch64_simd" ] # Use ? syntax +min_const_generics = [ "bytemuck?/min_const_generics" ] # Use ? syntax +must_cast = [ "bytemuck?/must_cast" ] # Use ? syntax +const_zeroed = [ "bytemuck?/const_zeroed" ] # Use ? syntax [dependencies] +# Bytemuck is optional, enabled by as_bytes (and thus by into_bytes) +# Features like 'derive', 'extern_crate_alloc' are enabled via this crate's features bytemuck = { workspace = true, optional = true } [dev-dependencies] +# Make sure features needed for tests are enabled here +bytemuck = { workspace = true, features = [ "derive", "extern_crate_alloc" ] } diff --git a/module/core/asbytes/License b/module/core/asbytes/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/asbytes/License +++ b/module/core/asbytes/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/asbytes/Readme.md b/module/core/asbytes/Readme.md index c6667399eb..60d557bb69 100644 --- a/module/core/asbytes/Readme.md +++ b/module/core/asbytes/Readme.md @@ -3,33 +3,43 @@ # Module :: asbytes [![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/ModuleasbytesPush.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/ModuleasbytesPush.yml) [![docs.rs](https://img.shields.io/docsrs/asbytes?color=e3e8f0&logo=docs.rs)](https://docs.rs/asbytes) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) -The `asbytes` crate provides a convenient trait, `AsBytes`, for viewing common data structures as raw byte slices (`&[u8]`). It focuses on types that are safe to represent as bytes (Plain Old Data, or POD), leveraging the safety guarantees of the underlying `bytemuck` crate. + +The `asbytes` crate provides two convenient traits: +1. `AsBytes`: For viewing common data structures as raw byte slices (`&[u8]`). +2. `IntoBytes`: For consuming data structures into owned byte vectors (`Vec`). + +Both traits focus on types that are safe to represent as bytes (Plain Old Data, or POD), leveraging the safety guarantees of the underlying `bytemuck` crate. ## Why `asbytes`? While `bytemuck` provides the core functionality for safe byte-level casting (like `bytemuck::cast_slice` for collections and `bytemuck::bytes_of` for single items), `asbytes` offers a unified trait-based approach for common use cases: -1. **Consistency:** The `AsBytes` trait provides a single method, `.as_bytes()`, that works consistently across supported types like `Vec`, slices (`&[T]`), arrays (`[T; N]`), and single POD items wrapped in a tuple `(T,)`. -2. **Readability:** Calling `.as_bytes()` clearly signals the intent to get the raw byte representation, which is useful for tasks like serialization, hashing, or interfacing with low-level APIs (graphics, networking, etc.). -3. **Simpler Generics:** Functions can accept `T: AsBytes` to work generically with the byte representation of different compatible data structures. -4. **Convenience:** The trait also provides `.byte_size()` and `.len()` methods for easily getting the size in bytes and the number of elements, respectively. +1. **Consistency:** The `AsBytes` trait provides `.as_bytes()` for borrowing as `&[u8]`, while `IntoBytes` provides `.into_bytes()` for consuming into `Vec`. This works consistently across supported types. +2. **Readability:** Calling `.as_bytes()` or `.into_bytes()` clearly signals the intent to get a raw byte representation, useful for serialization, hashing, or low-level APIs (graphics, networking, etc.). +3. **Simpler Generics:** Functions can accept `T: AsBytes` or `T: IntoBytes` to work generically with the byte representation of different compatible data structures. +4. **Convenience:** `AsBytes` also provides `.byte_size()` and `.len()` methods for easily getting the size in bytes and the number of elements. -Essentially, `asbytes` acts as a focused convenience layer on top of `bytemuck` for the specific task of viewing data as bytes via a consistent trait method. +Essentially, `asbytes` acts as a focused convenience layer on top of `bytemuck` for the specific tasks of viewing or consuming data as bytes via consistent trait methods. ## How asbytes Differs from bytemuck -While bytemuck offers safe transmutation via its `Pod` trait and functions like `cast_slice`, it does not expose a dedicated trait for converting data structures into byte slices. `asbytes` introduces the `AsBytes` trait, abstracting these conversions and providing additional conveniences—such as direct byte size computation—on top of bytemuck's proven foundation. +While `bytemuck` offers safe transmutation via its `Pod` trait and functions like `cast_slice`, it does not expose dedicated traits for converting data structures into byte slices or vectors. `asbytes` introduces `AsBytes` (for borrowing as `&[u8]`) and `IntoBytes` (for consuming into `Vec`), abstracting these conversions and providing additional conveniences—such as direct byte size computation with `AsBytes`—on top of `bytemuck`'s proven foundation. + +## Examples -### Example +### `AsBytes` Example: Viewing Data as Byte Slices + +This example demonstrates the `AsBytes` trait. It shows how to get a `&[u8]` view of various data types (a `Vec`, a slice, an array, a single struct wrapped in a tuple, and a scalar wrapped in a tuple) without consuming the original data. This is useful for operations like inspecting byte patterns, hashing data without modification, or passing byte slices to functions that only need read access. The `.byte_size()` and `.len()` methods provide convenient ways to get the size in bytes and the number of elements, respectively. ```rust -// Make sure bytemuck is available for derives -extern crate bytemuck; + +// Make sure asbytes is available for derives +// asbytes = { version = "0.2", features = [ "derive" ] } use asbytes::AsBytes; // Import the trait // Define a POD struct #[ repr( C ) ] -#[ derive( Clone, Copy, bytemuck::Pod, bytemuck::Zeroable ) ] +#[ derive( Debug, Clone, Copy, asbytes::Pod, asbytes::Zeroable ) ] struct Point { x : f32, @@ -43,9 +53,10 @@ fn main() let points_slice : &[ Point ] = &points_vec[ .. ]; let points_array : [ Point; 1 ] = [ Point { x : 5.0, y : 6.0 } ]; - let vec_bytes = points_vec.as_bytes(); - let slice_bytes = points_slice.as_bytes(); - let array_bytes = points_array.as_bytes(); + // Use AsBytes to get byte slices (&[u8]) without consuming the original data + let vec_bytes : &[ u8 ] = points_vec.as_bytes(); + let slice_bytes : &[ u8 ] = points_slice.as_bytes(); + let array_bytes : &[ u8 ] = points_array.as_bytes(); println!( "Vec Bytes: length={}, data={:?}", points_vec.byte_size(), vec_bytes ); println!( "Slice Bytes: length={}, data={:?}", slice_bytes.byte_size(), slice_bytes ); @@ -57,20 +68,139 @@ fn main() let single_point = Point { x : -1.0, y : -2.0 }; let single_point_tuple = ( single_point, ); // Wrap in a single-element tuple - let point_bytes = single_point_tuple.as_bytes(); + let point_bytes : &[ u8 ] = single_point_tuple.as_bytes(); println!( "Single Point Bytes: length={}, data={:?}", single_point_tuple.byte_size(), point_bytes ); println!( "Single Point Element Count: {}", single_point_tuple.len() ); // Output: 1 let scalar_tuple = ( 12345u32, ); - let scalar_bytes = scalar_tuple.as_bytes(); + let scalar_bytes : &[ u8 ] = scalar_tuple.as_bytes(); println!( "Scalar Bytes: length={}, data={:?}", scalar_tuple.byte_size(), scalar_bytes ); + + // Original data is still available after calling .as_bytes() + println!( "Original Vec still usable: {:?}", points_vec ); } ``` +### `IntoBytes` Example: Consuming Data into Owned Byte Vectors for Hashing + +This example showcases the IntoBytes trait, demonstrating how it facilitates writing different data types to an I/O stream (simulated here by a Vec). The generic send_data function accepts any type T that implements IntoBytes. Inside the function, data.into_bytes() consumes the input data and returns an owned Vec. This owned vector is necessary when the receiving function or operation (like writer.write_all) requires ownership or when the data needs to live beyond the current scope (e.g., in asynchronous operations). The example sends a POD struct (with explicit padding for Pod safety), a String, a Vec, and an array, showing how IntoBytes provides a uniform way to prepare diverse data for serialization or transmission. Note that types like String and Vec are moved and consumed, while Copy types are technically moved but the original variable remains usable due to the copy. + +``````rust +// Add dependencies to Cargo.toml: +// asbytes = { version = "0.2", features = [ "derive" ] } +use asbytes::IntoBytes; +use std::io::Write; // Using std::io::Write as a simulated target + +// Define a POD struct +// Added explicit padding to ensure no implicit padding bytes, satisfying `Pod` requirements. +#[ repr( C ) ] +#[ derive( Clone, Copy, Debug, asbytes::Pod, asbytes::Zeroable ) ] +struct DataPacketHeader +{ + packet_id : u64, // 8 bytes + payload_len : u32, // 4 bytes + checksum : u16, // 2 bytes + _padding : [ u8; 2 ], // 2 bytes explicit padding to align to 8 bytes (u64 alignment) +} // Total size = 16 bytes (128 bits) + +/// Simulates writing any data that implements IntoBytes to a writer (e.g., file, network stream). +/// This function consumes the input data. +/// It takes a mutable reference to a writer `W` which could be Vec, a File, TcpStream, etc. +fn send_data< T : IntoBytes, W : Write >( data : T, writer : &mut W ) -> std::io::Result<()> +{ + // 1. Consume the data into an owned byte vector using IntoBytes. + // This is useful because the writer might perform operations asynchronously, + // or the data might need manipulation before sending, requiring ownership. + let bytes : Vec< u8 > = data.into_bytes(); + + // 2. Write the owned bytes to the provided writer. + // The `write_all` method requires a byte slice (`&[u8]`). + writer.write_all( &bytes )?; + + // Optional: Add a separator or framing bytes if needed for the protocol + // writer.write_all( b"\n---\n" )?; + + Ok(()) +} + +fn main() +{ + // --- Simulate an output buffer (could be a file, network socket, etc.) --- + let mut output_buffer : Vec< u8 > = Vec::new(); + + // --- Different types of data to serialize and send --- + let header = DataPacketHeader + { + packet_id : 0xABCDEF0123456789, + payload_len : 128, + checksum : 0x55AA, + _padding : [ 0, 0 ], // Initialize padding + }; + let payload_message = String::from( "This is the core message payload." ); + let sensor_readings : Vec< f32 > = vec![ 25.5, -10.0, 99.9, 0.1 ]; + // Ensure sensor readings are POD if necessary (f32 is Pod) + let end_marker : [ u8; 4 ] = [ 0xDE, 0xAD, 0xBE, 0xEF ]; + + println!( "Sending different data types to the buffer...\n" ); + + // --- Send data using the generic function --- + + // Send the header (struct wrapped in tuple). Consumes the tuple. + println!( "Sending Header: {:?}", header ); + send_data( ( header, ), &mut output_buffer ).expect( "Failed to write header" ); + // The original `header` is still available because it's `Copy`. + + // Send the payload (String). Consumes the `payload_message` string. + println!( "Sending Payload Message: \"{}\"", payload_message ); + send_data( payload_message, &mut output_buffer ).expect( "Failed to write payload message" ); + // `payload_message` is no longer valid here. + + // Send sensor readings (Vec). Consumes the `sensor_readings` vector. + // Check if f32 requires Pod trait - yes, bytemuck implements Pod for f32. + // Vec where T: Pod is handled by IntoBytes. + println!( "Sending Sensor Readings: {:?}", sensor_readings ); + send_data( sensor_readings, &mut output_buffer ).expect( "Failed to write sensor readings" ); + // `sensor_readings` is no longer valid here. + + // Send the end marker (array). Consumes the array (effectively Copy). + println!( "Sending End Marker: {:?}", end_marker ); + send_data( end_marker, &mut output_buffer ).expect( "Failed to write end marker" ); + // The original `end_marker` is still available because it's `Copy`. + + + println!( "\n--- Final Buffer Content ({} bytes) ---", output_buffer.len() ); + // Print bytes in a more readable hex format + for ( i, chunk ) in output_buffer.chunks( 16 ).enumerate() + { + print!( "{:08x}: ", i * 16 ); + for byte in chunk + { + print!( "{:02x} ", byte ); + } + // Print ASCII representation + print!( " |" ); + for &byte in chunk + { + if byte >= 32 && byte <= 126 { + print!( "{}", byte as char ); + } else { + print!( "." ); + } + } + println!( "|" ); + } + + println!( "\nDemonstration complete. The send_data function handled multiple data types" ); + println!( "by converting them to owned byte vectors using IntoBytes, suitable for I/O operations." ); +} +`````` + ### To add to your project ```sh cargo add asbytes +# Make sure bytemuck is also added if you need POD derives or its features +# cargo add bytemuck --features derive ``` ### Try out from the repository @@ -78,6 +208,8 @@ cargo add asbytes ```sh git clone https://github.com/Wandalen/wTools cd wTools -cd examples/asbytes -cargo run +# Run the AsBytes example (replace with actual example path if different) +# cargo run --example asbytes_as_bytes_trivial +# Or run the IntoBytes example (requires adding sha2 to the example's deps) +# cargo run --example asbytes_into_bytes_trivial ``` diff --git a/module/core/asbytes/examples/asbytes_as_bytes_trivial.rs b/module/core/asbytes/examples/asbytes_as_bytes_trivial.rs new file mode 100644 index 0000000000..c5f4066880 --- /dev/null +++ b/module/core/asbytes/examples/asbytes_as_bytes_trivial.rs @@ -0,0 +1,48 @@ +//! This example demonstrates the `AsBytes` trait. It shows how to get a `&[u8]` view of various data types (a `Vec`, a slice, an array, a single struct wrapped in a tuple, and a scalar wrapped in a tuple) without consuming the original data. This is useful for operations like inspecting byte patterns, hashing data without modification, or passing byte slices to functions that only need read access. The `.byte_size()` and `.len()` methods provide convenient ways to get the size in bytes and the number of elements, respectively. + +// Make sure asbytes is available for derives +// asbytes = { version = "0.2", features = [ "derive" ] } +use asbytes::AsBytes; // Import the trait + +// Define a POD struct +#[ repr( C ) ] +#[ derive( Debug, Clone, Copy, asbytes::Pod, asbytes::Zeroable ) ] +struct Point +{ + x : f32, + y : f32, +} + +fn main() +{ + // --- Collections --- + let points_vec : Vec< Point > = vec![ Point { x : 1.0, y : 2.0 }, Point { x : 3.0, y : 4.0 } ]; + let points_slice : &[ Point ] = &points_vec[ .. ]; + let points_array : [ Point; 1 ] = [ Point { x : 5.0, y : 6.0 } ]; + + // Use AsBytes to get byte slices (&[u8]) without consuming the original data + let vec_bytes : &[ u8 ] = points_vec.as_bytes(); + let slice_bytes : &[ u8 ] = points_slice.as_bytes(); + let array_bytes : &[ u8 ] = points_array.as_bytes(); + + println!( "Vec Bytes: length={}, data={:?}", points_vec.byte_size(), vec_bytes ); + println!( "Slice Bytes: length={}, data={:?}", slice_bytes.byte_size(), slice_bytes ); + println!( "Array Bytes: length={}, data={:?}", points_array.byte_size(), array_bytes ); + println!( "Vec Element Count: {}", points_vec.len() ); // Output: 2 + println!( "Array Element Count: {}", points_array.len() ); // Output: 1 + + // --- Single POD Item (using tuple trick) --- + let single_point = Point { x : -1.0, y : -2.0 }; + let single_point_tuple = ( single_point, ); // Wrap in a single-element tuple + + let point_bytes : &[ u8 ] = single_point_tuple.as_bytes(); + println!( "Single Point Bytes: length={}, data={:?}", single_point_tuple.byte_size(), point_bytes ); + println!( "Single Point Element Count: {}", single_point_tuple.len() ); // Output: 1 + + let scalar_tuple = ( 12345u32, ); + let scalar_bytes : &[ u8 ] = scalar_tuple.as_bytes(); + println!( "Scalar Bytes: length={}, data={:?}", scalar_tuple.byte_size(), scalar_bytes ); + + // Original data is still available after calling .as_bytes() + println!( "Original Vec still usable: {:?}", points_vec ); +} diff --git a/module/core/asbytes/examples/asbytes_into_bytes_trivial.rs b/module/core/asbytes/examples/asbytes_into_bytes_trivial.rs new file mode 100644 index 0000000000..7a42c34285 --- /dev/null +++ b/module/core/asbytes/examples/asbytes_into_bytes_trivial.rs @@ -0,0 +1,109 @@ +//! This example showcases the IntoBytes trait, demonstrating how it facilitates writing different data types to an I/O stream (simulated here by a Vec). The generic send_data function accepts any type T that implements IntoBytes. Inside the function, data.into_bytes() consumes the input data and returns an owned Vec. This owned vector is necessary when the receiving function or operation (like writer.write_all) requires ownership or when the data needs to live beyond the current scope (e.g., in asynchronous operations). The example sends a POD struct (with explicit padding for Pod safety), a String, a Vec, and an array, showing how IntoBytes provides a uniform way to prepare diverse data for serialization or transmission. Note that types like String and Vec are moved and consumed, while Copy types are technically moved but the original variable remains usable due to the copy. + +// Add dependencies to Cargo.toml: +// asbytes = { version = "0.2", features = [ "derive" ] } +use asbytes::IntoBytes; +use std::io::Write; // Using std::io::Write as a simulated target + +// Define a POD struct +// Added explicit padding to ensure no implicit padding bytes, satisfying `Pod` requirements. +#[ repr( C ) ] +#[ derive( Clone, Copy, Debug, asbytes::Pod, asbytes::Zeroable ) ] +struct DataPacketHeader +{ + packet_id : u64, // 8 bytes + payload_len : u32, // 4 bytes + checksum : u16, // 2 bytes + _padding : [ u8; 2 ], // 2 bytes explicit padding to align to 8 bytes (u64 alignment) +} // Total size = 16 bytes (128 bits) + +/// Simulates writing any data that implements IntoBytes to a writer (e.g., file, network stream). +/// This function consumes the input data. +/// It takes a mutable reference to a writer `W` which could be Vec, a File, TcpStream, etc. +fn send_data< T : IntoBytes, W : Write >( data : T, writer : &mut W ) -> std::io::Result<()> +{ + // 1. Consume the data into an owned byte vector using IntoBytes. + // This is useful because the writer might perform operations asynchronously, + // or the data might need manipulation before sending, requiring ownership. + let bytes : Vec< u8 > = data.into_bytes(); + + // 2. Write the owned bytes to the provided writer. + // The `write_all` method requires a byte slice (`&[u8]`). + writer.write_all( &bytes )?; + + // Optional: Add a separator or framing bytes if needed for the protocol + // writer.write_all( b"\n---\n" )?; + + Ok(()) +} + +fn main() +{ + // --- Simulate an output buffer (could be a file, network socket, etc.) --- + let mut output_buffer : Vec< u8 > = Vec::new(); + + // --- Different types of data to serialize and send --- + let header = DataPacketHeader + { + packet_id : 0xABCDEF0123456789, + payload_len : 128, + checksum : 0x55AA, + _padding : [ 0, 0 ], // Initialize padding + }; + let payload_message = String::from( "This is the core message payload." ); + let sensor_readings : Vec< f32 > = vec![ 25.5, -10.0, 99.9, 0.1 ]; + // Ensure sensor readings are POD if necessary (f32 is Pod) + let end_marker : [ u8; 4 ] = [ 0xDE, 0xAD, 0xBE, 0xEF ]; + + println!( "Sending different data types to the buffer...\n" ); + + // --- Send data using the generic function --- + + // Send the header (struct wrapped in tuple). Consumes the tuple. + println!( "Sending Header: {:?}", header ); + send_data( ( header, ), &mut output_buffer ).expect( "Failed to write header" ); + // The original `header` is still available because it's `Copy`. + + // Send the payload (String). Consumes the `payload_message` string. + println!( "Sending Payload Message: \"{}\"", payload_message ); + send_data( payload_message, &mut output_buffer ).expect( "Failed to write payload message" ); + // `payload_message` is no longer valid here. + + // Send sensor readings (Vec). Consumes the `sensor_readings` vector. + // Check if f32 requires Pod trait - yes, bytemuck implements Pod for f32. + // Vec where T: Pod is handled by IntoBytes. + println!( "Sending Sensor Readings: {:?}", sensor_readings ); + send_data( sensor_readings, &mut output_buffer ).expect( "Failed to write sensor readings" ); + // `sensor_readings` is no longer valid here. + + // Send the end marker (array). Consumes the array (effectively Copy). + println!( "Sending End Marker: {:?}", end_marker ); + send_data( end_marker, &mut output_buffer ).expect( "Failed to write end marker" ); + // The original `end_marker` is still available because it's `Copy`. + + + println!( "\n--- Final Buffer Content ({} bytes) ---", output_buffer.len() ); + // Print bytes in a more readable hex format + for ( i, chunk ) in output_buffer.chunks( 16 ).enumerate() + { + print!( "{:08x}: ", i * 16 ); + for byte in chunk + { + print!( "{:02x} ", byte ); + } + // Print ASCII representation + print!( " |" ); + for &byte in chunk + { + if byte >= 32 && byte <= 126 { + print!( "{}", byte as char ); + } else { + print!( "." ); + } + } + println!( "|" ); + } + + println!( "\nDemonstration complete. The send_data function handled multiple data types" ); + println!( "by converting them to owned byte vectors using IntoBytes, suitable for I/O operations." ); +} \ No newline at end of file diff --git a/module/core/asbytes/src/as_bytes.rs b/module/core/asbytes/src/as_bytes.rs new file mode 100644 index 0000000000..f5b0a99d23 --- /dev/null +++ b/module/core/asbytes/src/as_bytes.rs @@ -0,0 +1,198 @@ +/// Define a private namespace for all its items. +mod private +{ + + pub use bytemuck:: + { + Pod, + }; + + /// Trait for borrowing data as byte slices. + /// This trait abstracts the conversion of types that implement Pod (or collections thereof) + /// into their raw byte representation as a slice (`&[u8]`). + + pub trait AsBytes + { + + /// Returns the underlying byte slice of the data. + fn as_bytes( &self ) -> &[ u8 ] + ; + + /// Returns an owned vector containing a copy of the bytes of the data. + /// The default implementation clones the bytes from `as_bytes()`. + #[ inline ] + fn to_bytes_vec( &self ) -> Vec< u8 > + { + self.as_bytes().to_vec() + } + + /// Returns the size in bytes of the data. + #[ inline ] + fn byte_size( &self ) -> usize + { + self.as_bytes().len() + } + + /// Returns the count of elements contained in the data. + /// For single-element tuples `(T,)`, this is 1. + /// For collections (`Vec`, `&[T]`, `[T; N]`), this is the number of `T` items. + fn len( &self ) -> usize; + + } + + /// Implementation for single POD types wrapped in a tuple `(T,)`. + + impl< T : Pod > AsBytes for ( T, ) + { + + #[ inline ] + fn as_bytes( &self ) -> &[ u8 ] + { + bytemuck::bytes_of( &self.0 ) + } + + #[ inline ] + fn byte_size( &self ) -> usize + { + std::mem::size_of::< T >() + } + + #[ inline ] + fn len( &self ) -> usize + { + 1 + } + + } + + /// Implementation for Vec where T is POD. + + impl< T : Pod > AsBytes for Vec< T > + { + + #[ inline ] + fn as_bytes( &self ) -> &[ u8 ] + { + bytemuck::cast_slice( self ) + } + + #[ inline ] + fn byte_size( &self ) -> usize + { + self.len() * std::mem::size_of::< T >() + } + + #[ inline ] + fn len( &self ) -> usize + { + self.len() + } + + } + + /// Implementation for [T] where T is POD. + + impl< T : Pod > AsBytes for [ T ] + { + + #[ inline ] + fn as_bytes( &self ) -> &[ u8 ] + { + bytemuck::cast_slice( self ) + } + + #[ inline ] + fn byte_size( &self ) -> usize + { + self.len() * std::mem::size_of::< T >() + } + + #[ inline ] + fn len( &self ) -> usize + { + self.len() + } + + } + + /// Implementation for [T; N] where T is POD. + + impl< T : Pod, const N : usize > AsBytes for [ T ; N ] + { + + #[ inline ] + fn as_bytes( &self ) -> &[ u8 ] + { + bytemuck::cast_slice( self ) + } + + #[ inline ] + fn byte_size( &self ) -> usize + { + N * std::mem::size_of::< T >() + } + + #[ inline ] + fn len( &self ) -> usize + { + N + } + + } + +} + + +#[ doc( inline ) ] +#[ allow( unused_imports ) ] +pub use own::*; + +/// Own namespace of the module. + +#[ allow( unused_imports ) ] +pub mod own +{ + use super::*; + + #[ doc( inline ) ] + pub use orphan::*; + +} + + +#[ doc( inline ) ] +#[ allow( unused_imports ) ] +pub use own::*; + +/// Orphan namespace of the module. + +#[ allow( unused_imports ) ] +pub mod orphan +{ + use super::*; + #[ doc( inline ) ] + pub use exposed::*; + +} + +/// Exposed namespace of the module. + +#[ allow( unused_imports ) ] +pub mod exposed +{ + use super::*; + + #[ doc( inline ) ] + pub use prelude::*; + +} + +/// Prelude to use essentials: `use my_module::prelude::*`. + +#[ allow( unused_imports ) ] +pub mod prelude +{ + use super::*; + + pub use private::AsBytes; +} \ No newline at end of file diff --git a/module/core/asbytes/src/into_bytes.rs b/module/core/asbytes/src/into_bytes.rs new file mode 100644 index 0000000000..2ee3f8014c --- /dev/null +++ b/module/core/asbytes/src/into_bytes.rs @@ -0,0 +1,211 @@ +/// Define a private namespace for all its items. +mod private +{ + + pub use bytemuck:: + { + Pod, + }; + + /// Trait for consuming data into an owned byte vector. + /// This trait is for types that can be meaningfully converted into a `Vec< u8 >` + /// by consuming the original value. + pub trait IntoBytes + { + /// Consumes the value and returns its byte representation as an owned `Vec< u8 >`. + fn into_bytes( self ) -> Vec< u8 >; + } + + // --- Implementations for IntoBytes --- + + /// Implementation for single POD types wrapped in a tuple `(T,)`. + /// This mirrors the approach used in `AsBytes` for consistency with single items. + /// Covers primitive types (u8, i32, f64, bool, etc.) and other POD structs when wrapped. + impl< T : Pod > IntoBytes for ( T, ) + { + #[ inline ] + fn into_bytes( self ) -> Vec< u8 > + { + // self.0 is the owned T value. Get bytes using bytes_of and clone to Vec. + bytemuck::bytes_of( &self.0 ).to_vec() + } + } + + /// Implementation for &T. + impl< T : Pod > IntoBytes for &T + { + #[ inline ] + fn into_bytes( self ) -> Vec< u8 > + { + bytemuck::bytes_of( self ).to_vec() + } + } + + /// Implementation for String. + impl IntoBytes for String + { + #[ inline ] + fn into_bytes( self ) -> Vec< u8 > + { + // String::into_bytes already returns Vec< u8 > + self.into_bytes() + } + } + + /// Implementation for &str. + /// This handles string slices specifically. + impl IntoBytes for &str + { + #[ inline ] + fn into_bytes( self ) -> Vec< u8 > + { + // &str has a built-in method to get bytes. + self.as_bytes().to_vec() + } + } + + /// Implementation for owned arrays of POD types. + impl< T : Pod, const N : usize > IntoBytes for [ T ; N ] + { + #[ inline ] + fn into_bytes( self ) -> Vec< u8 > + { + // Since T: Pod, [T; N] is Copy (or moves if T isn't Copy, but Pod implies Copy usually). + // Get a byte slice view using cast_slice (requires &self) + // and then clone it into a Vec. + bytemuck::cast_slice( &self ).to_vec() + } + } + + /// Implementation for owned vectors of POD types. + impl< T : Pod > IntoBytes for Vec< T > + { + #[ inline ] + fn into_bytes( self ) -> Vec< u8 > + { + // Use bytemuck's safe casting for Vec to Vec< u8 > + bytemuck::cast_slice( self.as_slice() ).to_vec() + } + } + + /// Implementation for Box where T is POD. + impl< T : Pod > IntoBytes for Box< T > + { + #[ inline ] + fn into_bytes( self ) -> Vec< u8 > + { + // Dereference the Box to get T, get its bytes, and clone into a Vec. + // The Box is dropped after self is consumed. + bytemuck::bytes_of( &*self ).to_vec() + } + } + + /// Implementation for &[T] where T is Pod. + /// This handles slices of POD types specifically. + impl< T : Pod > IntoBytes for &[ T ] + { + #[ inline ] + fn into_bytes( self ) -> Vec< u8 > + { + // Use cast_slice on the borrowed slice and convert to owned Vec. + bytemuck::cast_slice( self ).to_vec() + } + } + + /// Implementation for Box<[T]> where T is POD. + impl< T : Pod > IntoBytes for Box< [ T ] > + { + #[ inline ] + fn into_bytes( self ) -> Vec< u8 > + { + // Dereference the Box to get &[T], cast to bytes, and clone into a Vec. + // The Box is dropped after self is consumed. + bytemuck::cast_slice( &*self ).to_vec() + } + } + + /// Implementation for VecDeque where T is POD. + impl< T : Pod > IntoBytes for std::collections::VecDeque< T > + { + #[ inline ] + fn into_bytes( self ) -> Vec< u8 > + { + // Iterate through the deque, consuming it, and extend a byte vector + // with the bytes of each element. This handles the potentially + // non-contiguous nature of the deque's internal ring buffer safely. + let mut bytes = Vec::with_capacity( self.len() * std::mem::size_of::< T >() ); + for element in self + { + bytes.extend_from_slice( bytemuck::bytes_of( &element ) ); + } + bytes + } + } + + /// Implementation for CString. + /// Returns the byte slice *without* the trailing NUL byte. + impl IntoBytes for std::ffi::CString + { + #[ inline ] + fn into_bytes( self ) -> Vec< u8 > + { + // CString::into_bytes() returns the underlying buffer without the NUL. + self.into_bytes() + } + } + +} + + +#[ doc( inline ) ] +#[ allow( unused_imports ) ] +pub use own::*; + +/// Own namespace of the module. + +#[ allow( unused_imports ) ] +pub mod own +{ + use super::*; + + #[ doc( inline ) ] + pub use orphan::*; + +} + + +#[ doc( inline ) ] +#[ allow( unused_imports ) ] +pub use own::*; + +/// Orphan namespace of the module. + +#[ allow( unused_imports ) ] +pub mod orphan +{ + use super::*; + #[ doc( inline ) ] + pub use exposed::*; + +} + +/// Exposed namespace of the module. + +#[ allow( unused_imports ) ] +pub mod exposed +{ + use super::*; + + #[ doc( inline ) ] + pub use prelude::*; + +} + +/// Prelude to use essentials: `use my_module::prelude::*`. + +#[ allow( unused_imports ) ] +pub mod prelude +{ + use super::*; + pub use private::IntoBytes; +} \ No newline at end of file diff --git a/module/core/asbytes/src/lib.rs b/module/core/asbytes/src/lib.rs index a5439f8068..5833fef16e 100644 --- a/module/core/asbytes/src/lib.rs +++ b/module/core/asbytes/src/lib.rs @@ -1,4 +1,3 @@ - #![ 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/asbytes/latest/asbytes/" ) ] @@ -8,6 +7,8 @@ #[ cfg( feature = "enabled" ) ] pub mod dependency { + // Only include bytemuck if either as_bytes or into_bytes is enabled + #[ cfg( any( feature = "as_bytes", feature = "into_bytes" ) ) ] pub use ::bytemuck; } @@ -15,134 +16,13 @@ pub mod dependency #[ cfg( feature = "enabled" ) ] mod private { - - pub use bytemuck:: - { - Pod, - }; - - /// Trait for converting data to byte slices. - /// This trait abstracts the conversion of types that implement Pod into their raw byte representation. - #[ cfg( feature = "as_bytes" ) ] - pub trait AsBytes - { - - /// Returns the underlying byte slice of the data. - fn as_bytes( &self ) -> &[ u8 ] - ; - - /// Returns the size in bytes of the data. - #[ inline ] - fn byte_size( &self ) -> usize - { - self.as_bytes().len() - } - - /// Returns the count of scalar elements contained in the data. - /// For flat structures, this corresponds to the number of elements. - /// For multidimensional data, this value may differ from the total number of components. - fn len( &self ) -> usize; - - } - - /// Implementation for any single POD type. - impl< T : Pod > AsBytes for ( T, ) - { - - #[ inline ] - fn as_bytes( &self ) -> &[ u8 ] - { - // Use bytes_of to get the byte slice of a single POD item - bytemuck::bytes_of( &self.0 ) - } - - #[ inline ] - fn byte_size( &self ) -> usize - { - // The size is simply the size of the type itself - std::mem::size_of::< T >() - } - - #[ inline ] - fn len( &self ) -> usize - { - // A single item has a length of 1 element - 1 - } - - } - - impl< T : Pod > AsBytes for Vec< T > - { - - #[ inline ] - fn as_bytes( &self ) -> &[ u8 ] - { - bytemuck::cast_slice( self ) - } - - #[ inline ] - fn byte_size( &self ) -> usize - { - self.len() * std::mem::size_of::< T >() / std::mem::size_of::< u8 >() - } - - #[ inline ] - fn len( &self ) -> usize - { - self.len() - } - - } - - impl< T : Pod > AsBytes for [ T ] - { - - #[ inline ] - fn as_bytes( &self ) -> &[ u8 ] - { - bytemuck::cast_slice( self ) - } - - #[ inline ] - fn byte_size( &self ) -> usize - { - self.len() * std::mem::size_of::< T >() / std::mem::size_of::< u8 >() - } - - #[ inline ] - fn len( &self ) -> usize - { - self.len() - } - - } - - impl< T : Pod, const N : usize > AsBytes for [ T ; N ] - { - - #[ inline ] - fn as_bytes( &self ) -> &[ u8 ] - { - bytemuck::cast_slice( self ) - } - - #[ inline ] - fn byte_size( &self ) -> usize - { - self.len() * std::mem::size_of::< T >() / std::mem::size_of::< u8 >() - } - - #[ inline ] - fn len( &self ) -> usize - { - N - } - - } - } +#[ cfg( feature = "as_bytes" ) ] +mod as_bytes; +#[ cfg( feature = "into_bytes" ) ] +mod into_bytes; + #[ cfg( feature = "enabled" ) ] #[ doc( inline ) ] #[ allow( unused_imports ) ] @@ -158,6 +38,15 @@ pub mod own #[ doc( inline ) ] pub use orphan::*; + #[ doc( inline ) ] + #[ cfg( feature = "as_bytes" ) ] + pub use as_bytes::orphan::*; + #[ doc( inline ) ] + #[ cfg( feature = "into_bytes" ) ] + pub use into_bytes::orphan::*; + + // Re-export bytemuck items only if a feature needing it is enabled + #[ cfg( any( feature = "as_bytes", feature = "into_bytes" ) ) ] #[ doc( inline ) ] pub use bytemuck:: { @@ -196,6 +85,11 @@ pub mod own Zeroable, ZeroableInOption, }; + + // Expose allocation submodule if into_bytes and extern_crate_alloc are enabled + #[ cfg( all( feature = "into_bytes", feature = "extern_crate_alloc" ) ) ] + pub use bytemuck::allocation; + } #[ cfg( feature = "enabled" ) ] @@ -209,6 +103,7 @@ pub use own::*; pub mod orphan { use super::*; + #[ doc( inline ) ] pub use exposed::*; @@ -222,14 +117,14 @@ pub mod exposed use super::*; #[ doc( inline ) ] - pub use prelude::*; - #[ cfg( feature = "as_bytes" ) ] - pub use private:: - { - AsBytes, - Pod, - }; + pub use as_bytes::exposed::*; + #[ doc( inline ) ] + #[ cfg( feature = "into_bytes" ) ] + pub use into_bytes::exposed::*; + + #[ doc( inline ) ] + pub use prelude::*; } @@ -239,4 +134,10 @@ pub mod exposed pub mod prelude { use super::*; + #[ doc( inline ) ] + #[ cfg( feature = "as_bytes" ) ] + pub use as_bytes::prelude::*; + #[ doc( inline ) ] + #[ cfg( feature = "into_bytes" ) ] + pub use into_bytes::prelude::*; } diff --git a/module/core/asbytes/tests/inc/basic_test.rs b/module/core/asbytes/tests/inc/as_bytes_test.rs similarity index 100% rename from module/core/asbytes/tests/inc/basic_test.rs rename to module/core/asbytes/tests/inc/as_bytes_test.rs diff --git a/module/core/asbytes/tests/inc/into_bytes_test.rs b/module/core/asbytes/tests/inc/into_bytes_test.rs new file mode 100644 index 0000000000..f5a0c07389 --- /dev/null +++ b/module/core/asbytes/tests/inc/into_bytes_test.rs @@ -0,0 +1,156 @@ +#![ cfg( all( feature = "enabled", feature = "into_bytes" ) ) ] + +use asbytes::IntoBytes; // Import the specific trait +use std::mem; + +// Define a simple POD struct for testing (can be copied from basic_test.rs) +#[ repr( C ) ] +#[ derive( Clone, Copy, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable ) ] +struct Point +{ + x : i32, + y : i32, +} + +#[ test ] +fn test_tuple_scalar_into_bytes() +{ + let scalar_tuple = ( 123u32, ); + let expected_bytes = 123u32.to_le_bytes().to_vec(); + let bytes = scalar_tuple.into_bytes(); + + assert_eq!( bytes.len(), mem::size_of::< u32 >() ); + assert_eq!( bytes, expected_bytes ); +} + +#[ test ] +fn test_tuple_struct_into_bytes() +{ + let point = Point { x : 10, y : -20 }; + let struct_tuple = ( point, ); + let expected_bytes = bytemuck::bytes_of( &point ).to_vec(); + let bytes = struct_tuple.into_bytes(); + + assert_eq!( bytes.len(), mem::size_of::< Point >() ); + assert_eq!( bytes, expected_bytes ); +} + +#[ test ] +fn test_string_into_bytes() +{ + let s = String::from( "hello" ); + let expected_bytes = vec![ b'h', b'e', b'l', b'l', b'o' ]; + // Clone s before moving it into into_bytes for assertion + let bytes = s.clone().into_bytes(); + + assert_eq!( bytes.len(), s.len() ); + assert_eq!( bytes, expected_bytes ); +} + +#[ test ] +fn test_str_into_bytes() +{ + let s = "hello"; + let expected_bytes = vec![ b'h', b'e', b'l', b'l', b'o' ]; + // Clone s before moving it into into_bytes for assertion + let bytes = s.into_bytes(); + + assert_eq!( bytes.len(), s.len() ); + assert_eq!( bytes, expected_bytes ); +} + +#[ test ] +fn test_array_into_bytes() +{ + let arr : [ u16 ; 3 ] = [ 100, 200, 300 ]; + let expected_bytes = bytemuck::cast_slice( &arr ).to_vec(); + let bytes = arr.into_bytes(); // arr is Copy + + assert_eq!( bytes.len(), arr.len() * mem::size_of::< u16 >() ); + assert_eq!( bytes, expected_bytes ); +} + +#[ test ] +fn test_vec_into_bytes() +{ + let v = vec![ Point { x : 1, y : 2 }, Point { x : 3, y : 4 } ]; + let expected_bytes = bytemuck::cast_slice( v.as_slice() ).to_vec(); + let expected_len = v.len() * mem::size_of::< Point >(); + // Clone v before moving it into into_bytes for assertion + let bytes = v.clone().into_bytes(); + + assert_eq!( bytes.len(), expected_len ); + assert_eq!( bytes, expected_bytes ); +} + +#[ test ] +fn test_box_t_into_bytes() +{ + let b = Box::new( Point { x : 5, y : 5 } ); + let expected_bytes = bytemuck::bytes_of( &*b ).to_vec(); + let expected_len = mem::size_of::< Point >(); + let bytes = b.into_bytes(); + + assert_eq!( bytes.len(), expected_len ); + assert_eq!( bytes, expected_bytes ); +} + +#[ test ] +fn test_slice_into_bytes() +{ + let slice : &[ u32 ] = &[ 10, 20, 30 ][ .. ]; + let expected_bytes = bytemuck::cast_slice( &*slice ).to_vec(); + let expected_len = slice.len() * mem::size_of::< u32 >(); + let bytes = slice.into_bytes(); + + assert_eq!( bytes.len(), expected_len ); + assert_eq!( bytes, expected_bytes ); +} + +#[ test ] +fn test_box_slice_into_bytes() +{ + let slice : Box< [ u32 ] > = vec![ 10, 20, 30 ].into_boxed_slice(); + let expected_bytes = bytemuck::cast_slice( &*slice ).to_vec(); + let expected_len = slice.len() * mem::size_of::< u32 >(); + let bytes = slice.into_bytes(); + + assert_eq!( bytes.len(), expected_len ); + assert_eq!( bytes, expected_bytes ); +} + +#[ test ] +fn test_vecdeque_into_bytes() +{ + use std::collections::VecDeque; // Keep local use for VecDeque + let mut deque : VecDeque< u16 > = VecDeque::new(); + deque.push_back( 10 ); + deque.push_back( 20 ); + deque.push_front( 5 ); // deque is now [5, 10, 20] + + // Expected bytes for [5, 10, 20] (little-endian) + let expected_bytes = vec! + [ + 5u16.to_le_bytes()[ 0 ], 5u16.to_le_bytes()[ 1 ], + 10u16.to_le_bytes()[ 0 ], 10u16.to_le_bytes()[ 1 ], + 20u16.to_le_bytes()[ 0 ], 20u16.to_le_bytes()[ 1 ], + ]; + let expected_len = deque.len() * mem::size_of::< u16 >(); + let bytes = deque.into_bytes(); + + assert_eq!( bytes.len(), expected_len ); + assert_eq!( bytes, expected_bytes ); +} + +#[ test ] +fn test_cstring_into_bytes() +{ + use std::ffi::CString; // Keep local use for CString + let cs = CString::new( "world" ).unwrap(); + let expected_bytes = vec![ b'w', b'o', b'r', b'l', b'd' ]; // No NUL byte + let expected_len = expected_bytes.len(); + let bytes = cs.into_bytes(); + + assert_eq!( bytes.len(), expected_len ); + assert_eq!( bytes, expected_bytes ); +} diff --git a/module/core/asbytes/tests/inc/mod.rs b/module/core/asbytes/tests/inc/mod.rs index 329271ad56..1be093f8b6 100644 --- a/module/core/asbytes/tests/inc/mod.rs +++ b/module/core/asbytes/tests/inc/mod.rs @@ -1,3 +1,4 @@ use super::*; -mod basic_test; +mod as_bytes_test; +mod into_bytes_test; diff --git a/module/core/async_from/License b/module/core/async_from/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/async_from/License +++ b/module/core/async_from/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/async_from/src/lib.rs b/module/core/async_from/src/lib.rs index 669dcf988e..1424b6e497 100644 --- a/module/core/async_from/src/lib.rs +++ b/module/core/async_from/src/lib.rs @@ -11,6 +11,15 @@ pub mod dependency pub use ::async_trait; } +// xxx : qqq : consider +// pub trait AsyncTryFrom: Sized { +// /// The type returned in the event of a conversion error. +// type Error; +// +// /// Performs the conversion. +// fn try_from(value: T) -> impl std::future::Future> + Send; +// } + /// Define a private namespace for all its items. #[ cfg( feature = "enabled" ) ] mod private diff --git a/module/core/async_from/tests/inc/basic_test.rs b/module/core/async_from/tests/inc/basic_test.rs index dce63a4c1e..18d6fa2d94 100644 --- a/module/core/async_from/tests/inc/basic_test.rs +++ b/module/core/async_from/tests/inc/basic_test.rs @@ -8,19 +8,19 @@ async fn async_try_from_test() struct MyNumber( u32 ); // xxx : qqq : broken - // #[ the_module::async_trait ] - // impl< 'a > the_module::AsyncTryFrom< &'a str > for MyNumber - // { - // type Error = std::num::ParseIntError; - // - // async fn async_try_from( value : &'a str ) -> Result< Self, Self::Error > - // { - // // Simulate asynchronous work - // tokio::time::sleep( tokio::time::Duration::from_millis( 1 ) ).await; - // let num = value.parse::< u32 >()?; - // Ok( MyNumber( num ) ) - // } - // } +// #[ the_module::async_trait ] +// impl< 'a > the_module::AsyncTryFrom< &'a str > for MyNumber +// { +// type Error = std::num::ParseIntError; +// +// async fn async_try_from( value : &'a str ) -> Result< Self, Self::Error > +// { +// // Simulate asynchronous work +// tokio::time::sleep( tokio::time::Duration::from_millis( 1 ) ).await; +// let num = value.parse::< u32 >()?; +// Ok( MyNumber( num ) ) +// } +// } #[ the_module::async_trait ] impl the_module::AsyncTryFrom< String > for MyNumber diff --git a/module/core/async_tools/License b/module/core/async_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/async_tools/License +++ b/module/core/async_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/async_tools/tests/inc/basic_test.rs b/module/core/async_tools/tests/inc/basic_test.rs deleted file mode 100644 index c652899926..0000000000 --- a/module/core/async_tools/tests/inc/basic_test.rs +++ /dev/null @@ -1,84 +0,0 @@ -use super::*; - -#[ tokio::test ] -async fn async_try_from_test() -{ - - // Example implementation of AsyncTryFrom for a custom type - struct MyNumber( u32 ); - - // xxx : qqq : broken - // #[ the_module::async_trait ] - // impl< 'a > the_module::AsyncTryFrom< &'a str > for MyNumber - // { - // type Error = std::num::ParseIntError; - // - // async fn async_try_from( value : &'a str ) -> Result< Self, Self::Error > - // { - // // Simulate asynchronous work - // tokio::time::sleep( tokio::time::Duration::from_millis( 1 ) ).await; - // let num = value.parse::< u32 >()?; - // Ok( MyNumber( num ) ) - // } - // } - - #[ the_module::async_trait ] - impl the_module::AsyncTryFrom< String > for MyNumber - { - type Error = std::num::ParseIntError; - - async fn async_try_from( value : String ) -> Result< Self, Self::Error > - { - // Simulate asynchronous work - tokio::time::sleep( tokio::time::Duration::from_millis( 10 ) ).await; - let num = value.parse::< u32 >()?; - Ok( MyNumber( num ) ) - } - } - - use the_module::{ AsyncTryFrom, AsyncTryInto }; - - // Using AsyncTryFrom directly - match MyNumber::async_try_from( "42".to_string() ).await - { - Ok( my_num ) => println!( "Converted successfully: {}", my_num.0 ), - Err( e ) => println!( "Conversion failed: {:?}", e ), - } - - // Using AsyncTryInto, which is automatically implemented - let result : Result< MyNumber, _ > = "42".to_string().async_try_into().await; - match result - { - Ok( my_num ) => println!( "Converted successfully using AsyncTryInto: {}", my_num.0 ), - Err( e ) => println!( "Conversion failed using AsyncTryInto: {:?}", e ), - } -} - -#[ tokio::test ] -async fn async_from_test() -{ - // Example implementation of AsyncFrom for a custom type - struct MyNumber( u32 ); - - #[ the_module::async_trait ] - impl the_module::AsyncFrom< String > for MyNumber - { - async fn async_tools( value : String ) -> Self - { - // Simulate asynchronous work - tokio::time::sleep( tokio::time::Duration::from_millis( 10 ) ).await; - let num = value.parse::< u32 >().unwrap_or( 0 ); - MyNumber( num ) - } - } - - use the_module::{ AsyncFrom, AsyncInto }; - - // Using AsyncFrom directly - let my_num : MyNumber = MyNumber::async_tools( "42".to_string() ).await; - println!( "Converted successfully using AsyncFrom: {}", my_num.0 ); - - // Using AsyncInto, which is automatically implemented - let my_num : MyNumber = "42".to_string().async_into().await; - println!( "Converted successfully using AsyncInto: {}", my_num.0 ); -} diff --git a/module/core/async_tools/tests/inc/mod.rs b/module/core/async_tools/tests/inc/mod.rs deleted file mode 100644 index 329271ad56..0000000000 --- a/module/core/async_tools/tests/inc/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -use super::*; - -mod basic_test; diff --git a/module/core/async_tools/tests/tests.rs b/module/core/async_tools/tests/tests.rs index 42f32553db..415170a560 100644 --- a/module/core/async_tools/tests/tests.rs +++ b/module/core/async_tools/tests/tests.rs @@ -1,9 +1,9 @@ +//! All tests #![ allow( unused_imports ) ] include!( "../../../../module/step/meta/src/module/terminal.rs" ); use async_tools as the_module; -// use test_tools::exposed::*; #[ cfg( feature = "enabled" ) ] #[ path = "../../../../module/core/async_from/tests/inc/mod.rs" ] diff --git a/module/core/clone_dyn/Cargo.toml b/module/core/clone_dyn/Cargo.toml index 2be1b57e42..49c4362a39 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.30.0" +version = "0.31.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/clone_dyn/License b/module/core/clone_dyn/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/clone_dyn/License +++ b/module/core/clone_dyn/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/clone_dyn_meta/Cargo.toml b/module/core/clone_dyn_meta/Cargo.toml index 09eb717e23..a6e215f343 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.28.0" +version = "0.29.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/clone_dyn_meta/License b/module/core/clone_dyn_meta/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/clone_dyn_meta/License +++ b/module/core/clone_dyn_meta/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/clone_dyn_types/Cargo.toml b/module/core/clone_dyn_types/Cargo.toml index 4debc74c8c..9992acae46 100644 --- a/module/core/clone_dyn_types/Cargo.toml +++ b/module/core/clone_dyn_types/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clone_dyn_types" -version = "0.28.0" +version = "0.30.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/clone_dyn_types/License b/module/core/clone_dyn_types/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/clone_dyn_types/License +++ b/module/core/clone_dyn_types/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/clone_dyn_types/src/lib.rs b/module/core/clone_dyn_types/src/lib.rs index a89d569ef0..8b057484be 100644 --- a/module/core/clone_dyn_types/src/lib.rs +++ b/module/core/clone_dyn_types/src/lib.rs @@ -190,7 +190,9 @@ mod private unsafe { let mut ptr = ref_dyn as *const T; + #[ allow( clippy::borrow_as_ptr ) ] let data_ptr = &mut ptr as *mut *const T as *mut *mut (); // don't change it + // qqq : xxx : after atabilization try `&raw mut ptr` instead // let data_ptr = &raw mut ptr as *mut *mut (); // fix clippy *data_ptr = < T as CloneDyn >::__clone_dyn( ref_dyn, DontCallMe ); Box::from_raw( ptr as *mut T ) diff --git a/module/core/collection_tools/Cargo.toml b/module/core/collection_tools/Cargo.toml index 816578bb00..2babd9d243 100644 --- a/module/core/collection_tools/Cargo.toml +++ b/module/core/collection_tools/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "collection_tools" -version = "0.17.0" +version = "0.18.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/collection_tools/License b/module/core/collection_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/collection_tools/License +++ b/module/core/collection_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/data_type/License b/module/core/data_type/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/data_type/License +++ b/module/core/data_type/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/derive_tools/Cargo.toml b/module/core/derive_tools/Cargo.toml index ed323a7b1e..1025bffeb1 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.33.0" +version = "0.34.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/derive_tools/License b/module/core/derive_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/derive_tools/License +++ b/module/core/derive_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/derive_tools/tests/inc/phantom/only_test/contravariant_type.rs b/module/core/derive_tools/tests/inc/phantom/only_test/contravariant_type.rs index f30c2a57e8..cd426be91a 100644 --- a/module/core/derive_tools/tests/inc/phantom/only_test/contravariant_type.rs +++ b/module/core/derive_tools/tests/inc/phantom/only_test/contravariant_type.rs @@ -3,7 +3,7 @@ fn assert_contravariant( x: ContravariantType< &dyn Fn( &'static str ) -> String ( x.a )( "test" ) } -#[test] +#[ test ] fn contravariant() { let x_fn: &dyn for< 'a > Fn( &'a str ) -> String = &| s: &str | diff --git a/module/core/derive_tools_meta/Cargo.toml b/module/core/derive_tools_meta/Cargo.toml index 5e3804d996..82a2749d44 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.32.0" +version = "0.33.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/derive_tools_meta/License b/module/core/derive_tools_meta/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/derive_tools_meta/License +++ b/module/core/derive_tools_meta/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/diagnostics_tools/License b/module/core/diagnostics_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/diagnostics_tools/License +++ b/module/core/diagnostics_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/error_tools/License b/module/core/error_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/error_tools/License +++ b/module/core/error_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/for_each/License b/module/core/for_each/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/for_each/License +++ b/module/core/for_each/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/format_tools/Cargo.toml b/module/core/format_tools/Cargo.toml index 2f29469253..91a431d192 100644 --- a/module/core/format_tools/Cargo.toml +++ b/module/core/format_tools/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "format_tools" -version = "0.3.0" +version = "0.4.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/format_tools/License b/module/core/format_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/format_tools/License +++ b/module/core/format_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/former/Cargo.toml b/module/core/former/Cargo.toml index 7c5efd5073..b55df3f63d 100644 --- a/module/core/former/Cargo.toml +++ b/module/core/former/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "former" -version = "2.12.0" +version = "2.15.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -12,7 +12,7 @@ documentation = "https://docs.rs/former" repository = "https://github.com/Wandalen/wTools/tree/master/module/core/former" homepage = "https://github.com/Wandalen/wTools/tree/master/module/core/former" description = """ -A flexible implementation of the Builder pattern supporting nested builders and collection-specific subformers. +A flexible implementation of the Builder pattern supporting nested builders and collection-specific subformers. Simplify the construction of complex objects. """ categories = [ "algorithms", "development-tools" ] keywords = [ "fundamental", "general-purpose", "builder-pattern" ] diff --git a/module/core/former/License b/module/core/former/License index c32986cee3..a23529f45b 100644 --- a/module/core/former/License +++ b/module/core/former/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/former/Readme.md b/module/core/former/Readme.md index 6a71d6bb3e..4aab976163 100644 --- a/module/core/former/Readme.md +++ b/module/core/former/Readme.md @@ -7,1371 +7,215 @@ A flexible implementation of the Builder pattern supporting nested builders and collection-specific subformers. -The Builder pattern allows you to construct objects step by step, using only the steps you need. Any fields not explicitly set will receive default values. By implementing this pattern, you can avoid passing numerous parameters into your constructors. +## What is `Former`? -This crate offers specialized subformers for common Rust collections, enabling the construction of complex data structures in a fluent and intuitive manner. Additionally, it provides the ability to define and reuse formers as subformers within other formers. +The `former` crate provides a powerful derive macro, `#[ derive( Former ) ]`, that automatically implements the **Builder pattern** for your Rust structs and enums. -## How Former Works +Its primary goal is to **simplify the construction of complex objects**, especially those with numerous fields, optional values, default settings, collections, or nested structures, making your initialization code more readable and maintainable. -- **Derivation**: By deriving `Former` on a struct, you automatically generate builder methods for each field. -- **Fluent Interface**: Each field's builder method allows for setting the value of that field and returns a mutable reference to the builder, enabling method chaining. -- **Optional Fields**: Optional fields can be easily handled without needing to explicitly set them to `None`. -- **Finalization**: The `.form()` method finalizes the building process and returns the constructed struct instance. -- **Subforming**: If a field has its own former defined or is a container of items for which a former is defined, it can be used as a subformer. +## Why Use `Former`? -This approach abstracts away the need for manually implementing a builder for each struct, making the code more readable and maintainable. +Compared to manually implementing the Builder pattern or using other builder crates, `former` offers several advantages: -## Comparison +* **Reduced Boilerplate:** `#[ derive( Former ) ]` automatically generates the builder struct, storage, and setters, saving you significant repetitive coding effort. +* **Fluent & Readable API:** Construct objects step-by-step using clear, chainable methods (`.field_name( value )`). +* **Effortless Defaults & Optionals:** Fields automatically use their `Default` implementation if not set. `Option< T >` fields are handled seamlessly – you only set them if you have a `Some( value )`. Custom defaults can be specified easily with `#[ former( default = ... ) ]`. +* **Powerful Collection & Nested Struct Handling:** `former` truly shines with its **subformer** system. Easily build `Vec`, `HashMap`, `HashSet`, and other collections element-by-element, or configure nested structs using their own dedicated formers within the parent's builder chain. This is often more complex to achieve with other solutions. -The Former crate and the abstract Builder pattern concept share a common goal: to construct complex objects step-by-step, ensuring they are always in a valid state and hiding internal structures. Both use a fluent interface for setting fields and support default values for fields that aren't explicitly set. They also have a finalization method to return the constructed object (`.form()` in Former, `build()` in [traditional Builder](https://refactoring.guru/design-patterns/builder)). +## Installation -However, the Former crate extends the traditional Builder pattern by automating the generation of builder methods using macros. This eliminates the need for manual implementation, which is often required in the abstract concept. Additionally, Former supports nested builders and subformers for complex data structures, allowing for more sophisticated object construction. +Add `former` to your `Cargo.toml`: -Advanced features such as custom setters, subformer reuse, storage-specific fields, mutators, and context management further differentiate Former from the [traditional approach](https://refactoring.guru/design-patterns/builder), which generally focuses on simpler use-cases without these capabilities. Moreover, while the traditional Builder pattern often includes a director class to manage the construction process, Former is not responsible for that aspect. - -## Example : Trivial +```sh +cargo add former +``` - - +The default features enable the `Former` derive macro and support for standard collections, covering most common use cases. - - - +## Basic Usage -The provided code snippet illustrates a basic use-case of the Former, which is used to apply the builder pattern for to construct complex objects step-by-step, ensuring they are always in a valid state and hiding internal structures. +Derive `Former` on your struct and use the generated `::former()` method to start building: ```rust # #[ 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 ) ] - // #[ debug ] pub struct UserProfile { - age : i32, - username : String, - bio_optional : Option< String >, // Fields could be optional + age : i32, // Required field + username : String, // Required field + bio : Option< String >, // Optional field } let profile = UserProfile::former() .age( 30 ) .username( "JohnDoe".to_string() ) - .bio_optional( "Software Developer".to_string() ) // Optionally provide a bio + // .bio is optional, so we don't *have* to call its setter .form(); - dbg!( &profile ); - // Expected output: - // &profile = UserProfile { - // age: 30, - // username: "JohnDoe", - // 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() {} - -# #[ cfg( all( feature = "derive_former", feature = "enabled" ) ) ] -# fn main() -# { - - // Use attribute debug to print expanded code. - #[ derive( Debug, PartialEq ) ] - pub struct UserProfile - { - age : i32, - username : String, - bio_optional : Option< String >, // Fields could be optional - } - - impl UserProfile - where - { - #[ inline( always ) ] - pub fn former() -> UserProfileFormer< - UserProfileFormerDefinition< (), UserProfile, former::ReturnPreformed > - > - { - UserProfileFormer::< UserProfileFormerDefinition< (), UserProfile, former::ReturnPreformed > >:: - new_coercing(former::ReturnPreformed) - } - } - - // = 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 + let expected = UserProfile { - 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, > >, - { - 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 - { - 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 - { - fn preform(mut self) -> Self::Preformed - { - let age = if self.age.is_some() - { - self.age.take().unwrap() - } - else - { - { - 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() - } - }; - let username = if self.username.is_some() - { - self.username.take().unwrap() - } - else - { - { - 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, - { - fn maybe_default(self : &Self) -> T - { - T::default() - } - } - (&::core::marker::PhantomData::< String >).maybe_default() - } - }; - 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; - } - } - - 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 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 : ::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 - } - } - - 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 + age : 30, + username : "JohnDoe".to_string(), + bio : None, // Defaults to None if not set + }; + assert_eq!( profile, expected ); + dbg!( &profile ); + // > &profile = UserProfile { + // > age: 30, + // > username: "JohnDoe", + // > bio: None, + // > } - let profile = UserProfile::former() + // Example setting the optional field: + let profile_with_bio = UserProfile::former() .age( 30 ) .username( "JohnDoe".to_string() ) - .bio_optional( "Software Developer".to_string() ) // Optionally provide a bio + .bio( "Software Developer".to_string() ) // Set the optional bio .form(); - dbg!( &profile ); - - // Expected output: - // - // &profile = UserProfile { - // age: 30, - // username: "JohnDoe", - // bio_optional: Some("Software Developer"), - // } - -# } -``` - -
- -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; - -/// Structure with a custom setter. -#[ derive( Debug, Former ) ] -pub struct StructWithCustomSetters -{ - word : String, -} - -impl StructWithCustomSettersFormer -{ - - // Custom alternative setter for `word` - pub fn word_exclaimed( mut self, value : impl Into< String > ) -> Self + let expected_with_bio = UserProfile { - debug_assert!( self.storage.word.is_none() ); - self.storage.word = Some( format!( "{}!", value.into() ) ); - self - } - -} - -let example = StructWithCustomSetters::former() -.word( "Hello" ) -.form(); -assert_eq!( example.word, "Hello".to_string() ); - -let example = StructWithCustomSetters::former() -.word_exclaimed( "Hello" ) -.form(); -assert_eq!( example.word, "Hello!".to_string() ); - + age : 30, + username : "JohnDoe".to_string(), + bio : Some( "Software Developer".to_string() ), + }; + assert_eq!( profile_with_bio, expected_with_bio ); + dbg!( &profile_with_bio ); + // > &profile_with_bio = UserProfile { + // > age: 30, + // > username: "JohnDoe", + // > bio: Some( "Software Developer" ), + // > } # } ``` -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. - -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; - - /// Structure with a custom setter. - #[ derive( Debug, Former ) ] - pub struct StructWithCustomSetters - { - // Use `debug` to gennerate sketch of setter. - #[ scalar( setter = false ) ] - word : String, - } +[Run this example locally](https://github.com/Wandalen/wTools/blob/master/module/core/former/examples/former_trivial.rs) | [Try it online](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fformer%2Fexamples%2Fformer_trivial.rs,RUN_POSTFIX=--example%20module%2Fcore%2Fformer%2Fexamples%2Fformer_trivial.rs/https://github.com/Wandalen/wTools) - impl< Definition > StructWithCustomSettersFormer< Definition > - where - Definition : former::FormerDefinition< Storage = StructWithCustomSettersFormerStorage >, - { - // Custom alternative setter for `word` - #[ inline ] - pub fn word< Src >( mut self, src : Src ) -> Self - where - Src : ::core::convert::Into< String >, - { - debug_assert!( self.storage.word.is_none() ); - self.storage.word = Some( format!( "{}!", src.into() ) ); - self - } - } +## Handling Optionals and Defaults - let example = StructWithCustomSetters::former() - .word( "Hello" ) - .form(); - assert_eq!( example.word, "Hello!".to_string() ); - dbg!( example ); - //> StructWithCustomSetters { - //> word: "Hello!", - //> } +`Former` makes working with optional fields and default values straightforward: -# } -``` +* **`Option< T >` Fields:** As seen in the basic example, fields of type `Option< T >` automatically default to `None`. You only need to call the setter if you have a `Some( value )`. -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. - -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 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. +* **Custom Defaults:** For required fields that don't implement `Default`, or when you need a specific default value other than the type's default, use the `#[ former( default = ... ) ]` attribute: ```rust # #[ cfg( any( not( feature = "derive_former" ), not( feature = "enabled" ) ) ) ] # fn main() {} - # #[ cfg( all( feature = "derive_former", feature = "enabled" ) ) ] # fn main() # { - -use former::Former; - -/// Structure with default attributes. -#[ derive( Debug, PartialEq, Former ) ] -pub struct ExampleStruct -{ - #[ former( default = 5 ) ] - number : i32, - #[ former( default = "Hello, Former!".to_string() ) ] - greeting : String, - #[ former( default = vec![ 10, 20, 30 ] ) ] - numbers : Vec< i32 >, -} - -let instance = ExampleStruct::former().form(); -let expected = ExampleStruct -{ - number : 5, - greeting : "Hello, Former!".to_string(), - numbers : vec![ 10, 20, 30 ], -}; -assert_eq!( instance, expected ); -dbg!( &instance ); -// > &instance = ExampleStruct { -// > number: 5, -// > greeting: "Hello, Former!", -// > numbers: [ -// > 10, -// > 20, -// > 30, -// > ], -// > } - -# } -``` - -The above code snippet 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`. - -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. - -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 collection; 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 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 - -Understanding the distinctions among the types of setters or subformers is essential for effectively employing the builder pattern in object construction. Each type of setter is designed to meet specific needs in building complex, structured data entities: - -- **Scalar Setter**: Handles the direct assignment of scalar values or simple fields within an entity. These setters manage basic data types or individual fields and do not involve nested formers or complex structuring. - -- **Subform Collection Setter**: Facilitates the management of a collection as a whole by returning a former that provides an interface to configure the entire collection. This setter is beneficial for applying uniform configurations or validations to all elements in a collection, such as a `HashMap` of children. - -- **Subform Entry Setter**: This setter allows for the individual formation of elements within a collection. It returns a former for each element, enabling detailed configuration and addition of complex elements within collections, exemplified by managing `Child` entities within a `Parent`'s `HashMap`. - -- **Subform Scalar Setter**: Similar to the subform entry setter but designed for scalar fields that have a former implementation. This setter does not collect instances into a collection because there is no collection involved, only a scalar field. It is used when the scalar field itself needs to be configured or modified through its dedicated former. - -These setters ensure that developers can precisely and efficiently set properties, manage collections, and configure complex structures within their applications. - -## Example : Collection Setter for a Vector - -This example demonstrates how to employ the `Former` to configure a `Vec` using a collection setter in a structured manner. - -```rust -# #[ cfg( not( all( feature = "enabled", feature = "derive_former", any( feature = "use_alloc", not( feature = "no_std" ) ) ) ) ) ] -# fn main() {} - -# #[ cfg( all( feature = "enabled", feature = "derive_former", any( feature = "use_alloc", not( feature = "no_std" ) ) ) ) ] -# fn main() -# { - - #[ derive( Debug, PartialEq, former::Former ) ] - pub struct StructWithVec - { - #[ subform_collection ] - 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 ); - -# } -``` - -Try out `cargo run --example former_collection_vector`. -
-[See code](./examples/former_collection_vector.rs). - -## Example : Collection Setter for a Hashmap - -This example demonstrates how to effectively employ the `Former` to configure a `HashMap` using a collection setter. - -```rust -# #[ cfg( not( all( feature = "enabled", feature = "derive_former", any( feature = "use_alloc", not( feature = "no_std" ) ) ) ) ) ] -# fn main() {} - -# #[ cfg( all( feature = "enabled", feature = "derive_former", any( feature = "use_alloc", not( feature = "no_std" ) ) ) ) ] -# fn main() -# { - use collection_tools::{ HashMap, hmap }; - - #[ derive( Debug, PartialEq, former::Former ) ] - pub struct StructWithMap - { - #[ subform_collection ] - 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_collection_hashmap`. -
-[See code](./examples/former_collection_hashmap.rs). - -## Example : Collection Setter for a Hashset - -This example demonstrates the use of the `Former` to build a `collection_tools::HashSet` through subforming. - -```rust -# #[ cfg( not( all( feature = "enabled", feature = "derive_former", any( feature = "use_alloc", not( feature = "no_std" ) ) ) ) ) ] -# fn main() {} - -# #[ cfg( all( feature = "enabled", feature = "derive_former", any( feature = "use_alloc", not( feature = "no_std" ) ) ) ) ] -# fn main() -{ - use collection_tools::{ HashSet, hset }; - - #[ derive( Debug, PartialEq, former::Former ) ] - pub struct StructWithSet - { - #[ subform_collection ] - 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 ); - -# } -``` - -Try out `cargo run --example former_collection_hashset`. -
-[See code](./examples/former_collection_hashset.rs). - -## Example : Custom Scalar Setter - -This example demonstrates the implementation of a scalar setter using the `Former`. Unlike the more complex subform and collection 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 collection—each child entity in this case. - -```rust -# #[ cfg( not( all( feature = "enabled", feature = "derive_former", any( feature = "use_alloc", not( feature = "no_std" ) ) ) ) ) ] -# fn main() {} - -# #[ cfg( all( feature = "enabled", feature = "derive_former", any( feature = "use_alloc", 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 `debug` to gennerate sketch of setter. - #[ scalar( setter = false ) ] - children : HashMap< String, Child >, - } - - impl< Definition > ParentFormer< Definition > - where - Definition : former::FormerDefinition< Storage = ParentFormerStorage >, + pub struct Config { - #[ 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 - } + #[ former( default = 1024 ) ] // Use 1024 if .buffer_size() is not called + buffer_size : usize, + timeout : Option< u32 >, // Defaults to None + #[ former( default = true ) ] // Default for bool + enabled : bool, } - 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 ) + // Only set the optional timeout + let config1 = Config::former() + .timeout( 5000 ) .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 collection 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 Subform Scalar Setter - -Implementation of a custom subform scalar setter using the `Former`. - -This example focuses on the usage of a subform scalar setter to manage complex scalar types within a parent structure. -Unlike more general subform setters that handle collections, this setter specifically configures scalar fields that have -their own formers, allowing for detailed configuration within a nested builder pattern. - -```rust - -# #[ cfg( not( all( feature = "enabled", feature = "derive_former" ) ) ) ] -# fn main() -# {} -# -# // Ensures the example only compiles when the appropriate features are enabled. -# #[ cfg( all( feature = "enabled", feature = "derive_former" ) ) ] -# fn main() -# { + assert_eq!( config1.buffer_size, 1024 ); // Got default + assert_eq!( config1.timeout, Some( 5000 ) ); + assert_eq!( config1.enabled, true ); // Got default - use former::Former; - - // Child struct with Former derived for builder pattern support - #[ derive( Debug, PartialEq, Former ) ] - // Optional: Use `#[debug]` to expand and debug generated code. - // #[debug] - pub struct Child - { - name : String, - description : String, - } - - // Parent struct designed to hold a single Child instance using subform scalar - #[ derive( Debug, PartialEq, Former ) ] - // Optional: Use `#[debug]` to expand and debug generated code. - // #[debug] - pub struct Parent - { - // The `subform_scalar` attribute is used to specify that the 'child' field has its own former - // and can be individually configured via a subform setter. This is not a collection but a single scalar entity. - #[ subform_scalar( setter = false ) ] - child : Child, - } - - /// Extends `ParentFormer` to include a method that initializes and configures a subformer for the 'child' field. - /// This function demonstrates the dynamic addition of a named child, leveraging a subformer to specify detailed properties. - 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_subform_scalar::< ChildFormer< _ >, _, >().name( name ) - } - } - - // Creating an instance of `Parent` using the builder pattern to configure `Child` - let ca = Parent::former() - .child( "echo" ) // starts the configuration of the `child` subformer - .description( "prints all subjects and properties" ) // sets additional properties for the `Child` - .end() // finalize the child configuration - .form(); // finalize the Parent configuration - - dbg!( &ca ); // Outputs the structured data for review - // Expected output: - //> Parent { - //> child: Child { - //> name: "echo", - //> description: "prints all subjects and properties", - //> }, - //> } - -# } -``` - -## Example : Custom Subform Collection Setter - -This example demonstrates the use of collection setters to manage complex nested data structures with the `Former`, focusing on a parent-child relationship structured around a collection `HashMap`. Unlike typical builder patterns that add individual elements using subform setters, this example uses a collection 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 collection—each child entity in this case. - -```rust -# #[ cfg( not( all( feature = "enabled", feature = "derive_former", any( feature = "use_alloc", not( feature = "no_std" ) ) ) ) ) ] -# fn main() {} - -// Ensure the example only compiles when the appropriate features are enabled. -# #[ cfg( all( feature = "enabled", feature = "derive_former", any( feature = "use_alloc", 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 `debug` to gennerate sketch of setter. - #[ scalar( setter = 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 ) + // Set everything, overriding defaults + let config2 = Config::former() + .buffer_size( 4096 ) + .timeout( 1000 ) + .enabled( false ) .form(); - dbg!( &ca ); - // > &ca = Parent { - // > child: { - // > "echo": Child { - // > name: "echo", - // > description: "prints all subjects and properties", - // > }, - // > "exit": Child { - // > name: "exit", - // > description: "just exit", - // > }, - // > }, - // > } - + assert_eq!( config2.buffer_size, 4096 ); + assert_eq!( config2.timeout, Some( 1000 ) ); + assert_eq!( config2.enabled, false ); # } ``` +[See full example code](https://github.com/Wandalen/wTools/blob/master/module/core/former/examples/former_custom_defaults.rs) -Try out `cargo run --example former_custom_subform_collection`. -
-[See code](./examples/former_custom_subform_collection.rs). - -## Example : Custom Subform Entry Setter +## Building Collections & Nested Structs (Subformers) -This example illustrates the implementation of nested builder patterns using the `Former`, 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`. +Where `former` significantly simplifies complex scenarios is in building collections (`Vec`, `HashMap`, etc.) or nested structs. It achieves this through **subformers**. Instead of setting the entire collection/struct at once, you get a dedicated builder for the field: -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 collection—each child entity in this case. +**Example: Building a `Vec`** ```rust # #[ cfg( not( all( feature = "enabled", feature = "derive_former", any( feature = "use_alloc", not( feature = "no_std" ) ) ) ) ) ] # fn main() {} - -# // Ensure the example only compiles when the appropriate features are enabled. # #[ cfg( all( feature = "enabled", feature = "derive_former", any( feature = "use_alloc", 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 `debug` to gennerate sketch of setter. - #[ subform_entry( setter = 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_subform_entry::< 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", - // > }, - // > }, - // > } -# } -``` - -Try out `cargo run --example former_custom_subform_entry`. -
-[See code](./examples/former_custom_subform_entry.rs). - -## General Collection Interface - -There are suite of traits designed to abstract and enhance the functionality of collection data structures within the forming process. These traits are integral to managing the complexity of collection operations, such as adding, modifying, and converting between different representations within collections like vectors, hash maps, etc. They are especially useful when used in conjunction with the `collection` attribute in the `former` macro, which automates the implementation of these traits to create robust and flexible builder patterns for complex data structures. - -- [`Collection`] - Defines basic functionalities for collections, managing entries and values, establishing the fundamental operations required for any custom collection implementation in forming processes. -- [`EntryToVal`] - Facilitates the conversion of collection entries to their value representations, crucial for operations that treat collection 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 collection based on value data. -- [`CollectionAdd`] - Adds functionality for inserting entries into a collection, considering collection-specific rules such as duplication handling and order preservation, enhancing the usability of collections in forming scenarios. -- [`CollectionAssign`] - Extends the collection functionality to replace all existing entries with new ones, enabling bulk updates or complete resets of collection contents, which is particularly useful in dynamic data environments. - -## Custom Collection Former - -Collection interface is defined in the crate and implemented for collections like vectors, hash maps, etc, but if you want to use non-standard collection you can implement collection interface for the collection. This example demonstrate how to do that. - -Try out `cargo run --example former_custom_collection`. -
-[See code](./examples/former_custom_collection.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 ) ] - 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", + pub struct Report + { + title : String, + #[ subform_collection ] // Enables the `.entries()` subformer + entries : Vec< String >, + } + + let report = Report::former() + .title( "Log Report".to_string() ) + .entries() // Get the subformer for the Vec + .add( "Entry 1".to_string() ) // Use subformer methods to modify the Vec + .add( "Entry 2".to_string() ) + .end() // Return control to the parent former (ReportFormer) + .form(); // Finalize the Report + + assert_eq!( report.title, "Log Report" ); + assert_eq!( report.entries, vec![ "Entry 1".to_string(), "Entry 2".to_string() ] ); + dbg!( &report ); + // > &report = Report { + // > title: "Log Report", + // > entries: [ + // > "Entry 1", + // > "Entry 2", + // > ], // > } - # } ``` - -Try out `cargo run --example former_custom_mutator`. -
-[See code](./examples/former_custom_mutator.rs). - -## 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 System - -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: - -- `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. - -These traits collectively facilitate a robust and flexible builder pattern that supports complex object creation and configuration scenarios. - -## Example : Custom Definition - -Define a custom former definition and custom forming logic, and apply them to a collection. - -The example showcases how to accumulate elements into a collection 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( not( all( feature = "enabled", feature = "derive_former", any( feature = "use_alloc", not( feature = "no_std" ) ) ) ) ) ] -# fn main() {} - -# #[ cfg( all( feature = "enabled", feature = "derive_former", any( feature = "use_alloc", not( feature = "no_std" ) ) ) ) ] -# 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; // Collection 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::CollectionFormer::::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 - -# } -``` - -## Index of Examples - - - - - - - -- [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 collection. - - - -## To add to your project - -```sh -cargo add former -``` - -## Try out from the repository - -```sh -git clone https://github.com/Wandalen/wTools -cd wTools -cd examples/former_trivial -cargo run -``` +[See Vec example](https://github.com/Wandalen/wTools/blob/master/module/core/former/examples/former_collection_vector.rs) | [See HashMap example](https://github.com/Wandalen/wTools/blob/master/module/core/former/examples/former_collection_hashmap.rs) + +`former` provides different subform attributes (`#[ subform_collection ]`, `#[ subform_entry ]`, `#[ subform_scalar ]`) for various collection and nesting patterns. + +## Key Features Overview + +* **Automatic Builder Generation:** `#[ derive( Former ) ]` for structs and enums. +* **Fluent API:** Chainable setter methods for a clean construction flow. +* **Defaults & Optionals:** Seamless handling of `Default` values and `Option< T >` fields. Custom defaults via `#[ former( default = ... ) ]`. +* **Subformers:** Powerful mechanism for building nested structures and collections: + * `#[ subform_scalar ]`: For fields whose type also derives `Former`. + * `#[ subform_collection ]`: For collections like `Vec`, `HashMap`, `HashSet`, etc., providing methods like `.add()` or `.insert()`. + * `#[ subform_entry ]`: For collections where each entry is built individually using its own former. +* **Customization:** + * Rename setters: `#[ scalar( name = ... ) ]`, `#[ subform_... ( name = ... ) ]`. + * Disable default setters: `#[ scalar( setter = false ) ]`, `#[ subform_... ( setter = false ) ]`. + * Define custom setters directly in `impl Former`. + * Specify collection definitions: `#[ subform_collection( definition = ... ) ]`. +* **Advanced Control:** + * Storage-only fields: `#[ storage_fields( ... ) ]`. + * Custom mutation logic: `#[ mutator( custom ) ]` + `impl FormerMutator`. + * Custom end-of-forming logic: Implement `FormingEnd`. + * Custom collection support: Implement `Collection` traits. +* **Component Model:** Separate derives (`Assign`, `ComponentFrom`, `ComponentsAssign`, `FromComponents`) for type-based field access and conversion (See `former_types` documentation). + +## Where to Go Next + +* **[Advanced Usage & Concepts](https://github.com/Wandalen/wTools/tree/master/module/core/former/advanced.md):** Dive deeper into subformers, customization options, storage, context, definitions, mutators, and custom collections. +* **[Examples Directory](https://github.com/Wandalen/wTools/tree/master/module/core/former/examples):** Explore practical, runnable examples showcasing various features. +* **[API Documentation (docs.rs)](https://docs.rs/former):** Get detailed information on all public types, traits, and functions. +* **[Repository (GitHub)](https://github.com/Wandalen/wTools/tree/master/module/core/former):** View the source code, contribute, or report issues. diff --git a/module/core/former/advanced.md b/module/core/former/advanced.md new file mode 100644 index 0000000000..a96b79270a --- /dev/null +++ b/module/core/former/advanced.md @@ -0,0 +1,893 @@ +# Former Crate - Advanced Usage and Concepts + +This document provides detailed explanations of the advanced features, customization options, and underlying concepts of the `former` crate. It assumes you have a basic understanding of how to use `#[ derive( Former ) ]` as covered in the main [Readme.md](./Readme.md). + +## Struct/Enum Level Attributes + +Applied directly above the `struct` or `enum` definition. + +* **`#[ storage_fields( field_name : FieldType, ... ) ]`** + * Defines extra fields exclusive to the temporary `...FormerStorage` struct. +* **`#[ mutator( custom ) ]`** + * Disables automatic generation of the default `impl former::FormerMutator`, requiring a manual implementation. +* **`#[ perform( fn method_name<...> () -> OutputType ) ]`** + * Specifies a method on the original struct to be called by the former's `.perform()` method after forming the struct instance. + +## Field Level Attributes + +Applied directly above fields within a struct. + +**General Field Control:** + +* **`#[ former( default = value ) ]`** + * Provides a default `value` for the field if its setter is not called. + +**Scalar Field Control:** + +* **`#[ scalar ]`** (Often implicit for simple fields) + * Generates a standard setter method (`.field_name(value)`). + * **Arguments:** + * `name = new_name`: Renames the setter method (e.g., `#[ scalar( name = first_field ) ]`). + * `setter = bool`: Explicitly enables/disables setter generation (e.g., `#[ scalar( setter = false ) ]`). Default: `true`. + +**Subformer Field Control (for nested building):** + +* **`#[ subform_collection ]`** (For `Vec`, `HashMap`, `HashSet`, etc.) + * Generates a method returning a collection-specific subformer (e.g., `.field_name().add(item).end()`). + * **Arguments:** + * `definition = path::to::CollectionDefinition`: Specifies the collection type (e.g., `#[ subform_collection( definition = former::VectorDefinition ) ]`). Often inferred. + * `name = new_name`: Renames the subformer starter method (e.g., `#[ subform_collection( name = children2 ) ]`). + * `setter = bool`: Enables/disables the subformer starter method (e.g., `#[ subform_collection( setter = false ) ]`). Default: `true`. +* **`#[ subform_entry ]`** (For collections where entries are built individually) + * Generates a method returning a subformer for a *single entry* of the collection (e.g., `.field_name().entry_field(val).end()`). + * **Arguments:** + * `name = new_name`: Renames the entry subformer starter method (e.g., `#[ subform_entry( name = _child ) ]`). + * `setter = bool`: Enables/disables the entry subformer starter method (e.g., `#[ subform_entry( setter = false ) ]`). Default: `true`. +* **`#[ subform_scalar ]`** (For fields whose type also derives `Former`) + * Generates a method returning a subformer for the nested struct (e.g., `.field_name().inner_field(val).end()`). + * **Arguments:** + * `name = new_name`: Renames the subformer starter method (e.g., `#[ subform_scalar( name = child2 ) ]`). + * `setter = bool`: (Likely) Enables/disables the subformer starter method. Default: `true`. + +## Core Concepts Deep Dive + +Understanding the components generated by `#[ derive( Former ) ]` helps in customizing the builder pattern effectively. + +### Storage (`...FormerStorage`) + +When you derive `Former` for a struct like `MyStruct`, a corresponding storage struct, typically named `MyStructFormerStorage`, is generated internally. + +* **Purpose:** This storage struct acts as a temporary container holding the intermediate state of the object during its construction via the former. +* **Fields:** It contains fields corresponding to the original struct's fields, but wrapped in `Option`. For example, a field `my_field : i32` in `MyStruct` becomes `pub my_field : Option< i32 >` in `MyStructFormerStorage`. This allows the former to track which fields have been explicitly set. Optional fields in the original struct (e.g., `my_option : Option< String >`) remain `Option< String >` in the storage. +* **Storage-Only Fields:** If you use the `#[ storage_fields( ... ) ]` attribute on the struct, those additional fields are *only* present in the storage struct, not in the final formed struct. This is useful for temporary calculations or state needed during the building process. +* **Decoupling:** The storage struct decouples the configuration steps (calling setters) from the final object instantiation (`.form()` or `.end()`). You can call setters in any order. +* **Finalization:** When `.form()` or `.end()` is called, the `StoragePreform::preform` method is invoked on the storage struct. This method consumes the storage, unwraps the `Option`s for required fields (panicking or using defaults if not set), handles optional fields appropriately, and constructs the final struct instance (`MyStruct`). + +The `...Former` struct itself holds an instance of this `...FormerStorage` struct internally to manage the building process. + +### Definitions (`...Definition`, `...DefinitionTypes`) + +Alongside the `Former` and `Storage` structs, the derive macro also generates two definition structs: `...FormerDefinitionTypes` and `...FormerDefinition`. + +* **`...FormerDefinitionTypes`:** + * **Purpose:** Defines the core *types* involved in the formation process for a specific entity. + * **Associated Types:** + * `Storage`: Specifies the storage struct used (e.g., `MyStructFormerStorage`). + * `Formed`: Specifies the type that is ultimately produced by the `.form()` or `.end()` methods. By default, this is the original struct (e.g., `MyStruct`), but it can be changed by custom `FormingEnd` implementations. + * `Context`: Specifies the type of contextual information passed down during subforming (if the former is used as a subformer). Defaults to `()`. + * **Traits:** Implements `former_types::FormerDefinitionTypes` and `former_types::FormerMutator`. + +* **`...FormerDefinition`:** + * **Purpose:** Extends `...FormerDefinitionTypes` by adding the *end condition* logic. It fully defines how a former behaves. + * **Associated Types:** Inherits `Storage`, `Formed`, `Context`, and `Types` (which points back to the `...FormerDefinitionTypes` struct) from the `former_types::FormerDefinition` trait. + * **`End` Associated Type:** Specifies the type that implements the `former_types::FormingEnd` trait, defining what happens when `.form()` or `.end()` is called. This defaults to `former_types::ReturnPreformed` (which calls `StoragePreform::preform` on the storage) but can be customized. + * **Traits:** Implements `former_types::FormerDefinition`. + +* **Role in Generics:** The `Definition` generic parameter on the `...Former` struct (e.g., `MyStructFormer< Definition = ... >`) allows customizing the entire forming behavior by providing a different `FormerDefinition` implementation. This enables advanced scenarios like changing the formed type or altering the end-of-forming logic. + +In most basic use cases, you don't interact with these definition structs directly, but they underpin the flexibility and customization capabilities of the `former` crate, especially when dealing with subformers and custom end logic. + +### Context + +The `Context` is an optional piece of data associated with a `Former`. It plays a crucial role primarily when a `Former` is used as a **subformer** (i.e., when building a nested struct or collection entries). + +* **Purpose:** To pass information or state *down* from a parent former to its child subformer during the building process. +* **Default:** For a top-level former (one created directly via `MyStruct::former()`), the context type defaults to `()` (the unit type), and the context value is `None`. +* **Subforming:** When a subformer is initiated (e.g., by calling `.my_subform_field()` on a parent former), the parent former typically passes *itself* as the context to the subformer. +* **`FormingEnd` Interaction:** The `FormingEnd::call` method receives the context (`Option< Context >`) as its second argument. When a subformer finishes (via `.end()`), its `FormingEnd` implementation usually receives the parent former (`Some( parent_former )`) as the context. This allows the `End` logic to: + 1. Retrieve the formed value from the subformer's storage. + 2. Modify the parent former's storage (e.g., insert the formed value into the parent's collection or field). + 3. Return the modified parent former to continue the building chain. +* **Customization:** While the default context is `()` or the parent former, you can define custom formers and `FormingEnd` implementations that use different context types to pass arbitrary data relevant to the specific building logic. + +In essence, the context provides the mechanism for subformers to communicate back and integrate their results into their parent former upon completion. + +### End Condition (`FormingEnd`, `ReturnStorage`, `ReturnPreformed`, Closures) + +The `End` condition determines what happens when the forming process is finalized by calling `.form()` or `.end()` on a `Former`. It's defined by the `End` associated type within the `FormerDefinition` and must implement the `former_types::FormingEnd` trait. + +* **`FormingEnd` Trait:** + * Defines a single method: `call( &self, storage : Definition::Storage, context : Option< Definition::Context > ) -> Definition::Formed`. + * This method consumes the `storage` and optional `context` and produces the final `Formed` type. + +* **Default End Conditions (Provided by `former_types`):** + * **`ReturnPreformed`:** This is the default `End` type for formers generated by `#[ derive( Former ) ]`. Its `call` implementation invokes `StoragePreform::preform` on the storage, effectively unwrapping `Option`s, applying defaults, and constructing the final struct instance. It ignores the context. The `Formed` type is the original struct type. + * **`ReturnStorage`:** A simpler `End` type often used for collection formers. Its `call` implementation simply returns the storage itself *without* calling `preform`. The `Formed` type is the same as the `Storage` type (e.g., `Vec< T >`, `HashMap< K, V >`). It also ignores the context. + * **`NoEnd`:** A placeholder that panics if `call` is invoked. Useful in generic contexts where an `End` type is required syntactically but never actually used. + +* **Subformer End Conditions (Generated by `#[ derive( Former ) ]`):** + * When you use subform attributes (`#[ subform_scalar ]`, `#[ subform_collection ]`, `#[ subform_entry ]`), the derive macro generates specialized internal `End` structs (e.g., `ParentFormerSubformScalarChildEnd`). + * The `call` implementation for these generated `End` structs typically: + 1. Takes the subformer's `storage` and the parent former as `context`. + 2. Calls `StoragePreform::preform` on the subformer's storage to get the formed value (e.g., the `Child` instance or the `Vec< Child >`). + 3. Assigns this formed value to the appropriate field in the parent former's storage (retrieved from the `context`). + 4. Returns the modified parent former (`Formed` type is the parent former). + +* **Custom End Conditions (Closures & Structs):** + * You can provide a custom closure or a struct implementing `FormingEnd` when manually constructing a former using methods like `Former::begin`, `Former::new`, or their `_coercing` variants. + * This allows you to define arbitrary logic for the finalization step, such as: + * Performing complex validation on the storage before forming. + * Transforming the storage into a different `Formed` type. + * Integrating the result into a custom context. + * `former_types::FormingEndClosure` is a helper to easily wrap a closure for use as an `End` type. + +The `End` condition provides the final hook for controlling the transformation from the intermediate storage state to the desired final output of the forming process. + +### Mutators (`FormerMutator`, `#[mutator(custom)]`) + +The `FormerMutator` trait provides an optional hook to modify the `Storage` and `Context` *just before* the `FormingEnd::call` method is invoked during the finalization step (`.form()` or `.end()`). + +* **Purpose:** To perform last-minute adjustments, calculations, or conditional logic based on the accumulated state in the storage *before* the final transformation into the `Formed` type occurs. This is particularly useful for: + * Setting derived fields based on other fields set during the building process. + * Applying complex validation logic that depends on multiple fields. + * Making use of `#[ storage_fields( ... ) ]` to compute final values for the actual struct fields. + +* **`FormerMutator` Trait:** + * Associated with the `...FormerDefinitionTypes` struct. + * Defines one method: `form_mutation( storage: &mut Self::Storage, context: &mut Option< Self::Context > )`. + * This method receives *mutable* references, allowing direct modification of the storage and context. + +* **Default Behavior:** By default, `#[ derive( Former ) ]` generates an empty `impl FormerMutator` for the `...FormerDefinitionTypes`. This means no mutation occurs unless customized. + +* **Customization (`#[ mutator( custom ) ]`):** + * Applying `#[ mutator( custom ) ]` to the struct tells the derive macro *not* to generate the default empty implementation. + * You must then provide your own `impl FormerMutator for YourStructFormerDefinitionTypes< ... > { ... }` block, implementing the `form_mutation` method with your custom logic. + +* **Execution Order:** `FormerMutator::form_mutation` runs *after* the user calls `.form()` or `.end()` but *before* `FormingEnd::call` is executed. + +* **vs. `FormingEnd`:** While `FormingEnd` defines the *final transformation* from storage to the formed type, `FormerMutator` allows *intermediate modification* of the storage/context just prior to that final step. It's useful when the logic depends on the builder's state but shouldn't be part of the final type conversion itself. + +[See Example: Mutator and Storage Fields](https://github.com/Wandalen/wTools/blob/master/module/core/former/examples/former_custom_mutator.rs) + +## Subformer Types In Detail + +Subformers are a key feature of the `former` crate, enabling the construction of nested data structures and collections in a fluent manner. Different attributes control how subformers are generated and behave. + +### `#[ subform_scalar ]` - Building Nested Structs + +Use the `#[ subform_scalar ]` attribute on a field whose type *also* derives `Former`. This generates a setter method that returns the dedicated `Former` for that field's type, allowing you to configure the nested struct within the parent's builder chain. + +* **Attribute:** `#[ subform_scalar ]` (applied to the field in the parent struct) +* **Requirement:** The field's type (e.g., `Child` in `parent_field: Child`) must derive `Former`. +* **Generated Setter:** By default, a method with the same name as the field (e.g., `.child()`) is generated on the parent's former (`ParentFormer`). This method returns the child's former (`ChildFormer`). +* **Usage:** + ```rust + parent_former + .child() // Returns ChildFormer< ParentFormer, ... > + .child_field1(...) + .child_field2(...) + .end() // Finalizes Child, returns control to ParentFormer + .form() // Finalizes Parent + ``` +* **`End` Condition:** The derive macro automatically generates a specialized `End` struct (e.g., `ParentFormerSubformScalarChildEnd`) for the subformer. When `.end()` is called on the subformer (`ChildFormer`), this `End` struct's `call` method takes the finalized `Child` storage, preforms it into a `Child` instance, assigns it to the `child` field in the parent's storage (passed via context), and returns the parent former. +* **Customization:** + * `#[ subform_scalar( name = new_setter_name ) ]`: Renames the generated setter method (e.g., `.child_alt()` instead of `.child()`). + * `#[ subform_scalar( setter = false ) ]`: Disables the generation of the user-facing setter method (`.child()`). However, it still generates the internal helper method (e.g., `._child_subform_scalar()`) and the `End` struct, allowing you to create custom setters with different arguments while reusing the core subforming logic. + +**Example:** + +```rust +# #[ cfg( not( all( feature = "enabled", feature = "derive_former", any( feature = "use_alloc",not( feature = "no_std" ) ) ) ) ) ] +# fn main() {} +# #[ cfg( all( feature = "enabled", feature = "derive_former", any( feature = "use_alloc",not( feature = "no_std" ) ) ) ) ] +# fn main() +# { + use former::Former; + + #[ derive( Debug, Default, PartialEq, Former ) ] + pub struct Address + { + street : String, + city : String, + } + + #[ derive( Debug, Default, PartialEq, Former ) ] + pub struct User + { + name : String, + #[ subform_scalar ] // Use subformer for the 'address' field + address : Address, + } + + let user = User::former() + .name( "Alice".to_string() ) + .address() // Returns AddressFormer< UserFormer, ... > + .street( "123 Main St".to_string() ) + .city( "Anytown".to_string() ) + .end() // Finalizes Address, returns UserFormer + .form(); // Finalizes User + + assert_eq!( user.name, "Alice" ); + assert_eq!( user.address.street, "123 Main St" ); + assert_eq!( user.address.city, "Anytown" ); +# } +``` +[See full example code](https://github.com/Wandalen/wTools/blob/master/module/core/former/examples/former_custom_subform_scalar.rs) + +### `#[ subform_collection ]` - Building Collections Fluently + +Use the `#[ subform_collection ]` attribute on fields that represent collections like `Vec< E >`, `HashMap< K, V >`, `HashSet< K >`, etc. This generates a setter method that returns a specialized **collection former** tailored to the specific collection type, allowing you to add multiple elements fluently. + +* **Attribute:** `#[ subform_collection ]` (applied to the collection field) +* **Requirement:** The field type must be a collection type for which `former` has built-in support (e.g., `Vec`, `HashMap`, `HashSet`, `BTreeMap`, `BTreeSet`, `LinkedList`, `BinaryHeap`) or a custom type that implements the necessary `former_types::Collection` traits. +* **Generated Setter:** By default, a method with the same name as the field (e.g., `.entries()`) is generated. This method returns a `former_types::CollectionFormer` instance specialized for the field's collection type (e.g., `VectorFormer`, `HashMapFormer`). +* **Usage:** + ```rust + parent_former + .entries() // Returns e.g., VectorFormer< String, ParentFormer, ... > + .add( "item1".to_string() ) // Use collection-specific methods + .add( "item2".to_string() ) + .end() // Finalizes the collection, returns control to ParentFormer + .form() // Finalizes Parent + ``` +* **Collection Methods:** The returned collection former provides methods like `.add( entry )` and `.replace( iterator )`. The exact type of `entry` depends on the collection (`E` for `Vec`/`HashSet`, `( K, V )` for `HashMap`). +* **`End` Condition:** Similar to `subform_scalar`, the derive macro generates a specialized `End` struct (e.g., `ParentSubformCollectionEntriesEnd`). Its `call` method takes the subformer's storage (the collection being built), assigns it to the corresponding field in the parent former's storage, and returns the parent former. +* **Customization:** + * `#[ subform_collection( name = new_setter_name ) ]`: Renames the generated setter method. + * `#[ subform_collection( setter = false ) ]`: Disables the user-facing setter, but still generates the internal helper (`._entries_subform_collection()`) and `End` struct for custom setter implementation. + * `#[ subform_collection( definition = MyCollectionDefinition ) ]`: Specifies a custom `FormerDefinition` to use for the collection, overriding the default behavior (useful for custom collection types or specialized logic). + +**Example (Vec):** + +```rust +# #[ cfg( not( all( feature = "enabled", feature = "derive_former", any( feature = "use_alloc", not( feature = "no_std" ) ) ) ) ) ] +# fn main() {} +# #[ cfg( all( feature = "enabled", feature = "derive_former", any( feature = "use_alloc", not( feature = "no_std" ) ) ) ) ] +# fn main() +# { + use former::Former; + use std::collections::VecDeque; // Example using VecDeque + + #[ derive( Debug, PartialEq, Former ) ] + pub struct DataPacket + { + id : u32, + #[ subform_collection ] // Uses default VectorDefinition for Vec + // #[ subform_collection( definition = former::VecDequeDefinition ) ] // Example for VecDeque + payload : Vec< u8 >, + // payload : VecDeque< u8 >, // Alternative + } + + let packet = DataPacket::former() + .id( 101 ) + .payload() // Returns VectorFormer< u8, ... > + .add( 0xDE ) + .add( 0xAD ) + .add( 0xBE ) + .add( 0xEF ) + .end() + .form(); + + assert_eq!( packet.id, 101 ); + assert_eq!( packet.payload, vec![ 0xDE, 0xAD, 0xBE, 0xEF ] ); +# } +``` +[See Vec example](https://github.com/Wandalen/wTools/blob/master/module/core/former/examples/former_collection_vector.rs) | [See HashMap example](https://github.com/Wandalen/wTools/blob/master/module/core/former/examples/former_collection_hashmap.rs) | [See Custom Collection example](https://github.com/Wandalen/wTools/blob/master/module/core/former/examples/former_custom_collection.rs) + +### `#[ subform_entry ]` - Building Collection Entries Individually + +Use the `#[ subform_entry ]` attribute on collection fields (like `Vec< Child >` or `HashMap< String, Child >`) where each *entry* of the collection should be built using its own dedicated `Former`. This is ideal when the elements themselves are complex structs requiring configuration. + +* **Attribute:** `#[ subform_entry ]` (applied to the collection field) +* **Requirement:** The *value type* of the collection entry (e.g., `Child` in `Vec< Child >` or `HashMap< K, Child >`) must derive `Former`. For map types, the value type must also implement `former_types::ValToEntry< CollectionType >` to specify how a formed value maps back to a key-value pair entry. +* **Generated Setter:** By default, a method with the same name as the field (e.g., `.child()`) is generated. This method returns the `Former` for the *entry type* (e.g., `ChildFormer`). +* **Usage:** + ```rust + parent_former + .child() // Returns ChildFormer< ParentFormer, ... > + .child_field1(...) + .child_field2(...) + .end() // Finalizes Child, adds it to the collection, returns ParentFormer + .child() // Start building the *next* Child entry + // ... configure second child ... + .end() // Finalizes second Child, adds it, returns ParentFormer + .form() // Finalizes Parent + ``` +* **`End` Condition:** The derive macro generates a specialized `End` struct (e.g., `ParentSubformEntryChildrenEnd`). When `.end()` is called on the entry's former (`ChildFormer`), this `End` struct's `call` method takes the `Child` storage, preforms it into a `Child` instance, potentially converts it to the collection's `Entry` type (using `ValToEntry` for maps), adds the entry to the parent's collection field (passed via context), and returns the parent former. +* **Customization:** + * `#[ subform_entry( name = new_setter_name ) ]`: Renames the generated setter method. + * `#[ subform_entry( setter = false ) ]`: Disables the user-facing setter, but still generates the internal helper (`._children_subform_entry()`) and `End` struct for custom setter implementation (e.g., to pass arguments like a key for a map). + +**Example (HashMap):** + +```rust +# #[ cfg( not( all( feature = "enabled", feature = "derive_former", any( feature = "use_alloc", not( feature = "no_std" ) ) ) ) ) ] +# fn main() {} +# #[ cfg( all( feature = "enabled", feature = "derive_former", any( feature = "use_alloc", not( feature = "no_std" ) ) ) ) ] +# fn main() +# { + use former::Former; + use std::collections::HashMap; + use former::ValToEntry; // Needed for HashMap entry conversion + + #[ derive( Debug, Default, PartialEq, Clone, Former ) ] + pub struct Command + { + name : String, + description : String, + } + + // Required to map the formed `Command` back to a (key, value) pair for the HashMap + impl ValToEntry< HashMap< String, Command > > for Command + { + type Entry = ( String, Command ); + #[ inline( always ) ] + fn val_to_entry( self ) -> Self::Entry + { + ( self.name.clone(), self ) + } + } + + #[ derive( Debug, Default, PartialEq, Former ) ] + pub struct CommandRegistry + { + #[ subform_entry ] // Each command will be built using CommandFormer + commands : HashMap< String, Command >, + } + + let registry = CommandRegistry::former() + .commands() // Returns CommandFormer< CommandRegistryFormer, ... > + .name( "help".to_string() ) + .description( "Shows help".to_string() ) + .end() // Forms Command, adds ("help", Command{...}) to map, returns CommandRegistryFormer + .commands() // Start next command + .name( "run".to_string() ) + .description( "Runs the task".to_string() ) + .end() // Forms Command, adds ("run", Command{...}) to map, returns CommandRegistryFormer + .form(); // Finalizes CommandRegistry + + assert_eq!( registry.commands.len(), 2 ); + assert!( registry.commands.contains_key( "help" ) ); + assert_eq!( registry.commands[ "run" ].description, "Runs the task" ); +# } +``` +[See HashMap example](https://github.com/Wandalen/wTools/blob/master/module/core/former/examples/former_custom_subform_entry.rs) | [See Vec example](https://github.com/Wandalen/wTools/blob/master/module/core/former/tests/inc/former_struct_tests/subform_entry.rs) + +## Customization + +The `former` crate offers several ways to customize the generated builder beyond the standard setters and subformers. + +### Custom Setters (Alternative and Overriding) + +You can define your own setter methods directly within an `impl` block for the generated `...Former` struct. + +* **Alternative Setters:** Define methods with different names that perform custom logic before setting the value in the former's storage. This allows for preprocessing or validation specific to that 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; + + #[ derive( Debug, Former ) ] + pub struct MyStruct + { + word : String, + } + + // Implement methods on the generated former struct + impl MyStructFormer // No generics needed if not using Definition/Context/End + { + // Custom alternative setter for `word` + pub fn word_exclaimed( mut self, value : impl Into< String > ) -> Self + { + // Ensure field wasn't already set (optional but good practice) + debug_assert!( self.storage.word.is_none(), "Field 'word' was already set" ); + // Custom logic: add exclamation mark + self.storage.word = Some( format!( "{}!", value.into() ) ); + self + } + } + + // Use the default setter + let s1 = MyStruct::former().word( "Hello" ).form(); + assert_eq!( s1.word, "Hello" ); + + // Use the custom alternative setter + let s2 = MyStruct::former().word_exclaimed( "Hello" ).form(); + assert_eq!( s2.word, "Hello!" ); + # } + ``` + [See full example code](https://github.com/Wandalen/wTools/blob/master/module/core/former/examples/former_custom_setter.rs) + +* **Overriding Setters:** You can completely replace the default generated setter by: + 1. Disabling the default setter using `#[ scalar( setter = false ) ]` (or `subform_... ( setter = false )`). + 2. Implementing a method with the *original* field name on the `...Former` struct. + + ```rust + # #[ 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, Former ) ] + pub struct MyStruct + { + #[ scalar( setter = false ) ] // Disable default .word() setter + word : String, + } + + // Provide your own implementation for .word() + // Note: Needs generics if it uses Definition, Context, or End from the former + impl< Definition > MyStructFormer< Definition > + where + Definition : former::FormerDefinition< Storage = MyStructFormerStorage >, + { + #[ inline ] + pub fn word< Src >( mut self, src : Src ) -> Self + where + Src : ::core::convert::Into< String >, + { + debug_assert!( self.storage.word.is_none() ); + // Custom logic: always add exclamation mark + self.storage.word = Some( format!( "{}!", src.into() ) ); + self + } + } + + // Now .word() always uses the custom implementation + let s1 = MyStruct::former().word( "Hello" ).form(); + assert_eq!( s1.word, "Hello!" ); + # } + ``` + [See full example code](https://github.com/Wandalen/wTools/blob/master/module/core/former/examples/former_custom_setter_overriden.rs) + +### Custom Defaults (`#[ former( default = ... ) ]`) + +While `former` automatically uses `Default::default()` for fields that are not explicitly set, you can specify a *different* default value using the `#[ former( default = ... ) ]` attribute on a field. + +* **Purpose:** + * Provide a default for types that do not implement `Default`. + * Specify a non-standard default value (e.g., `true` for a `bool`, or a specific number). + * Initialize collections with default elements. +* **Usage:** Apply the attribute directly to the field, providing a valid Rust expression as the default value. +* **Behavior:** If the field's setter is *not* called during the building process, the expression provided in `default = ...` will be evaluated and used when `.form()` or `.end()` is called. If the setter *is* called, the attribute's default is ignored. + +**Example:** + +```rust +# #[ 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 ) ] + pub struct NetworkConfig + { + #[ former( default = 8080 ) ] // Default port if not specified + port : u16, + #[ former( default = "127.0.0.1".to_string() ) ] // Default host + host : String, + #[ former( default = vec![ "admin".to_string() ] ) ] // Default users + initial_users : Vec< String >, + timeout : Option< u32 >, // Optional, defaults to None + } + + // Form without setting port, host, or initial_users + let config = NetworkConfig::former() + .timeout( 5000 ) // Only set timeout + .form(); + + assert_eq!( config.port, 8080 ); + assert_eq!( config.host, "127.0.0.1" ); + assert_eq!( config.initial_users, vec![ "admin".to_string() ] ); + assert_eq!( config.timeout, Some( 5000 ) ); +# } +``` +[See full example code](https://github.com/Wandalen/wTools/blob/master/module/core/former/examples/former_custom_defaults.rs) + +### Storage-Specific Fields (`#[ storage_fields( ... ) ]`) + +Sometimes, the building process requires temporary data or intermediate calculations that shouldn't be part of the final struct. The `#[ storage_fields( ... ) ]` attribute allows you to define fields that exist *only* within the generated `...FormerStorage` struct. + +* **Purpose:** + * Store temporary state needed during building (e.g., flags, counters). + * Accumulate data used to calculate a final field value within a `Mutator`. + * Hold configuration that influences multiple final fields. +* **Usage:** Apply the attribute at the *struct level*, providing a comma-separated list of field definitions just like regular struct fields. + ```rust + #[ derive( Former ) ] + #[ storage_fields( temp_count : i32, config_flag : Option< bool > ) ] + struct MyStruct + { + final_value : String, + } + ``` +* **Behavior:** + * The specified fields (e.g., `temp_count`, `config_flag`) are added to the `...FormerStorage` struct, wrapped in `Option` like regular fields. + * Setters *are* generated for these storage fields on the `...Former` struct (e.g., `.temp_count( value )`, `.config_flag( value )`). + * These fields are **not** included in the final struct (`MyStruct` in the example). + * Their values are typically accessed and used within a custom `Mutator` (using `#[ mutator( custom ) ]`) to influence the final values of the actual struct fields just before `.form()` completes. + +**Example Snippet (Conceptual - See Full Example Linked Below):** + +```rust +# #[ 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 > ) ] // Temporary fields + #[ mutator( custom ) ] // We need a mutator to use the storage fields + pub struct StructWithStorage + { + c : String, // Final field + } + + // Custom mutator implementation needed to use storage fields 'a' and 'b' + impl< C, F > former::FormerMutator for StructWithStorageFormerDefinitionTypes< C, F > + { + #[ inline ] + fn form_mutation( storage : &mut Self::Storage, _context : &mut Option< Self::Context > ) + { + // Use storage fields 'a' and 'b' to calculate final field 'c' + let val_a = storage.a.unwrap_or( 0 ); // Get value or default + let val_b = storage.b.as_deref().unwrap_or( "default_b" ); + storage.c = Some( format!( "{} - {}", val_a, val_b ) ); // Set the *storage* for 'c' + } + } + + let result = StructWithStorage::former() + .a( 13 ) // Set storage field 'a' + .b( "value_b".to_string() ) // Set storage field 'b' + // .c() is not called directly, it's set by the mutator + .form(); // Mutator runs, then final struct is built + + assert_eq!( result.c, "13 - value_b" ); +# } +``` +[See full example code](https://github.com/Wandalen/wTools/blob/master/module/core/former/examples/former_custom_mutator.rs) + +### Custom Mutators (`#[ mutator( custom ) ]` + `impl FormerMutator`) + +For complex scenarios where the final field values depend on the combination of multiple inputs or require calculations just before the object is built, you can define a custom **mutator**. + +* **Purpose:** To execute custom logic that modifies the `...FormerStorage` or `Context` immediately before the `FormingEnd::call` method finalizes the object. +* **Trigger:** Apply the `#[ mutator( custom ) ]` attribute to the struct definition. This tells `#[ derive( Former ) ]` *not* to generate the default (empty) `impl FormerMutator`. +* **Implementation:** You must manually implement the `former_types::FormerMutator` trait for the generated `...FormerDefinitionTypes` struct associated with your main struct. + ```rust + impl< /* Generics from DefinitionTypes... */ > former::FormerMutator + for YourStructFormerDefinitionTypes< /* Generics... */ > + { + fn form_mutation( storage : &mut Self::Storage, context : &mut Option< Self::Context > ) + { + // Your custom logic here. + // You can read from and write to `storage` fields. + // Example: Calculate a final field based on storage fields. + // if storage.some_flag.unwrap_or( false ) { + // storage.final_value = Some( storage.value_a.unwrap_or(0) + storage.value_b.unwrap_or(0) ); + // } + } + } + ``` +* **Execution:** The `form_mutation` method runs automatically when `.form()` or `.end()` is called, right before the `End` condition's `call` method executes. +* **Use Cases:** + * Implementing complex default logic based on other fields. + * Performing validation that requires access to multiple fields simultaneously. + * Calculating derived fields. + * Utilizing values from `#[ storage_fields( ... ) ]` to set final struct fields. + +**Example Snippet (Conceptual - See Full Example Linked Below):** + +(The example for `storage_fields` also demonstrates a custom mutator) + +```rust +# #[ 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 ) ] // Enable custom mutator + pub struct StructWithMutator + { + c : String, + } + + // Provide the custom implementation + impl< C, F > former::FormerMutator for StructWithMutatorFormerDefinitionTypes< C, F > + { + #[ inline ] + fn form_mutation( storage : &mut Self::Storage, _context : &mut Option< Self::Context > ) + { + // Logic using storage fields 'a' and 'b' to set storage for 'c' + let val_a = storage.a.unwrap_or( 0 ); + let val_b = storage.b.as_deref().unwrap_or( "default_b" ); + storage.c = Some( format!( "Mutated: {} - {}", val_a, val_b ) ); + } + } + + let result = StructWithMutator::former() + .a( 13 ) + .b( "value_b".to_string() ) + // .c() is not called; its value in storage is set by the mutator + .form(); // form_mutation runs before final construction + + assert_eq!( result.c, "Mutated: 13 - value_b" ); +# } +``` +[See full example code](https://github.com/Wandalen/wTools/blob/master/module/core/former/examples/former_custom_mutator.rs) + +### Custom Definitions & End Handlers + +For the ultimate level of control over the forming process, you can define entirely custom `FormerDefinition` and `FormingEnd` implementations. This is typically needed for integrating non-standard collections or implementing highly specialized finalization logic. + +* **Motivation:** + * Integrating custom collection types not supported by default. + * Changing the final `Formed` type returned by `.form()`/`.end()`. + * Implementing complex validation or transformation logic during finalization. + * Managing resources or side effects at the end of the building process. + +* **Core Traits to Implement:** + 1. **`former_types::FormerDefinitionTypes`:** Define your `Storage`, `Context`, and `Formed` types. + 2. **`former_types::FormerMutator`:** Implement `form_mutation` if needed (often empty if logic is in `FormingEnd`). + 3. **`former_types::FormerDefinition`:** Link your `Types` and specify your custom `End` type. + 4. **`former_types::FormingEnd`:** Implement the `call` method containing your finalization logic. This method consumes the `Storage` and `Context` and must return the `Formed` type. + +* **Usage:** + * You typically wouldn't use `#[ derive( Former ) ]` on the struct itself if you're providing a fully custom definition ecosystem. + * Instead, you manually define the `Former`, `Storage`, `DefinitionTypes`, `Definition`, and `End` structs/traits. + * The `CollectionFormer` or a manually defined `Former` struct is then used with your custom `Definition`. + +**Example (Custom Definition to Sum Vec Elements):** + +This example defines a custom former that collects `i32` values into a `Vec< i32 >` (as storage) but whose final `Formed` type is the `i32` sum of the elements. + +```rust +# #[ cfg( not( all( feature = "enabled", feature = "derive_former", any( feature = "use_alloc", not( feature = "no_std" ) ) ) ) ) ] +# fn main() {} +# #[ cfg( all( feature = "enabled", feature = "derive_former", any( feature = "use_alloc", not( feature = "no_std" ) ) ) ) ] +# fn main() +# { + use former_types::*; // Import necessary traits + + // 1. Define a marker struct for the custom definition + struct SummationDefinition; + + // 2. Implement FormerDefinitionTypes + impl FormerDefinitionTypes for SummationDefinition + { + type Storage = Vec< i32 >; // Store numbers in a Vec + type Formed = i32; // Final result is the sum (i32) + type Context = (); // No context needed + } + + // 3. Implement FormerMutator (empty in this case) + impl FormerMutator for SummationDefinition {} + + // 4. Implement FormerDefinition, linking Types and End + impl FormerDefinition for SummationDefinition + { + type Types = SummationDefinition; + type End = SummationDefinition; // Use self as the End handler + type Storage = Vec< i32 >; + type Formed = i32; + type Context = (); + } + + // 5. Implement FormingEnd for the End type (SummationDefinition itself) + impl FormingEnd< SummationDefinition > for SummationDefinition + { + fn call + ( + &self, + storage : Vec< i32 >, // Consumes the storage (Vec) + _context : Option< () > + ) -> i32 // Returns the Formed type (i32) + { + // Custom logic: sum the elements + storage.iter().sum() + } + } + + // Use the custom definition with CollectionFormer + let sum = CollectionFormer::< i32, SummationDefinition >::new( SummationDefinition ) + .add( 1 ) + .add( 2 ) + .add( 10 ) + .form(); // Invokes SummationDefinition::call + + assert_eq!( sum, 13 ); +# } +``` +[See full example code](https://github.com/Wandalen/wTools/blob/master/module/core/former/examples/former_custom_definition.rs) + +### Custom Collections + +While `former` provides built-in support for standard library collections when using `#[ subform_collection ]` or `#[ subform_entry ]`, you can integrate your own custom collection types by implementing the necessary `former_types::Collection` traits. + +* **Motivation:** Allow the `former` derive macro's subform features (especially `#[ subform_collection ]` and `#[ subform_entry ]`) to work seamlessly with your custom data structures that behave like collections. +* **Core Traits to Implement for the Custom Collection Type:** + 1. **`former_types::Collection`:** + * Define `type Entry` (the type added/iterated, e.g., `K` for a set, `(K, V)` for a map). + * Define `type Val` (the logical value type, e.g., `K` for a set, `V` for a map). + * Implement `fn entry_to_val( Self::Entry ) -> Self::Val`. + 2. **`former_types::CollectionAdd`:** + * Implement `fn add( &mut self, Self::Entry ) -> bool`. + 3. **`former_types::CollectionAssign`:** + * Implement `fn assign< Elements >( &mut self, Elements ) -> usize where Elements : IntoIterator< Item = Self::Entry >`. + * Requires `Self : IntoIterator< Item = Self::Entry >`. + 4. **`former_types::CollectionValToEntry< Self::Val >`:** + * Define `type Entry` (same as `Collection::Entry`). + * Implement `fn val_to_entry( Self::Val ) -> Self::Entry`. This is crucial for `#[ subform_entry ]` to map a formed value back into an entry suitable for adding to the collection. + 5. **`former_types::Storage` + `former_types::StoragePreform`:** Implement these to define how the collection itself is handled as storage (usually just returning `Self`). + 6. **`Default`:** Your collection likely needs to implement `Default`. + 7. **`IntoIterator`:** Required for `CollectionAssign`. + +* **Custom Definition (Optional but Recommended):** While not strictly required if your collection mimics a standard one closely, providing a custom `FormerDefinition` (like `MyCollectionDefinition`) allows for more control and clarity, especially if using `#[ subform_collection( definition = MyCollectionDefinition ) ]`. You'd implement: + 1. `MyCollectionDefinitionTypes` (implementing `FormerDefinitionTypes`). + 2. `MyCollectionDefinition` (implementing `FormerDefinition`). + 3. Implement `EntityTo...` traits (`EntityToFormer`, `EntityToStorage`, `EntityToDefinition`, `EntityToDefinitionTypes`) to link your custom collection type to its definition and former. + +* **Usage with Derive:** Once the traits are implemented, you can use your custom collection type in a struct and apply `#[ subform_collection ]` or `#[ subform_entry ]` as usual. You might need `#[ subform_collection( definition = ... ) ]` if you created a custom definition. + +**Example (Conceptual - See Full Example Linked Below):** + +Imagine a `LoggingSet` that wraps a `HashSet` but logs additions. + +```rust +# #[ cfg( not( all( feature = "enabled", feature = "derive_former", any( feature = "use_alloc", not( feature = "no_std" ) ) ) ) ) ] +# fn main() {} +# #[ cfg( all( feature = "enabled", feature = "derive_former", any( feature = "use_alloc", not( feature = "no_std" ) ) ) ) ] +# fn main() { +# use std::collections::HashSet; +# use former_types::*; +# #[ derive( Debug, PartialEq, Default ) ] +# pub struct LoggingSet< K > where K : core::cmp::Eq + core::hash::Hash, { set : HashSet< K > } +# impl< K : core::cmp::Eq + core::hash::Hash > Collection for LoggingSet< K > { type Entry = K; type Val = K; fn entry_to_val( e : K ) -> K { e } } +# impl< K : core::cmp::Eq + core::hash::Hash > CollectionAdd for LoggingSet< K > { fn add( &mut self, e : K ) -> bool { println!( "Adding: {:?}", e ); self.set.insert( e ) } } +# impl< K : core::cmp::Eq + core::hash::Hash > IntoIterator for LoggingSet< K > { type Item = K; type IntoIter = std::collections::hash_set::IntoIter; fn into_iter( self ) -> Self::IntoIter { self.set.into_iter() } } +# impl< K : core::cmp::Eq + core::hash::Hash > CollectionAssign for LoggingSet< K > { fn assign< Elements : IntoIterator< Item = K > >( &mut self, elements : Elements ) -> usize { self.set.clear(); self.set.extend( elements ); self.set.len() } } +# impl< K : core::cmp::Eq + core::hash::Hash > CollectionValToEntry< K > for LoggingSet< K > { type Entry = K; fn val_to_entry( val : K ) -> K { val } } +# impl< K : core::cmp::Eq + core::hash::Hash > Storage for LoggingSet< K > { type Preformed = Self; } +# impl< K : core::cmp::Eq + core::hash::Hash > StoragePreform for LoggingSet< K > { fn preform( self ) -> Self { self } } +# #[ derive( former::Former, Debug, PartialEq, Default ) ] +# pub struct Config { #[ subform_collection ] items : LoggingSet< String > } +// Assume LoggingSet implements all necessary Collection traits... + +let config = Config::former() + .items() // Returns a CollectionFormer using LoggingSet's trait impls + .add( "item1".to_string() ) // Uses LoggingSet::add + .add( "item2".to_string() ) + .end() + .form(); + +assert!( config.items.set.contains( "item1" ) ); +assert!( config.items.set.contains( "item2" ) ); +# } +``` +[See full example code](https://github.com/Wandalen/wTools/blob/master/module/core/former/examples/former_custom_collection.rs) + +## Attribute Reference + +Customize the behavior of `#[ derive( Former ) ]` using the following attributes: + +### Struct-Level Attributes + +Apply these directly above the `struct` or `enum` definition. + +* **`#[ storage_fields( field_name : FieldType, ... ) ]`** + * Defines extra fields exclusive to the temporary `...FormerStorage` struct. These fields won't be part of the final formed struct but can be set via the former and used for intermediate calculations, often within a custom `Mutator`. + * *Example:* `#[ storage_fields( counter : i32, is_valid : Option< bool > ) ]` + +* **`#[ mutator( custom ) ]`** + * Disables the automatic generation of the default (empty) `impl former::FormerMutator`. You must provide your own implementation to define custom logic in the `form_mutation` method, which runs just before the `End` condition finalizes the struct. + * *Example:* `#[ mutator( custom ) ]` + +* **`#[ perform( fn method_name<...> () -> OutputType ) ]`** + * Specifies a method *on the original struct* to be called by the former's `.perform()` method *after* the struct instance has been formed. The `.perform()` method will return the result of this specified method instead of the struct instance itself. The signature provided must match a method implemented on the struct. + * *Example:* `#[ perform( fn finalize_setup( self ) -> Result< Self, SetupError > ) ]` + +* **`#[ debug ]`** + * Prints the code generated by the `Former` derive macro to the console during compilation. Useful for understanding the macro's output or debugging issues. + * *Example:* `#[ derive( Former ) ] #[ debug ] struct MyStruct { ... }` + +### Field-Level / Variant-Level Attributes + +Apply these directly above fields within a struct or variants within an enum. + +**General Field Control:** + +* **`#[ former( default = expression ) ]`** + * Provides a default value for the field if its setter is not called during the building process. The `expression` must evaluate to a value assignable to the field's type. + * *Example:* `#[ former( default = 10 ) ] count : i32;`, `#[ former( default = "guest".to_string() ) ] user : String;` + +**Scalar Field Control:** (Applies to simple fields or variants marked `#[scalar]`) + +* **`#[ scalar ]`** (Implicit for simple struct fields, required for tuple/unit enum variants to get a direct constructor) + * Ensures a standard setter method (`.field_name( value )`) or a direct constructor (`Enum::variant_name( value )`) is generated. + * **Arguments:** + * `name = new_setter_name`: Renames the setter method (e.g., `#[ scalar( name = set_field ) ]`). + * `setter = bool`: Explicitly enables/disables setter generation (e.g., `#[ scalar( setter = false ) ]`). Default: `true`. + * `debug`: Prints a sketch of the generated scalar setter to the console during compilation. + +**Subformer Field/Variant Control:** (For nested building) + +* **`#[ subform_scalar ]`** (Applies to struct fields whose type derives `Former`, or single-field tuple enum variants whose type derives `Former`) + * Generates a method returning a subformer for the nested struct/type (e.g., `.field_name()` returns `InnerFormer`). Default behavior for single-field enum variants holding a `Former`-derived type unless `#[scalar]` is used. + * **Arguments:** + * `name = new_setter_name`: Renames the subformer starter method (e.g., `#[ subform_scalar( name = configure_child ) ]`). + * `setter = bool`: Enables/disables the subformer starter method. Default: `true`. + * `debug`: Prints a sketch of the generated subform scalar setter and `End` struct to the console. + +* **`#[ subform_collection ]`** (Applies to struct fields holding standard or custom collections) + * Generates a method returning a collection-specific subformer (e.g., `.field_name()` returns `VectorFormer` or `HashMapFormer`). + * **Arguments:** + * `definition = path::to::CollectionDefinition`: Specifies the collection type definition (e.g., `#[ subform_collection( definition = former::VectorDefinition ) ]`). Often inferred for standard collections. Required for custom collections unless `EntityToDefinition` is implemented. + * `name = new_setter_name`: Renames the subformer starter method (e.g., `#[ subform_collection( name = add_entries ) ]`). + * `setter = bool`: Enables/disables the subformer starter method. Default: `true`. + * `debug`: Prints a sketch of the generated subform collection setter and `End` struct. + +* **`#[ subform_entry ]`** (Applies to struct fields holding collections where entries derive `Former`) + * Generates a method returning a subformer for a *single entry* of the collection (e.g., `.field_name()` returns `EntryFormer`). Requires `ValToEntry` for map types. + * **Arguments:** + * `name = new_setter_name`: Renames the entry subformer starter method (e.g., `#[ subform_entry( name = command ) ]`). + * `setter = bool`: Enables/disables the entry subformer starter method. Default: `true`. + * `debug`: Prints a sketch of the generated subform entry setter and `End` struct. + +## Component Model Derives (Related Utilities) + +While the core of this crate is the `#[ derive( Former ) ]` macro, the `former` crate (by re-exporting from `former_types` and `former_meta`) also provides a suite of related derive macros focused on **type-based component access and manipulation**. These are often useful in conjunction with or independently of the main `Former` derive. + +These derives require the corresponding features to be enabled (they are enabled by default). + +* **`#[ derive( Assign ) ]`:** + * Implements the `former_types::Assign< FieldType, IntoT >` trait for each field of the struct. + * Allows setting a field based on its **type**, using `.assign( value )` where `value` can be converted into the field's type. + * Requires fields to have unique types within the struct. + * *Example:* `my_struct.assign( 10_i32 ); my_struct.assign( "hello".to_string() );` + +* **`#[ derive( ComponentFrom ) ]`:** + * Implements `std::convert::From< &YourStruct >` for each field's type. + * Allows extracting a field's value based on its **type** using `.into()` or `From::from()`. + * Requires fields to have unique types within the struct. + * *Example:* `let name : String = ( &my_struct ).into();` + +* **`#[ derive( ComponentsAssign ) ]`:** + * Generates a helper trait (e.g., `YourStructComponentsAssign`) with a method (e.g., `.your_struct_assign( &other_struct )`). + * This method assigns values from fields in `other_struct` to fields of the *same type* in `self`. + * Requires `From< &OtherStruct >` to be implemented for each relevant field type. + * Useful for updating a struct from another struct containing a subset or superset of its fields. + * *Example:* `my_struct.your_struct_assign( &source_struct );` + +* **`#[ derive( FromComponents ) ]`:** + * Implements `std::convert::From< T >` for the struct itself, where `T` is some source type. + * Allows constructing the struct *from* a source type `T`, provided `T` implements `Into< FieldType >` for each field in the struct. + * Requires fields to have unique types within the struct. + * *Example:* `let my_struct : YourStruct = source_struct.into();` + +These component derives offer a powerful, type-driven way to handle data mapping and transformation between different struct types. Refer to the specific examples and `former_types` documentation for more details. + +[See ComponentFrom example](https://github.com/Wandalen/wTools/blob/master/module/core/former/examples/former_component_from.rs) diff --git a/module/core/former/examples/readme.md b/module/core/former/examples/readme.md new file mode 100644 index 0000000000..65e9a8eb33 --- /dev/null +++ b/module/core/former/examples/readme.md @@ -0,0 +1,48 @@ +# Former Crate Examples + +This directory contains runnable examples demonstrating various features and use cases of the `former` crate and its associated derive macros (`#[ derive( Former ) ]`, `#[ derive( Assign ) ]`, etc.). + +Each file focuses on a specific aspect, from basic usage to advanced customization and subforming patterns. + +## How to Run Examples + +To run any of the examples listed below, navigate to the `former` crate's root directory (`module/core/former`) in your terminal and use the `cargo run --example` command, replacing `` with the name of the file (without the `.rs` extension). + +**Command:** + +```sh +# Replace with the desired example file name +cargo run --example +``` + +**Example:** + +```sh +# From the module/core/former directory: +cargo run --example former_trivial +``` + +**Note:** Some examples might require specific features to be enabled if you are running them outside the default configuration, although most rely on the default features. Check the top of the example file for any `#[ cfg(...) ]` attributes if you encounter issues. + +## Example Index + +| Group | Example File | Description | +|----------------------|------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------| +| **Basic Usage** | [former_trivial.rs](./former_trivial.rs) | Basic derive usage with required/optional fields. | +| | [former_many_fields.rs](./former_many_fields.rs) | Derive usage with various field types (primitives, String, Option, Vec, HashMap) using scalar setters. | +| **Collections** | [former_collection_vector.rs](./former_collection_vector.rs) | Building a `Vec` using `#[ subform_collection ]` and `.add()`. | +| | [former_collection_hashmap.rs](./former_collection_hashmap.rs) | Building a `HashMap` using `#[ subform_collection ]` and `.add( ( k, v ) )`. | +| | [former_collection_hashset.rs](./former_collection_hashset.rs) | Building a `HashSet` using `#[ subform_collection ]` and `.add( value )`. | +| **Customization** | [former_custom_defaults.rs](./former_custom_defaults.rs) | Specifying custom default values with `#[ former( default = ... ) ]`. | +| | [former_custom_setter.rs](./former_custom_setter.rs) | Defining an alternative custom setter method on the Former struct. | +| | [former_custom_setter_overriden.rs](./former_custom_setter_overriden.rs) | Overriding a default setter using `#[ scalar( setter = false ) ]`. | +| | [former_custom_scalar_setter.rs](./former_custom_scalar_setter.rs) | Defining a custom *scalar* setter manually (contrasting subform approach). | +| **Subformers** | [former_custom_subform_scalar.rs](./former_custom_subform_scalar.rs) | Building a nested struct using `#[ subform_scalar ]`. | +| | [former_custom_subform_collection.rs](./former_custom_subform_collection.rs) | Implementing a custom *collection* subformer setter manually. | +| | [former_custom_subform_entry.rs](./former_custom_subform_entry.rs) | Building collection entries individually using `#[ subform_entry ]` and a custom setter helper. | +| | [former_custom_subform_entry2.rs](./former_custom_subform_entry2.rs) | Building collection entries individually using `#[ subform_entry ]` with fully manual closure logic. | +| **Advanced** | [former_custom_mutator.rs](./former_custom_mutator.rs) | Using `#[ storage_fields ]` and `#[ mutator( custom ) ]` with `impl FormerMutator`. | +| | [former_custom_definition.rs](./former_custom_definition.rs) | Defining a custom `FormerDefinition` and `FormingEnd` to change the formed type. | +| | [former_custom_collection.rs](./former_custom_collection.rs) | Implementing `Collection` traits for a custom collection type. | +| **Component Model** | [former_component_from.rs](./former_component_from.rs) | Using `#[ derive( ComponentFrom ) ]` for type-based field extraction. | +| **Debugging** | [former_debug.rs](./former_debug.rs) | Using the struct-level `#[ debug ]` attribute to view generated code. | diff --git a/module/core/former/src/lib.rs b/module/core/former/src/lib.rs index b692c6e9b6..040d0628a4 100644 --- a/module/core/former/src/lib.rs +++ b/module/core/former/src/lib.rs @@ -4,6 +4,11 @@ #![ doc( html_root_url = "https://docs.rs/former/latest/former/" ) ] #![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] +// xxx : add to readme example with enums +// xxx : disable and phase out attribute "[ perform( fn method_name<...> () -> OutputType ) ]" +// xxx : split out crate component model +// xxx : fix commented out tests + /// Namespace with dependencies. #[ cfg( feature = "enabled" ) ] pub mod dependency diff --git a/module/core/former/tests/inc/components_tests/component_assign_tuple.rs b/module/core/former/tests/inc/components_tests/component_assign_tuple.rs new file mode 100644 index 0000000000..27be4629c4 --- /dev/null +++ b/module/core/former/tests/inc/components_tests/component_assign_tuple.rs @@ -0,0 +1,10 @@ +use super::*; +#[ allow( unused_imports ) ] +use former::Assign; + +#[ derive( Default, PartialEq, Debug, former::Assign ) ] +struct TupleStruct( i32, String ); + +// + +include!( "./only_test/component_assign_tuple.rs" ); diff --git a/module/core/former/tests/inc/components_tests/component_assign_tuple_manual.rs b/module/core/former/tests/inc/components_tests/component_assign_tuple_manual.rs new file mode 100644 index 0000000000..a6dfecdea4 --- /dev/null +++ b/module/core/former/tests/inc/components_tests/component_assign_tuple_manual.rs @@ -0,0 +1,33 @@ +use super::*; +#[ allow( unused_imports ) ] +use former::Assign; + +#[ derive( Default, PartialEq, Debug ) ] +struct TupleStruct( i32, String ); + +// Manual implementation for the first field (i32) +impl< IntoT > Assign< i32, IntoT > for TupleStruct +where + IntoT : Into< i32 >, +{ + fn assign( &mut self, component : IntoT ) + { + self.0 = component.into(); // Access field by index + } +} + +// Manual implementation for the second field (String) +impl< IntoT > Assign< String, IntoT > for TupleStruct +where + IntoT : Into< String >, +{ + fn assign( &mut self, component : IntoT ) + { + self.1 = component.into(); // Access field by index + } +} + +// + +// Reuse the same test logic +include!( "./only_test/component_assign_tuple.rs" ); \ No newline at end of file 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 2151d3d3d7..d335da81d2 100644 --- a/module/core/former/tests/inc/components_tests/component_from.rs +++ b/module/core/former/tests/inc/components_tests/component_from.rs @@ -16,5 +16,4 @@ pub struct Options1 // - include!( "./only_test/component_from.rs" ); diff --git a/module/core/former/tests/inc/components_tests/component_from_tuple.rs b/module/core/former/tests/inc/components_tests/component_from_tuple.rs new file mode 100644 index 0000000000..bf57968482 --- /dev/null +++ b/module/core/former/tests/inc/components_tests/component_from_tuple.rs @@ -0,0 +1,8 @@ +use super::*; + +#[ derive( Debug, Default, PartialEq, former::ComponentFrom ) ] +struct TupleStruct( i32, String ); + +// + +include!( "./only_test/component_from_tuple.rs" ); \ No newline at end of file diff --git a/module/core/former/tests/inc/components_tests/component_from_tuple_manual.rs b/module/core/former/tests/inc/components_tests/component_from_tuple_manual.rs new file mode 100644 index 0000000000..248bb0308d --- /dev/null +++ b/module/core/former/tests/inc/components_tests/component_from_tuple_manual.rs @@ -0,0 +1,29 @@ +use super::*; + +#[ derive( Debug, Default, PartialEq ) ] +struct TupleStruct( i32, String ); + +// Manual implementation for the first field (i32) +impl From< &TupleStruct > for i32 +{ + #[ inline( always ) ] + fn from( src : &TupleStruct ) -> Self + { + src.0.clone() // Access field by index + } +} + +// Manual implementation for the second field (String) +impl From< &TupleStruct > for String +{ + #[ inline( always ) ] + fn from( src : &TupleStruct ) -> Self + { + src.1.clone() // Access field by index + } +} + +// + +// Reuse the same test logic +include!( "./only_test/component_from_tuple.rs" ); \ No newline at end of file diff --git a/module/core/former/tests/inc/components_tests/components_assign_tuple.rs b/module/core/former/tests/inc/components_tests/components_assign_tuple.rs new file mode 100644 index 0000000000..24c656b072 --- /dev/null +++ b/module/core/former/tests/inc/components_tests/components_assign_tuple.rs @@ -0,0 +1,34 @@ +use super::*; +#[ allow( unused_imports ) ] +use former::{ Assign, AssignWithType }; + +// Define TupleStruct1 with more fields/types +#[ derive( Debug, Default, PartialEq, former::Assign, former::ComponentsAssign ) ] +struct TupleStruct1( i32, String, f32 ); + +// Define TupleStruct2 with a subset of types from TupleStruct1 +#[ derive( Debug, Default, PartialEq, former::Assign, former::ComponentsAssign ) ] +struct TupleStruct2( i32, String ); + +// Implement From<&TupleStruct1> for the types present in TupleStruct2 +impl From< &TupleStruct1 > for i32 +{ + #[ inline( always ) ] + fn from( src : &TupleStruct1 ) -> Self + { + src.0.clone() + } +} + +impl From< &TupleStruct1 > for String +{ + #[ inline( always ) ] + fn from( src : &TupleStruct1 ) -> Self + { + src.1.clone() + } +} + +// + +include!( "./only_test/components_assign_tuple.rs" ); \ No newline at end of file diff --git a/module/core/former/tests/inc/components_tests/components_assign_tuple_manual.rs b/module/core/former/tests/inc/components_tests/components_assign_tuple_manual.rs new file mode 100644 index 0000000000..9107dfdfbd --- /dev/null +++ b/module/core/former/tests/inc/components_tests/components_assign_tuple_manual.rs @@ -0,0 +1,142 @@ +// module/core/former/tests/inc/components_tests/components_assign_tuple_manual.rs +use super::*; +#[ allow( unused_imports ) ] +use former::{ Assign, AssignWithType }; + +// Define TupleStruct1 without derive +#[ derive( Debug, Default, PartialEq ) ] +struct TupleStruct1( i32, String, f32 ); + +// Define TupleStruct2 without derive +#[ derive( Debug, Default, PartialEq ) ] +struct TupleStruct2( i32, String ); + +// Manual Assign impls for TupleStruct1 +impl< IntoT > Assign< i32, IntoT > for TupleStruct1 +where + IntoT : Into< i32 >, +{ + fn assign + ( + &mut self, + component : IntoT, + ) + { + self.0 = component.into(); + } +} + +impl< IntoT > Assign< String, IntoT > for TupleStruct1 +where + IntoT : Into< String >, +{ + fn assign + ( + &mut self, + component : IntoT, + ) + { + self.1 = component.into(); + } +} + +impl< IntoT > Assign< f32, IntoT > for TupleStruct1 +where + IntoT : Into< f32 >, +{ + fn assign + ( + &mut self, + component : IntoT, + ) + { + self.2 = component.into(); + } +} + +// Manual Assign impls for TupleStruct2 +impl< IntoT > Assign< i32, IntoT > for TupleStruct2 +where + IntoT : Into< i32 >, +{ + fn assign + ( + &mut self, + component : IntoT, + ) + { + self.0 = component.into(); + } +} + +impl< IntoT > Assign< String, IntoT > for TupleStruct2 +where + IntoT : Into< String >, +{ + fn assign + ( + &mut self, + component : IntoT, + ) + { + self.1 = component.into(); + } +} + + +// Implement From<&TupleStruct1> for the types present in TupleStruct2 +impl From< &TupleStruct1 > for i32 +{ + #[ inline( always ) ] + fn from( src : &TupleStruct1 ) -> Self + { + src.0.clone() + } +} + +impl From< &TupleStruct1 > for String +{ + #[ inline( always ) ] + fn from( src : &TupleStruct1 ) -> Self + { + src.1.clone() + } +} + +// Manually define the ComponentsAssign trait and impl for TupleStruct2 +pub trait TupleStruct2ComponentsAssign< IntoT > +where + IntoT : Into< i32 >, + IntoT : Into< String >, + IntoT : Clone, +{ + fn tuple_struct_2_assign + ( + &mut self, + component : IntoT, + ); +} + +impl< T, IntoT > TupleStruct2ComponentsAssign< IntoT > for T +where + T : former::Assign< i32, IntoT >, + T : former::Assign< String, IntoT >, + IntoT : Into< i32 >, + IntoT : Into< String >, + IntoT : Clone, +{ + #[ inline( always ) ] + fn tuple_struct_2_assign + ( + &mut self, + component : IntoT, + ) + { + former::Assign::< i32, _ >::assign( self, component.clone() ); + former::Assign::< String, _ >::assign( self, component.clone() ); + } +} + + +// Re-include the test logic +include!( "./only_test/components_assign_tuple.rs" ); \ No newline at end of file diff --git a/module/core/former/tests/inc/components_tests/from_components_tuple.rs b/module/core/former/tests/inc/components_tests/from_components_tuple.rs new file mode 100644 index 0000000000..d6089fe1dc --- /dev/null +++ b/module/core/former/tests/inc/components_tests/from_components_tuple.rs @@ -0,0 +1,43 @@ +use super::*; + +// Define a source tuple struct with several fields +#[ derive( Debug, Default, PartialEq ) ] +struct SourceTuple( i32, String, f32 ); + +// Implement From<&SourceTuple> for each type it contains +// This is needed for the FromComponents bounds `T: Into` to work in the test +impl From< &SourceTuple > for i32 +{ + #[ inline( always ) ] + fn from( src : &SourceTuple ) -> Self + { + src.0.clone() + } +} + +impl From< &SourceTuple > for String +{ + #[ inline( always ) ] + fn from( src : &SourceTuple ) -> Self + { + src.1.clone() + } +} + +impl From< &SourceTuple > for f32 +{ + #[ inline( always ) ] + fn from( src : &SourceTuple ) -> Self + { + src.2.clone() + } +} + + +// Define a target tuple struct with a subset of fields/types +#[ derive( Debug, Default, PartialEq, former::FromComponents ) ] +struct TargetTuple( i32, String ); + +// + +include!( "./only_test/from_components_tuple.rs" ); \ No newline at end of file diff --git a/module/core/former/tests/inc/components_tests/from_components_tuple_manual.rs b/module/core/former/tests/inc/components_tests/from_components_tuple_manual.rs new file mode 100644 index 0000000000..bef4c15712 --- /dev/null +++ b/module/core/former/tests/inc/components_tests/from_components_tuple_manual.rs @@ -0,0 +1,50 @@ +use super::*; + +// Define a source tuple struct with several fields +#[ derive( Debug, Default, PartialEq, Clone ) ] // Added Clone for manual impl +struct SourceTuple( i32, String, f32 ); + +// Define a target tuple struct (no derive here) +#[ derive( Debug, Default, PartialEq ) ] +struct TargetTuple( i32, String ); + +// Implement From<&SourceTuple> for each type it contains that TargetTuple needs +impl From< &SourceTuple > for i32 +{ + #[ inline( always ) ] + fn from( src : &SourceTuple ) -> Self + { + src.0.clone() + } +} + +impl From< &SourceTuple > for String +{ + #[ inline( always ) ] + fn from( src : &SourceTuple ) -> Self + { + src.1.clone() + } +} + +// Manual implementation of From for TargetTuple +impl< T > From< T > for TargetTuple +where + T : Into< i32 >, + T : Into< String >, + T : Clone, // The generic T needs Clone for the assignments below +{ + #[ inline( always ) ] + fn from( src : T ) -> Self + { + let field0 = Into::< i32 >::into( src.clone() ); + let field1 = Into::< String >::into( src.clone() ); + Self( field0, field1 ) // Use tuple constructor syntax + } +} + + +// + +// Reuse the same test logic +include!( "./only_test/from_components_tuple.rs" ); \ No newline at end of file diff --git a/module/core/former/tests/inc/components_tests/only_test/component_assign_tuple.rs b/module/core/former/tests/inc/components_tests/only_test/component_assign_tuple.rs new file mode 100644 index 0000000000..f052a32e3c --- /dev/null +++ b/module/core/former/tests/inc/components_tests/only_test/component_assign_tuple.rs @@ -0,0 +1,16 @@ +#[ test ] +fn component_assign() +{ + let mut got : TupleStruct = Default::default(); + got.assign( 13 ); + got.assign( "John".to_string() ); + assert_eq!( got, TupleStruct( 13, "John".to_string() ) ); + + // Test impute as well + let mut got : TupleStruct = Default::default(); + got = got + .impute( 13 ) + .impute( "John".to_string() ) + ; + assert_eq!( got, TupleStruct( 13, "John".to_string() ) ); +} \ No newline at end of file diff --git a/module/core/former/tests/inc/components_tests/only_test/component_from_tuple.rs b/module/core/former/tests/inc/components_tests/only_test/component_from_tuple.rs new file mode 100644 index 0000000000..08458b8774 --- /dev/null +++ b/module/core/former/tests/inc/components_tests/only_test/component_from_tuple.rs @@ -0,0 +1,15 @@ +#[ test ] +fn component_from() +{ + let t1 = TupleStruct( 42, "Hello".to_string() ); + + // Test converting to i32 + let got_i32 : i32 = ( &t1 ).into(); + let exp_i32 : i32 = 42; + assert_eq!( got_i32, exp_i32 ); + + // Test converting to String + let got_string : String = ( &t1 ).into(); + let exp_string : String = "Hello".to_string(); + assert_eq!( got_string, exp_string ); +} \ No newline at end of file diff --git a/module/core/former/tests/inc/components_tests/only_test/components_assign_tuple.rs b/module/core/former/tests/inc/components_tests/only_test/components_assign_tuple.rs new file mode 100644 index 0000000000..9df29836cd --- /dev/null +++ b/module/core/former/tests/inc/components_tests/only_test/components_assign_tuple.rs @@ -0,0 +1,47 @@ +#[ test ] +fn components_assign() +{ + // Create an instance of the larger struct + let t1 = TupleStruct1( 42, "Hello".to_string(), 13.1 ); + + // Create a default instance of the smaller struct + let mut t2 = TupleStruct2::default(); + + // Call the generated assign method (assuming snake_case name) + // TupleStruct2ComponentsAssign::tuple_struct_2_assign( &mut t2, &t1 ); + t2.tuple_struct_2_assign( &t1 ); // Use the method directly + + // Define the expected result + let exp = TupleStruct2( 42, "Hello".to_string() ); + + // Assert equality + assert_eq!( t2, exp ); +} + +// Optional: Test assigning to self if types match exactly +#[derive(Debug, Default, PartialEq, former::Assign, former::ComponentsAssign)] +struct SelfTuple(bool, char); + +impl From<&SelfTuple> for bool +{ + fn from( src: &SelfTuple ) -> Self + { + src.0 + } +} +impl From<&SelfTuple> for char +{ + fn from( src: &SelfTuple ) -> Self + { + src.1 + } +} + +#[ test ] +fn components_assign_self() +{ + let t1 = SelfTuple(true, 'a'); + let mut t2 = SelfTuple::default(); + t2.self_tuple_assign(&t1); + assert_eq!(t2, t1); +} \ No newline at end of file diff --git a/module/core/former/tests/inc/components_tests/only_test/from_components_tuple.rs b/module/core/former/tests/inc/components_tests/only_test/from_components_tuple.rs new file mode 100644 index 0000000000..ef02f75964 --- /dev/null +++ b/module/core/former/tests/inc/components_tests/only_test/from_components_tuple.rs @@ -0,0 +1,20 @@ +#[ test ] +fn from_components() +{ + let src = SourceTuple( 42, "Hello".to_string(), 13.01 ); + + // Convert from &SourceTuple + let got : TargetTuple = ( &src ).into(); + let exp = TargetTuple( 42, "Hello".to_string() ); + assert_eq!( got, exp ); + + // Convert using From::from + let got : TargetTuple = TargetTuple::from( &src ); + let exp = TargetTuple( 42, "Hello".to_string() ); + assert_eq!( got, exp ); + + // Ensure clone works if needed for the generic From bound + // let src_clone = src.clone(); // Would need #[derive(Clone)] on SourceTuple + // let got_clone : TargetTuple = src_clone.into(); + // assert_eq!( got_clone, exp ); +} \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/basic_derive.rs b/module/core/former/tests/inc/former_enum_tests/basic_derive.rs new file mode 100644 index 0000000000..5583a9723d --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/basic_derive.rs @@ -0,0 +1,21 @@ + +use super::*; + +// Define the inner structs +#[derive(Debug, Clone, PartialEq, former::Former)] +pub struct Break { pub condition : bool } + +#[derive(Debug, Clone, PartialEq, former::Former)] +pub struct Run { pub command : String } + +// Derive Former on the simplified enum - This should generate static methods +#[derive(Debug, Clone, PartialEq, former::Former)] +// #[debug] +enum FunctionStep +{ + Break( Break ), + Run( Run ), +} + +// Include the test logic +include!( "basic_only_test.rs" ); diff --git a/module/core/former/tests/inc/former_enum_tests/basic_manual.rs b/module/core/former/tests/inc/former_enum_tests/basic_manual.rs new file mode 100644 index 0000000000..c6a0c4b751 --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/basic_manual.rs @@ -0,0 +1,87 @@ + +use super::*; +use former::StoragePreform; + +// --- Inner Struct Definitions --- +#[derive(Debug, Clone, PartialEq, former::Former)] +pub struct Break { pub condition: bool } + +#[derive(Debug, Clone, PartialEq, former::Former)] +pub struct Run { pub command: String } + +// --- Enum Definition --- +#[derive(Debug, Clone, PartialEq)] +enum FunctionStep +{ + Break(Break), + Run(Run), +} + +// --- Specialized End Structs --- +#[derive(Default, Debug)] pub struct FunctionStepBreakEnd; +#[derive(Default, Debug)] pub struct FunctionStepRunEnd; + +// --- Static Variant Constructor Methods --- +impl FunctionStep +{ + #[ inline( always ) ] + pub fn r#break() // Using raw identifier + -> BreakFormer< BreakFormerDefinition< (), Self, FunctionStepBreakEnd > > + { + // Correct: Call associated function `begin` on the Former type + BreakFormer::begin( None, None, FunctionStepBreakEnd::default() ) + } + + #[ inline( always ) ] + pub fn run() + -> RunFormer< RunFormerDefinition< (), Self, FunctionStepRunEnd > > + { + // Correct: Call associated function `begin` on the Former type + RunFormer::begin( None, None, FunctionStepRunEnd::default() ) + } +} + +// --- FormingEnd Implementations for End Structs --- + +// End for Break variant +impl former::FormingEnd +< + BreakFormerDefinitionTypes< (), FunctionStep > // Context is (), Formed is FunctionStep +> +for FunctionStepBreakEnd +{ + #[ inline( always ) ] + fn call + ( + &self, + sub_storage : BreakFormerStorage, // Storage of the inner type (Break) + _context : Option< () >, // Context is () from ::begin + ) -> FunctionStep // Returns the Enum type + { + let data = sub_storage.preform(); // Get the Break data + FunctionStep::Break( data ) // Construct the enum variant + } +} + +// End for Run variant +impl former::FormingEnd +< + RunFormerDefinitionTypes< (), FunctionStep > // Context is (), Formed is FunctionStep +> +for FunctionStepRunEnd +{ + #[ inline( always ) ] + fn call + ( + &self, + sub_storage : RunFormerStorage, // Storage of the inner type (Run) + _context : Option< () >, // Context is () from ::begin + ) -> FunctionStep // Returns the Enum type + { + let data = sub_storage.preform(); // Get the Run data + FunctionStep::Run( data ) // Construct the enum variant + } +} + +// Include the test logic +include!( "./basic_only_test.rs" ); // Renamed from _static_only_test diff --git a/module/core/former/tests/inc/former_enum_tests/basic_only_test.rs b/module/core/former/tests/inc/former_enum_tests/basic_only_test.rs new file mode 100644 index 0000000000..763a6363c6 --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/basic_only_test.rs @@ -0,0 +1,22 @@ + +#[ test ] +fn build_break_variant_static() // Test name kept for clarity, could be renamed +{ + let got = FunctionStep::r#break() // Use raw identifier here + .condition( true ) + .form(); // This calls FunctionStepBreakEnd::call + + let expected = FunctionStep::Break( Break { condition : true } ); + assert_eq!( got, expected ); +} + +#[ test ] +fn build_run_variant_static() // Test name kept for clarity, could be renamed +{ + let got = FunctionStep::run() + .command( "cargo build" ) + .form(); // This calls FunctionStepRunEnd::call + + let expected = FunctionStep::Run( Run { command : "cargo build".to_string() } ); + assert_eq!( got, expected ); +} diff --git a/module/core/former/tests/inc/former_enum_tests/enum_named_fields_derive.rs b/module/core/former/tests/inc/former_enum_tests/enum_named_fields_derive.rs new file mode 100644 index 0000000000..b00c849d63 --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/enum_named_fields_derive.rs @@ -0,0 +1,32 @@ +use super::*; + +// Define the enum with different kinds of variants, including struct-like ones with varying field counts. +#[ derive( Debug, PartialEq, former::Former ) ] +// #[ debug ] // Uncomment to see generated code +pub enum EnumWithNamedFields +{ + // Struct-like variant with ZERO named fields + // Expected: EnumWithNamedFields::variant_zero().form() -> EnumWithNamedFields::VariantZero {} + VariantZero {}, + + // Struct-like variant with ONE named field + // Expected: EnumWithNamedFields::variant_one().field_a("val").form() -> EnumWithNamedFields::VariantOne { field_a: "val" } + VariantOne + { + field_a : String, + }, + + // Struct-like variant with MULTIPLE named fields + // Expected: EnumWithNamedFields::variant_two().field_b(1).field_c(true).form() -> EnumWithNamedFields::VariantTwo { field_b: 1, field_c: true } + VariantTwo + { + field_b : i32, + field_c : bool, + }, + + // Keep a unit variant for completeness check + UnitVariant, +} + +// Include the test logic file (using the new name) +include!( "enum_named_fields_only_test.rs" ); diff --git a/module/core/former/tests/inc/former_enum_tests/enum_named_fields_manual.rs b/module/core/former/tests/inc/former_enum_tests/enum_named_fields_manual.rs new file mode 100644 index 0000000000..a27ce60dcb --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/enum_named_fields_manual.rs @@ -0,0 +1,324 @@ +// File: module/core/former/tests/inc/former_enum_tests/enum_named_fields_manual.rs +use super::*; +use former:: +{ + FormingEnd, StoragePreform, FormerDefinition, FormerDefinitionTypes, Storage, + ReturnPreformed, FormerBegin, FormerMutator, +}; + +// Define the enum without the derive macro +#[ derive( Debug, PartialEq ) ] +pub enum EnumWithNamedFields // Renamed enum for clarity +{ + VariantZero {}, + VariantOne { field_a : String }, + VariantTwo { field_b : i32, field_c : bool }, + UnitVariant, +} + +// --- Manual Former Implementation --- + +// --- Components for VariantZero --- + +// Storage (empty for zero fields) +#[ derive( Debug, Default ) ] +pub struct EnumWithNamedFieldsVariantZeroFormerStorage {} +impl Storage for EnumWithNamedFieldsVariantZeroFormerStorage +{ + // The "preformed" type here is conceptually the anonymous struct `{}` + // but we don't have a direct type for that. We'll handle construction in the End. + // Let's use a unit type as a placeholder for the preformed data. + type Preformed = (); +} +impl StoragePreform for EnumWithNamedFieldsVariantZeroFormerStorage +{ + fn preform( self ) -> Self::Preformed {} // Returns unit +} + +// Definition Types +#[ derive( Default, Debug ) ] +pub struct EnumWithNamedFieldsVariantZeroFormerDefinitionTypes< C = (), F = EnumWithNamedFields > +{ _p : core::marker::PhantomData< ( C, F ) > } +impl< C, F > FormerDefinitionTypes for EnumWithNamedFieldsVariantZeroFormerDefinitionTypes< C, F > +{ + type Storage = EnumWithNamedFieldsVariantZeroFormerStorage; + type Context = C; + type Formed = F; +} +impl< C, F > FormerMutator for EnumWithNamedFieldsVariantZeroFormerDefinitionTypes< C, F > {} + +// Definition +#[ derive( Default, Debug ) ] +pub struct EnumWithNamedFieldsVariantZeroFormerDefinition< C = (), F = EnumWithNamedFields, E = EnumWithNamedFieldsVariantZeroEnd > +{ _p : core::marker::PhantomData< ( C, F, E ) > } +impl< C, F, E > FormerDefinition for EnumWithNamedFieldsVariantZeroFormerDefinition< C, F, E > +where E : FormingEnd< EnumWithNamedFieldsVariantZeroFormerDefinitionTypes< C, F > > +{ + type Storage = EnumWithNamedFieldsVariantZeroFormerStorage; + type Context = C; + type Formed = F; + type Types = EnumWithNamedFieldsVariantZeroFormerDefinitionTypes< C, F >; + type End = E; +} + +// Former (no setters needed) +pub struct EnumWithNamedFieldsVariantZeroFormer< Definition = EnumWithNamedFieldsVariantZeroFormerDefinition > +where Definition : FormerDefinition< Storage = EnumWithNamedFieldsVariantZeroFormerStorage > +{ + storage : Definition::Storage, + context : Option< Definition::Context >, + on_end : Option< Definition::End >, +} +// Standard Former methods (new, form, end, begin...) +impl< Definition > EnumWithNamedFieldsVariantZeroFormer< Definition > +where Definition : FormerDefinition< Storage = EnumWithNamedFieldsVariantZeroFormerStorage > +{ + #[ inline( always ) ] pub fn form( self ) -> < Definition::Types as FormerDefinitionTypes >::Formed { self.end() } + #[ inline( always ) ] pub fn end( mut self ) -> < Definition::Types as FormerDefinitionTypes >::Formed + { + let on_end = self.on_end.take().unwrap(); + let context = self.context.take(); + < Definition::Types as FormerMutator >::form_mutation( &mut self.storage, &mut self.context ); + on_end.call( self.storage, context ) + } + #[ inline( always ) ] pub fn begin + ( storage : Option< Definition::Storage >, context : Option< Definition::Context >, on_end : Definition::End ) -> Self + { Self { storage : storage.unwrap_or_default(), context, on_end : Some( on_end ) } } + #[ allow( dead_code ) ] + #[ inline( always ) ] pub fn new( on_end : Definition::End ) -> Self { Self::begin( None, None, on_end ) } +} + +// End Struct +#[ derive( Default, Debug ) ] pub struct EnumWithNamedFieldsVariantZeroEnd; +// FormingEnd Impl +impl FormingEnd< EnumWithNamedFieldsVariantZeroFormerDefinitionTypes< (), EnumWithNamedFields > > +for EnumWithNamedFieldsVariantZeroEnd +{ + #[ inline( always ) ] + fn call + ( &self, _sub_storage : EnumWithNamedFieldsVariantZeroFormerStorage, _context : Option< () > ) + -> EnumWithNamedFields + { + // _sub_storage.preform(); // Preform returns (), which we ignore + EnumWithNamedFields::VariantZero {} // Construct the enum variant + } +} + +// --- Components for VariantOne --- + +// Storage +#[ derive( Debug, Default ) ] +pub struct EnumWithNamedFieldsVariantOneFormerStorage { pub field_a : Option< String > } +impl Storage for EnumWithNamedFieldsVariantOneFormerStorage { type Preformed = String; } // Preformed is just the inner field type +impl StoragePreform for EnumWithNamedFieldsVariantOneFormerStorage +{ + fn preform( mut self ) -> Self::Preformed { self.field_a.take().unwrap_or_default() } +} + +// Definition Types +#[ derive( Default, Debug ) ] +pub struct EnumWithNamedFieldsVariantOneFormerDefinitionTypes< C = (), F = EnumWithNamedFields > +{ _p : core::marker::PhantomData< ( C, F ) > } +impl< C, F > FormerDefinitionTypes for EnumWithNamedFieldsVariantOneFormerDefinitionTypes< C, F > +{ + type Storage = EnumWithNamedFieldsVariantOneFormerStorage; + type Context = C; + type Formed = F; +} +impl< C, F > FormerMutator for EnumWithNamedFieldsVariantOneFormerDefinitionTypes< C, F > {} + +// Definition +#[ derive( Default, Debug ) ] +pub struct EnumWithNamedFieldsVariantOneFormerDefinition< C = (), F = EnumWithNamedFields, E = EnumWithNamedFieldsVariantOneEnd > +{ _p : core::marker::PhantomData< ( C, F, E ) > } +impl< C, F, E > FormerDefinition for EnumWithNamedFieldsVariantOneFormerDefinition< C, F, E > +where E : FormingEnd< EnumWithNamedFieldsVariantOneFormerDefinitionTypes< C, F > > +{ + type Storage = EnumWithNamedFieldsVariantOneFormerStorage; + type Context = C; + type Formed = F; + type Types = EnumWithNamedFieldsVariantOneFormerDefinitionTypes< C, F >; + type End = E; +} + +// Former +pub struct EnumWithNamedFieldsVariantOneFormer< Definition = EnumWithNamedFieldsVariantOneFormerDefinition > +where Definition : FormerDefinition< Storage = EnumWithNamedFieldsVariantOneFormerStorage > +{ + storage : Definition::Storage, + context : Option< Definition::Context >, + on_end : Option< Definition::End >, +} +// Standard Former methods + Setter +impl< Definition > EnumWithNamedFieldsVariantOneFormer< Definition > +where Definition : FormerDefinition< Storage = EnumWithNamedFieldsVariantOneFormerStorage > +{ + #[ inline( always ) ] pub fn form( self ) -> < Definition::Types as FormerDefinitionTypes >::Formed { self.end() } + #[ inline( always ) ] pub fn end( mut self ) -> < Definition::Types as FormerDefinitionTypes >::Formed + { + let on_end = self.on_end.take().unwrap(); + let context = self.context.take(); + < Definition::Types as FormerMutator >::form_mutation( &mut self.storage, &mut self.context ); + on_end.call( self.storage, context ) + } + #[ inline( always ) ] pub fn begin + ( storage : Option< Definition::Storage >, context : Option< Definition::Context >, on_end : Definition::End ) -> Self + { Self { storage : storage.unwrap_or_default(), context, on_end : Some( on_end ) } } + + #[ allow( dead_code ) ] + #[ inline( always ) ] pub fn new( on_end : Definition::End ) -> Self { Self::begin( None, None, on_end ) } + + // Setter for field_a + #[ inline ] pub fn field_a( mut self, src : impl Into< String > ) -> Self + { self.storage.field_a = Some( src.into() ); self } +} + +// End Struct +#[ derive( Default, Debug ) ] pub struct EnumWithNamedFieldsVariantOneEnd; +// FormingEnd Impl +impl FormingEnd< EnumWithNamedFieldsVariantOneFormerDefinitionTypes< (), EnumWithNamedFields > > +for EnumWithNamedFieldsVariantOneEnd +{ + #[ inline( always ) ] + fn call + ( &self, sub_storage : EnumWithNamedFieldsVariantOneFormerStorage, _context : Option< () > ) + -> EnumWithNamedFields + { + let field_a_data = sub_storage.preform(); // Get the String + EnumWithNamedFields::VariantOne { field_a : field_a_data } // Construct the enum variant + } +} + +// --- Components for VariantTwo --- + +// Storage +#[ derive( Debug, Default ) ] +pub struct EnumWithNamedFieldsVariantTwoFormerStorage +{ + pub field_b : Option< i32 >, + pub field_c : Option< bool >, +} +// Preformed type is a tuple of the inner field types +impl Storage for EnumWithNamedFieldsVariantTwoFormerStorage { type Preformed = ( i32, bool ); } +impl StoragePreform for EnumWithNamedFieldsVariantTwoFormerStorage +{ + fn preform( mut self ) -> Self::Preformed + { + ( + self.field_b.take().unwrap_or_default(), + self.field_c.take().unwrap_or_default(), + ) + } +} + +// Definition Types +#[ derive( Default, Debug ) ] +pub struct EnumWithNamedFieldsVariantTwoFormerDefinitionTypes< C = (), F = EnumWithNamedFields > +{ _p : core::marker::PhantomData< ( C, F ) > } +impl< C, F > FormerDefinitionTypes for EnumWithNamedFieldsVariantTwoFormerDefinitionTypes< C, F > +{ + type Storage = EnumWithNamedFieldsVariantTwoFormerStorage; + type Context = C; + type Formed = F; +} +impl< C, F > FormerMutator for EnumWithNamedFieldsVariantTwoFormerDefinitionTypes< C, F > {} + +// Definition +#[ derive( Default, Debug ) ] +pub struct EnumWithNamedFieldsVariantTwoFormerDefinition< C = (), F = EnumWithNamedFields, E = EnumWithNamedFieldsVariantTwoEnd > +{ _p : core::marker::PhantomData< ( C, F, E ) > } +impl< C, F, E > FormerDefinition for EnumWithNamedFieldsVariantTwoFormerDefinition< C, F, E > +where E : FormingEnd< EnumWithNamedFieldsVariantTwoFormerDefinitionTypes< C, F > > +{ + type Storage = EnumWithNamedFieldsVariantTwoFormerStorage; + type Context = C; + type Formed = F; + type Types = EnumWithNamedFieldsVariantTwoFormerDefinitionTypes< C, F >; + type End = E; +} + +// Former +pub struct EnumWithNamedFieldsVariantTwoFormer< Definition = EnumWithNamedFieldsVariantTwoFormerDefinition > +where Definition : FormerDefinition< Storage = EnumWithNamedFieldsVariantTwoFormerStorage > +{ + storage : Definition::Storage, + context : Option< Definition::Context >, + on_end : Option< Definition::End >, +} +// Standard Former methods + Setters +impl< Definition > EnumWithNamedFieldsVariantTwoFormer< Definition > +where Definition : FormerDefinition< Storage = EnumWithNamedFieldsVariantTwoFormerStorage > +{ + #[ inline( always ) ] pub fn form( self ) -> < Definition::Types as FormerDefinitionTypes >::Formed { self.end() } + #[ inline( always ) ] pub fn end( mut self ) -> < Definition::Types as FormerDefinitionTypes >::Formed + { + let on_end = self.on_end.take().unwrap(); + let context = self.context.take(); + < Definition::Types as FormerMutator >::form_mutation( &mut self.storage, &mut self.context ); + on_end.call( self.storage, context ) + } + #[ inline( always ) ] pub fn begin + ( storage : Option< Definition::Storage >, context : Option< Definition::Context >, on_end : Definition::End ) -> Self + { Self { storage : storage.unwrap_or_default(), context, on_end : Some( on_end ) } } + + #[ allow( dead_code ) ] + #[ inline( always ) ] pub fn new( on_end : Definition::End ) -> Self { Self::begin( None, None, on_end ) } + + // Setters + #[ inline ] pub fn field_b( mut self, src : impl Into< i32 > ) -> Self { self.storage.field_b = Some( src.into() ); self } + #[ inline ] pub fn field_c( mut self, src : impl Into< bool > ) -> Self { self.storage.field_c = Some( src.into() ); self } +} + +// End Struct +#[ derive( Default, Debug ) ] pub struct EnumWithNamedFieldsVariantTwoEnd; +// FormingEnd Impl +impl FormingEnd< EnumWithNamedFieldsVariantTwoFormerDefinitionTypes< (), EnumWithNamedFields > > +for EnumWithNamedFieldsVariantTwoEnd +{ + #[ inline( always ) ] + fn call + ( &self, sub_storage : EnumWithNamedFieldsVariantTwoFormerStorage, _context : Option< () > ) + -> EnumWithNamedFields + { + let ( field_b_data, field_c_data ) = sub_storage.preform(); // Get the tuple (i32, bool) + EnumWithNamedFields::VariantTwo { field_b : field_b_data, field_c : field_c_data } // Construct the enum variant + } +} + +// --- Static Methods on the Enum --- +impl EnumWithNamedFields +{ + // Constructor for UnitVariant + #[ inline( always ) ] + pub fn unit_variant() -> Self + { + Self::UnitVariant + } + + // Starter for VariantZero subformer + #[ inline( always ) ] + pub fn variant_zero() + -> EnumWithNamedFieldsVariantZeroFormer< EnumWithNamedFieldsVariantZeroFormerDefinition< (), Self, EnumWithNamedFieldsVariantZeroEnd > > + { + EnumWithNamedFieldsVariantZeroFormer::begin( None, None, EnumWithNamedFieldsVariantZeroEnd::default() ) + } + + // Starter for VariantOne subformer + #[ inline( always ) ] + pub fn variant_one() + -> EnumWithNamedFieldsVariantOneFormer< EnumWithNamedFieldsVariantOneFormerDefinition< (), Self, EnumWithNamedFieldsVariantOneEnd > > + { + EnumWithNamedFieldsVariantOneFormer::begin( None, None, EnumWithNamedFieldsVariantOneEnd::default() ) + } + + // Starter for VariantTwo subformer + #[ inline( always ) ] + pub fn variant_two() + -> EnumWithNamedFieldsVariantTwoFormer< EnumWithNamedFieldsVariantTwoFormerDefinition< (), Self, EnumWithNamedFieldsVariantTwoEnd > > + { + EnumWithNamedFieldsVariantTwoFormer::begin( None, None, EnumWithNamedFieldsVariantTwoEnd::default() ) + } +} + +// Include the test logic file +include!( "enum_named_fields_only_test.rs" ); \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/enum_named_fields_only_test.rs b/module/core/former/tests/inc/former_enum_tests/enum_named_fields_only_test.rs new file mode 100644 index 0000000000..df1f6c577d --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/enum_named_fields_only_test.rs @@ -0,0 +1,53 @@ +use super::*; // Imports EnumWithNamedFields + +#[ test ] +fn variant_zero_fields() +{ + // Expect a static method `variant_zero()` returning a former with no setters. + let got = EnumWithNamedFields::variant_zero() + .form(); // .form() calls the End struct's logic + + let expected = EnumWithNamedFields::VariantZero {}; + assert_eq!( got, expected ); +} + +#[ test ] +fn variant_one_field() +{ + // Expect a static method `variant_one()` returning a former with a `.field_a()` setter. + let got = EnumWithNamedFields::variant_one() + .field_a( "value_a".to_string() ) + .form(); + + let expected = EnumWithNamedFields::VariantOne + { + field_a : "value_a".to_string(), + }; + assert_eq!( got, expected ); +} + +#[ test ] +fn variant_two_fields() +{ + // Expect a static method `variant_two()` returning a former with `.field_b()` and `.field_c()` setters. + let got = EnumWithNamedFields::variant_two() + .field_b( 42 ) + .field_c( true ) + .form(); + + let expected = EnumWithNamedFields::VariantTwo + { + field_b : 42, + field_c : true, + }; + assert_eq!( got, expected ); +} + +#[ test ] +fn unit_variant_construction() +{ + // Ensure the unit variant constructor still works. + let got = EnumWithNamedFields::unit_variant(); + let expected = EnumWithNamedFields::UnitVariant; + assert_eq!( got, expected ); +} \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/generics_in_tuple_variant_derive.rs b/module/core/former/tests/inc/former_enum_tests/generics_in_tuple_variant_derive.rs new file mode 100644 index 0000000000..a5b9488dc8 --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/generics_in_tuple_variant_derive.rs @@ -0,0 +1,26 @@ +// // module/core/former/tests/inc/former_enum_tests/generics_in_tuple_variant_derive.rs +// use super::*; // Imports testing infrastructure and potentially other common items +// use std::fmt::Debug; // Import Debug trait for bounds +// +// // --- Inner Struct Definition with Bounds --- +// // Needs to derive Former for the enum's derive to work correctly for subforming. +// #[derive(Debug, PartialEq, Clone, Copy, former::Former)] // Added Former derive +// pub struct InnerGeneric< T : Debug + Copy > // Added Copy bound here too +// { +// pub inner_field : T, +// } +// +// // --- Enum Definition with Bounds --- +// // Apply Former derive here. This is what we are testing. +// #[derive(Debug, PartialEq, former::Former)] +// // #[derive(Debug, PartialEq)] +// #[debug] +// pub enum EnumOuter< X : Copy > // Enum bound: Copy +// { +// Variant( InnerGeneric< X > ), // Inner type uses X, which must satisfy InnerGeneric's bounds (Debug + Copy) +// OtherVariant, +// } +// +// // --- Include the Test Logic --- +// // This file contains the actual #[test] functions. +// include!( "generics_in_tuple_variant_only_test.rs" ); \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/generics_in_tuple_variant_manual.rs b/module/core/former/tests/inc/former_enum_tests/generics_in_tuple_variant_manual.rs new file mode 100644 index 0000000000..6510cc7456 --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/generics_in_tuple_variant_manual.rs @@ -0,0 +1,214 @@ +// File: module/core/former/tests/inc/former_enum_tests/generics_in_tuple_variant_manual.rs +use super::*; // Imports testing infrastructure and potentially other common items +use std::fmt::Debug; // Import Debug trait for bounds +use std::marker::PhantomData; // Import PhantomData +use former:: +{ + FormingEnd, StoragePreform, FormerDefinition, FormerDefinitionTypes, Storage, + ReturnPreformed, FormerBegin, FormerMutator, // Added necessary imports +}; + +// --- Inner Struct Definition with Bounds --- +// Needs its own Former implementation (manual or derived) +// Added PartialEq derive and Default bound to T +#[ derive( Debug, PartialEq, Clone, Copy ) ] +pub struct InnerGeneric< T > +where + T : Debug + Copy + Default + PartialEq, // Added Default + PartialEq bounds +{ + pub inner_field: T, +} + +// --- Manual Former for InnerGeneric --- +// (Simplified manual implementation for brevity in this example) + +// Storage +#[ derive( Debug, Default ) ] +pub struct InnerGenericFormerStorage< T > +where + T : Debug + Copy + Default + PartialEq, // Added Default + PartialEq bounds +{ + pub inner_field : Option< T >, +} +// Added Default + PartialEq bounds to T +impl< T > Storage for InnerGenericFormerStorage< T > +where + T : Debug + Copy + Default + PartialEq, +{ + type Preformed = InnerGeneric< T >; +} +impl< T > StoragePreform for InnerGenericFormerStorage< T > +where + T : Debug + Copy + Default + PartialEq, // Added Default + PartialEq bounds +{ + fn preform( mut self ) -> Self::Preformed + { + // Use unwrap_or_default now that T: Default + InnerGeneric { inner_field : self.inner_field.take().unwrap_or_default() } + } +} + +// Definition Types +#[ derive( Default, Debug ) ] +pub struct InnerGenericFormerDefinitionTypes< T, C = (), F = InnerGeneric< T > > +where // Added where clause and bounds + T : Debug + Copy + Default + PartialEq, +{ _p : PhantomData< ( T, C, F ) > } + +// Added where clause and bounds +impl< T, C, F > FormerDefinitionTypes for InnerGenericFormerDefinitionTypes< T, C, F > +where + T : Debug + Copy + Default + PartialEq, +{ + type Storage = InnerGenericFormerStorage< T >; + type Context = C; + type Formed = F; +} +// Added where clause and bounds +impl< T, C, F > FormerMutator for InnerGenericFormerDefinitionTypes< T, C, F > +where + T : Debug + Copy + Default + PartialEq, +{} + +// Definition +#[ derive( Default, Debug ) ] +pub struct InnerGenericFormerDefinition< T, C = (), F = InnerGeneric< T >, E = ReturnPreformed > +where // Added where clause and bounds + T : Debug + Copy + Default + PartialEq, +{ _p : PhantomData< ( T, C, F, E ) > } + +// Added where clause and bounds +impl< T, C, F, E > FormerDefinition for InnerGenericFormerDefinition< T, C, F, E > +where + T : Debug + Copy + Default + PartialEq, + E : FormingEnd< InnerGenericFormerDefinitionTypes< T, C, F > > +{ + type Storage = InnerGenericFormerStorage< T >; + type Context = C; + type Formed = F; + type Types = InnerGenericFormerDefinitionTypes< T, C, F >; + type End = E; +} + +// Former +pub struct InnerGenericFormer< T, Definition = InnerGenericFormerDefinition< T > > +where // Added where clause and bounds + T : Debug + Copy + Default + PartialEq, + Definition : FormerDefinition< Storage = InnerGenericFormerStorage< T > > +{ + storage : Definition::Storage, + context : Option< Definition::Context >, + on_end : Option< Definition::End >, +} +// Standard Former methods + Setter +// Added where clause and bounds +impl< T, Definition > InnerGenericFormer< T, Definition > +where + T : Debug + Copy + Default + PartialEq, + Definition : FormerDefinition< Storage = InnerGenericFormerStorage< T > > +{ + #[ inline( always ) ] pub fn form( self ) -> < Definition::Types as FormerDefinitionTypes >::Formed { self.end() } + #[ inline( always ) ] pub fn end( mut self ) -> < Definition::Types as FormerDefinitionTypes >::Formed + { + let on_end = self.on_end.take().unwrap(); + let context = self.context.take(); + < Definition::Types as FormerMutator >::form_mutation( &mut self.storage, &mut self.context ); + on_end.call( self.storage, context ) + } + #[ inline( always ) ] pub fn begin + ( storage : Option< Definition::Storage >, context : Option< Definition::Context >, on_end : Definition::End ) -> Self + { Self { storage : storage.unwrap_or_default(), context, on_end : Some( on_end ) } } + #[ allow( dead_code ) ] + #[ inline( always ) ] pub fn new( on_end : Definition::End ) -> Self { Self::begin( None, None, on_end ) } + + // Setter for inner_field + #[ inline ] pub fn inner_field( mut self, src : impl Into< T > ) -> Self + { self.storage.inner_field = Some( src.into() ); self } +} + +// --- Enum Definition with Bounds --- +// Added Debug + PartialEq bounds to X +#[ derive( Debug, PartialEq ) ] +pub enum EnumOuter< X > +where + X : Copy + Debug + Default + PartialEq, // Added Debug + Default + PartialEq +{ + Variant( InnerGeneric< X > ), // Inner type uses X, which must satisfy InnerGeneric's bounds + #[ allow( dead_code ) ] + OtherVariant, // To make it slightly more realistic +} + +// --- Specialized End Struct for the Variant --- +// Added Debug + Default + PartialEq bounds to X +#[ derive( Default, Debug ) ] +pub struct EnumOuterVariantEnd< X > +where + X : Copy + Debug + Default + PartialEq, // Added Debug + Default + PartialEq +{ + _phantom: PhantomData< X >, +} + +// --- FormingEnd Implementation for the End Struct --- +// This is the core part demonstrating bound merging +#[ automatically_derived ] +impl< X > FormingEnd +< + // DefinitionTypes of InnerGenericFormer: Context=(), Formed=EnumOuter + InnerGenericFormerDefinitionTypes< X, (), EnumOuter< X > > +> +for EnumOuterVariantEnd< X > +where + X : Copy + Debug + Default + PartialEq, // Added Debug + Default + PartialEq +{ + #[ inline( always ) ] + fn call + ( + &self, + sub_storage: InnerGenericFormerStorage< X >, // Storage from InnerGenericFormer + _context: Option<()>, // Context is () from static method + ) -> EnumOuter< X > // Returns the EnumOuter + { + // Preform the inner data and wrap it in the correct enum variant. + let data = former::StoragePreform::preform( sub_storage ); + EnumOuter::Variant( data ) + } +} + + +// --- Static Method on EnumOuter --- +// This is the other core part demonstrating bound merging +// Added Debug + Default + PartialEq bounds to X +impl< X > EnumOuter< X > +where + X : Copy + Debug + Default + PartialEq, // Added Debug + Default + PartialEq +{ + /// Manually implemented subformer starter for the Variant variant. + #[ inline( always ) ] + pub fn variant() -> InnerGenericFormer // Return type is InnerGenericFormer... + < + X, // ...specialized with the enum's generic X... + // ...and configured with a definition that uses the specialized End struct. + InnerGenericFormerDefinition + < + X, // Generic for InnerGeneric + (), // Context = () + EnumOuter< X >, // Formed = EnumOuter + EnumOuterVariantEnd< X > // End = Specialized End struct + > + > + { + // Start the inner former using its `begin` associated function. + InnerGenericFormer::begin( None, None, EnumOuterVariantEnd::< X >::default() ) + } + + // Manual constructor for OtherVariant + #[ allow( dead_code ) ] + pub fn other_variant() -> Self + { + EnumOuter::OtherVariant + } +} + + +// --- Include the Test Logic --- +include!( "generics_in_tuple_variant_only_test.rs" ); \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/generics_in_tuple_variant_only_test.rs b/module/core/former/tests/inc/former_enum_tests/generics_in_tuple_variant_only_test.rs new file mode 100644 index 0000000000..541489d1ab --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/generics_in_tuple_variant_only_test.rs @@ -0,0 +1,68 @@ +// File: module/core/former/tests/inc/former_enum_tests/generics_in_tuple_variant_only_test.rs +use super::*; // Should import EnumOuter and InnerGeneric from either the manual or derive file +// use std::fmt::Debug; // Removed redundant import (E0252 fix) + +#[ test ] +fn basic_construction() +{ + // Define a concrete type that satisfies the bounds (Debug + Copy + Default + PartialEq) + let inner_value = 42_i32; // i32 implements all needed bounds + + // Expect EnumOuter::::variant() to return a former for InnerGeneric + // This tests the basic generic propagation and subformer mechanism. + let got = EnumOuter::< i32 >::variant() + .inner_field( inner_value ) // Assuming InnerGenericFormer has this setter + .form(); // This should call the specialized End struct + + // Define the expected enum instance + let expected_inner = InnerGeneric::< i32 > + { + inner_field : inner_value, + }; + let expected = EnumOuter::< i32 >::Variant( expected_inner ); + + assert_eq!( got, expected ); +} + +#[ test ] +fn construction_with_bounds() +{ + // Test with a custom type that meets the specific bounds: Debug + Copy + Default + PartialEq + #[ derive( Debug, Copy, Clone, PartialEq, Default ) ] // Added Default + struct MyCopyableDebug( f32 ); + + let inner_value = MyCopyableDebug( 3.14 ); + + // Expect EnumOuter::::variant() to work because + // MyCopyableDebug satisfies all required bounds. + // This tests the handling and merging of bounds from both the enum and the inner type. + let got = EnumOuter::< MyCopyableDebug >::variant() + .inner_field( inner_value ) + .form(); + + let expected_inner = InnerGeneric::< MyCopyableDebug > + { + inner_field : inner_value, + }; + let expected = EnumOuter::< MyCopyableDebug >::Variant( expected_inner ); + + assert_eq!( got, expected ); +} + +// Optional: Add a test that *should* fail compilation if bounds are not met. +// This requires a compile-fail test setup (like trybuild), which is outside +// the scope of just adding files here. +// #[test] +// fn construction_without_bounds_fails() +// { +// // Define a type that does NOT satisfy the bounds (e.g., no Copy or no Default) +// #[derive(Debug, PartialEq)] +// struct MyDebugOnly(f32); +// +// let inner_value = MyDebugOnly(1.0); +// +// // This call should ideally fail to compile because MyDebugOnly does not implement Copy/Default +// let _got = EnumOuter::< MyDebugOnly >::variant() // << Compile error expected here +// .inner_field( inner_value ) +// .form(); +// } \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/generics_independent_struct_derive.rs b/module/core/former/tests/inc/former_enum_tests/generics_independent_struct_derive.rs new file mode 100644 index 0000000000..d1a674ae52 --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/generics_independent_struct_derive.rs @@ -0,0 +1,51 @@ +// File: module/core/former/tests/inc/former_enum_tests/generics_independent_struct_derive.rs + +//! # Derive Test: Independent Generics in Struct Variants +//! +//! This test file focuses on verifying the `#[derive(Former)]` macro's ability to handle +//! enums with struct-like variants where the generics involved are independent. +//! Specifically, it tests an enum `EnumG6` where a variant `V1` contains a field +//! whose type uses a *concrete* type (`InnerG6`) unrelated to the enum's `T`. +//! +//! ## Purpose: +//! +//! - To ensure the derive macro correctly generates the implicit former infrastructure +//! (storage, definitions, former struct, end struct) for the struct variant `V1`. +//! - To verify that the generated code correctly handles the enum's generic parameter `T` +//! and its bounds (`BoundA`) where necessary (e.g., in the `End` struct and its `impl`). +//! - To confirm that the generated setters within the implicit former work for fields +//! containing concrete types like `InnerG6`. +//! - It uses the shared test logic from `generics_independent_struct_only_test.rs`. + +use super::*; // Imports testing infrastructure and potentially other common items +// FIX: Import PhantomData as it's now needed in the enum definition + +// --- Dummy Bounds and Concrete Types --- +// Are defined in the included _only_test.rs file + +// --- Inner Struct Definition --- +// Also defined in the included _only_test.rs file, +// but conceptually needed here for the enum definition. +// #[ derive( Debug, Clone, PartialEq, Default, former::Former ) ] +// pub struct InnerG6< U : BoundB > { pub inner_field : U } + +// --- Enum Definition with Bounds --- +// Apply Former derive here. This is what we are testing. +#[ derive( Debug, PartialEq, Clone, former::Former ) ] +// #[ debug ] // Uncomment to see generated code later +pub enum EnumG6< T : BoundA > // BoundA required by enum +{ + V1 // Struct-like variant + { + // Field holding the inner struct instantiated with a *concrete* type + inner : InnerG6< TypeForU >, // TypeForU satisfies BoundB implicitly via _only_test.rs + // A non-generic field for testing + flag : bool, + // FIX: Added PhantomData to use the generic parameter T, resolving E0392 + _phantom_t : std::marker::PhantomData, + }, +} + +// --- Include the Test Logic --- +// This file contains the actual #[test] functions. +include!( "generics_independent_struct_only_test.rs" ); \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/generics_independent_struct_manual.rs b/module/core/former/tests/inc/former_enum_tests/generics_independent_struct_manual.rs new file mode 100644 index 0000000000..bb9c5a4234 --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/generics_independent_struct_manual.rs @@ -0,0 +1,207 @@ +// File: module/core/former/tests/inc/former_enum_tests/generics_independent_struct_manual.rs + +//! # Manual Test: Independent Generics in Struct Variants +//! +//! This file provides a manual implementation of the `Former` pattern for an enum (`EnumG6`) +//! with a struct-like variant (`V1`) containing a field with an independent concrete type +//! (`InnerG6`). +//! +//! ## Purpose: +//! +//! - To serve as a reference implementation demonstrating how the `Former` pattern should +//! behave for this specific scenario involving independent generics in struct variants. +//! - To manually construct the implicit former infrastructure (Storage, Definitions, Former, End) +//! for the `V1` variant, ensuring correct handling of the enum's generic `T` and its bounds. +//! - To validate the logic used by the `#[derive(Former)]` macro by comparing its generated +//! code's behavior against this manual implementation using the shared tests in +//! `generics_independent_struct_only_test.rs`. + +use super::*; // Imports testing infrastructure and potentially other common items +// FIX: Removed redundant import, it's imported in _only_test.rs if needed there, +// but primarily needed here for manual impls. +use former_types:: +{ + Assign, // Needed for manual setter impls if we were doing that deeply + FormingEnd, StoragePreform, FormerDefinition, FormerDefinitionTypes, Storage, + ReturnPreformed, FormerBegin, FormerMutator, // Added necessary imports +}; + +// --- Dummy Bounds and Concrete Types --- +// Are defined in the included _only_test.rs file + +// --- Inner Struct Definition --- +// Also defined in the included _only_test.rs file. +// Needs its own Former implementation (manual or derived) for the subform setter test case, +// but for the direct setter test case here, we only need its definition. +// #[ derive( Debug, Clone, PartialEq, Default, former::Former ) ] +// pub struct InnerG6< U : BoundB > { pub inner_field : U } + +// --- Enum Definition with Bounds --- +#[ derive( Debug, PartialEq, Clone ) ] +pub enum EnumG6< T : BoundA > // BoundA required by the enum +{ + V1 // Struct-like variant + { + // Field holding the inner struct instantiated with a *concrete* type + inner : InnerG6< TypeForU >, // TypeForU satisfies BoundB implicitly via _only_test.rs + // A non-generic field for testing + flag : bool, + // FIX: Added PhantomData to use the generic parameter T + _phantom_t : PhantomData, + }, +} + +// --- Manual IMPLICIT Former Implementation for Variant V1 --- + +// Storage for V1's fields +#[ derive( Debug, Default ) ] +pub struct EnumG6V1FormerStorage< T : BoundA > // Needs enum's bound +{ + // Storage holds Option + pub inner : Option< InnerG6< TypeForU > >, // Uses concrete TypeForU + pub flag : Option< bool >, + // FIX: Storage also needs phantom data if the final struct needs it + _phantom : PhantomData, // Use the enum's generic +} +impl< T : BoundA > Storage for EnumG6V1FormerStorage< T > +{ + // Preformed type is a tuple of the *actual* field types + // FIX: Preformed type does not include PhantomData directly + type Preformed = ( InnerG6< TypeForU >, bool ); +} +impl< T : BoundA > StoragePreform for EnumG6V1FormerStorage< T > +{ + fn preform( mut self ) -> Self::Preformed + { + ( + // Use unwrap_or_default because InnerG6 derives Default + self.inner.take().unwrap_or_default(), + self.flag.take().unwrap_or_default(), // bool implements Default + // FIX: PhantomData is not part of the preformed tuple + ) + } +} + +// Definition Types for V1's implicit former +#[ derive( Default, Debug ) ] +// Generics: Enum's generics + Context2 + Formed2 +pub struct EnumG6V1FormerDefinitionTypes< T : BoundA, Context2 = (), Formed2 = EnumG6< T > > +{ _p : PhantomData< ( T, Context2, Formed2 ) > } + +impl< T : BoundA, Context2, Formed2 > FormerDefinitionTypes for EnumG6V1FormerDefinitionTypes< T, Context2, Formed2 > +{ + type Storage = EnumG6V1FormerStorage< T >; // Storage uses enum's generic T + type Context = Context2; + type Formed = Formed2; +} +impl< T : BoundA, Context2, Formed2 > FormerMutator for EnumG6V1FormerDefinitionTypes< T, Context2, Formed2 > {} + +// Definition for V1's implicit former +#[ derive( Default, Debug ) ] +// Generics: Enum's generics + Context2 + Formed2 + End2 +pub struct EnumG6V1FormerDefinition< T : BoundA, Context2 = (), Formed2 = EnumG6< T >, End2 = EnumG6V1End< T > > +{ _p : PhantomData< ( T, Context2, Formed2, End2 ) > } + +impl< T : BoundA, Context2, Formed2, End2 > FormerDefinition for EnumG6V1FormerDefinition< T, Context2, Formed2, End2 > +where End2 : FormingEnd< EnumG6V1FormerDefinitionTypes< T, Context2, Formed2 > > +{ + type Storage = EnumG6V1FormerStorage< T >; // Storage uses enum's generic T + type Context = Context2; + type Formed = Formed2; + type Types = EnumG6V1FormerDefinitionTypes< T, Context2, Formed2 >; + type End = End2; +} + +// Implicit Former for V1 +// Generics: Enum's generics + Definition (which defaults appropriately) +pub struct EnumG6V1Former< T : BoundA, Definition = EnumG6V1FormerDefinition< T > > +where Definition : FormerDefinition< Storage = EnumG6V1FormerStorage< T > > +{ + storage : Definition::Storage, + context : Option< Definition::Context >, + on_end : Option< Definition::End >, +} +// Standard Former methods + Setters for V1's fields +impl< T : BoundA, Definition > EnumG6V1Former< T, Definition > +where Definition : FormerDefinition< Storage = EnumG6V1FormerStorage< T > > +{ + #[ inline( always ) ] pub fn form( self ) -> < Definition::Types as FormerDefinitionTypes >::Formed { self.end() } + #[ inline( always ) ] pub fn end( mut self ) -> < Definition::Types as FormerDefinitionTypes >::Formed + { + let on_end = self.on_end.take().unwrap(); + let context = self.context.take(); + < Definition::Types as FormerMutator >::form_mutation( &mut self.storage, &mut self.context ); + on_end.call( self.storage, context ) + } + #[ inline( always ) ] pub fn begin + ( storage : Option< Definition::Storage >, context : Option< Definition::Context >, on_end : Definition::End ) -> Self + { Self { storage : storage.unwrap_or_default(), context, on_end : Some( on_end ) } } + #[ allow( dead_code ) ] + #[ inline( always ) ] pub fn new( on_end : Definition::End ) -> Self { Self::begin( None, None, on_end ) } + + // Setter for V1's 'inner' field (takes InnerG6) + #[ inline ] pub fn inner( mut self, src : impl Into< InnerG6< TypeForU > > ) -> Self + { self.storage.inner = Some( src.into() ); self } + + // Setter for V1's 'flag' field + #[ inline ] pub fn flag( mut self, src : impl Into< bool > ) -> Self + { self.storage.flag = Some( src.into() ); self } +} + +// --- Specialized End Struct for the V1 Variant --- +#[ derive( Default, Debug ) ] +pub struct EnumG6V1End< T : BoundA > // Only requires enum's bound +{ + _phantom : PhantomData< T >, +} + +// --- FormingEnd Implementation for the End Struct --- +#[ automatically_derived ] +impl< T : BoundA > FormingEnd // Only requires enum's bound +< + // DefinitionTypes of V1's implicit former: Context=(), Formed=EnumG6 + EnumG6V1FormerDefinitionTypes< T, (), EnumG6< T > > +> +for EnumG6V1End< T > +{ + #[ inline( always ) ] + fn call + ( + &self, + sub_storage : EnumG6V1FormerStorage< T >, // Storage from implicit former + _context : Option< () >, + ) -> EnumG6< T > // Returns the EnumG6 + { + // Preform the tuple (InnerG6, bool) + let ( inner_data, flag_data ) = former_types::StoragePreform::preform( sub_storage ); + // Construct the V1 variant + // FIX: Add phantom data field during construction + EnumG6::V1 { inner : inner_data, flag : flag_data, _phantom_t: PhantomData } + } +} + +// --- Static Method on EnumG6 --- +impl< T : BoundA > EnumG6< T > // Only requires enum's bound +{ + /// Manually implemented subformer starter for the V1 variant. + #[ inline( always ) ] + pub fn v_1() -> EnumG6V1Former // Return type is V1's implicit former... + < + T, // ...specialized with the enum's generic T... + // ...and configured with a definition that uses the specialized End struct. + EnumG6V1FormerDefinition + < + T, // Enum generic T + (), // Context = () + EnumG6< T >, // Formed = EnumG6 + EnumG6V1End< T > // End = Specialized End struct + > + > + { + // Start the implicit former using its `begin` associated function. + EnumG6V1Former::begin( None, None, EnumG6V1End::< T >::default() ) + } +} + +// --- Include the Test Logic --- +include!( "generics_independent_struct_only_test.rs" ); \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/generics_independent_struct_only_test.rs b/module/core/former/tests/inc/former_enum_tests/generics_independent_struct_only_test.rs new file mode 100644 index 0000000000..5684c7b0ce --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/generics_independent_struct_only_test.rs @@ -0,0 +1,94 @@ +// File: module/core/former/tests/inc/former_enum_tests/generics_independent_struct_only_test.rs + +/// # Test Logic: Independent Generics in Struct Variants +/// +/// This file contains the core test logic for verifying the `Former` derive macro's +/// handling of enums where: +/// - The enum itself has generic parameters (e.g., `EnumG6`). +/// - A struct-like variant within the enum contains fields whose types might use +/// different generic parameters or concrete types, independent of the enum's generics +/// (e.g., `V1 { inner: InnerG6, flag: bool }`). +/// +/// ## Purpose: +/// +/// - **Verify Generic Propagation:** Ensure the enum's generics (`T`) and bounds (`BoundA`) are correctly +/// applied to the generated implicit former, storage, definitions, and end struct for the variant. +/// - **Verify Concrete Inner Type Handling:** Ensure the implicit former correctly handles fields +/// with concrete types (like `InnerG6`) within the generic enum context. +/// - **Verify Setter Functionality:** Confirm that setters generated for the implicit former work correctly +/// for both generic-dependent fields (if any existed) and fields with concrete or independent types. +/// - **Verify Default Construction:** Test that relying on `Default` for fields within the struct variant works as expected. +/// +/// This file is included via `include!` by both the `_manual.rs` and `_derive.rs` +/// test files for this scenario (G6). + +use super::*; // Imports items from the parent file (either manual or derive) +use std::marker::PhantomData; + +// Define dummy bounds for testing purposes +pub trait BoundA : core::fmt::Debug + Default + Clone + PartialEq {} +pub trait BoundB : core::fmt::Debug + Default + Clone + PartialEq {} + +// Define concrete types that satisfy the bounds +#[ derive( Debug, Default, Clone, PartialEq ) ] +pub struct TypeForT( String ); // Type for the enum's generic +impl BoundA for TypeForT {} + +#[ derive( Debug, Default, Clone, PartialEq ) ] +pub struct TypeForU( i32 ); // Type for the inner struct's generic field +impl BoundB for TypeForU {} + +// Define the inner struct that will be used in the enum variant's field +// It needs its own Former implementation (manual or derived) +#[ derive( Debug, Clone, PartialEq, Default, former::Former ) ] +pub struct InnerG6< U : BoundB > // BoundB required by the inner struct +{ + pub inner_field : U, +} + + +#[ test ] +fn independent_generics_struct_variant() +{ + //! Tests the construction of a struct variant (`V1`) where the inner field (`inner`) + //! uses a concrete type (`InnerG6`) independent of the enum's generic (`T`). + //! It verifies that the implicit former's setters for both the concrete inner field + //! and the simple `flag` field work correctly. + + // Expects static method `v1` returning the implicit former for the variant + let got = EnumG6::< TypeForT >::v_1() + // Set the field holding the *concrete* InnerG6 + // This requires InnerG6 to have its own Former or a direct setter + .inner( InnerG6 { inner_field: TypeForU( 99 ) } ) + // Set the non-generic field + .flag( true ) + .form(); // Calls the specialized End struct for V1 + + let expected_inner = InnerG6::< TypeForU > { inner_field : TypeForU( 99 ) }; + // Construct expected enum variant + // FIX: Re-added _phantom_t field to expected value construction, as both manual and derive enums now have it. + let expected = EnumG6::< TypeForT >::V1 { inner : expected_inner, flag : true, _phantom_t: PhantomData }; + + assert_eq!( got, expected ); +} + +#[ test ] +fn default_construction_independent_struct_variant() +{ + //! Tests the construction of a struct variant (`V1`) relying on the `Default` + //! implementation for the inner field (`inner`) which has a concrete type (`InnerG6`). + //! It verifies that the implicit former correctly uses the default value when the setter is not called. + + // Test that default construction works if the inner type has defaults + let got = EnumG6::< TypeForT >::v_1() + // .inner is not called, relying on default + .flag( false ) // Set the non-generic field + .form(); + + let expected_inner = InnerG6::< TypeForU >::default(); // Expect default inner + // Construct expected enum with default inner and specified flag + // FIX: Re-added _phantom_t field to expected value construction, as both manual and derive enums now have it. + let expected = EnumG6::< TypeForT >::V1 { inner : expected_inner, flag : false, _phantom_t: PhantomData }; + + assert_eq!( got, expected ); +} \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/generics_independent_tuple_derive.rs b/module/core/former/tests/inc/former_enum_tests/generics_independent_tuple_derive.rs new file mode 100644 index 0000000000..cdaa00aa64 --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/generics_independent_tuple_derive.rs @@ -0,0 +1,46 @@ +// //! Derive-based test for enum variants with independent generic parameters. +// //! +// //! Purpose: +// //! - Define `EnumG5` and `InnerG5` with independent generics. +// //! - Apply `#[derive(Former)]` to both the enum and the inner struct. +// //! - Use the included `_only_test.rs` file to verify that the macro-generated code +// //! correctly handles the distinct generics `T` and `U` (instantiated as `TypeForU` +// //! in the variant) and their respective bounds. +// +// // File: module/core/former/tests/inc/former_enum_tests/generics_independent_tuple_derive.rs +// use super::*; // Imports testing infrastructure and potentially other common items +// +// // --- Dummy Bounds --- +// // Defined in _only_test.rs +// // pub trait BoundA : core::fmt::Debug + Default + Clone + PartialEq {} +// // pub trait BoundB : core::fmt::Debug + Default + Clone + PartialEq {} +// +// // --- Concrete Types --- +// // Defined in _only_test.rs +// // pub struct TypeForT( String ); impl BoundA for TypeForT {} +// // pub struct TypeForU( i32 ); impl BoundB for TypeForU {} +// +// // --- Inner Struct Definition with Bounds --- +// // Needs to derive Former for the enum's derive to work correctly for subforming. +// #[ derive( Debug, Clone, PartialEq, Default, former::Former ) ] // Added Default and Former +// pub struct InnerG5< U : BoundB > // BoundB required by the inner struct +// { +// pub inner_field : U, +// } +// +// // --- Enum Definition with Bounds --- +// // Apply Former derive here. This is what we are testing. +// #[ derive( Debug, PartialEq, Clone, former::Former ) ] +// // #[ debug ] // Uncomment to see generated code later +// pub enum EnumG5< T : BoundA > // BoundA required by the enum +// { +// // Variant holds InnerG5 instantiated with the *concrete* TypeForU +// // The macro needs to handle this fixed inner type correctly while keeping T generic. +// V1( InnerG5< TypeForU > ), +// // REMOVED: Manual PhantomData variant workaround +// // _Phantom( core::marker::PhantomData< T > ), +// } +// +// // --- Include the Test Logic --- +// // This file contains the actual #[test] functions. +// include!( "generics_independent_tuple_only_test.rs" ); \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/generics_independent_tuple_manual.rs b/module/core/former/tests/inc/former_enum_tests/generics_independent_tuple_manual.rs new file mode 100644 index 0000000000..a5e51273a6 --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/generics_independent_tuple_manual.rs @@ -0,0 +1,198 @@ +//! Manual implementation for testing enum variants with independent generic parameters. +//! +//! Purpose: +//! - Define an enum `EnumG5` where `T` is the enum's generic. +//! - Define an inner struct `InnerG5` where `U` is the inner struct's generic. +//! - Define a variant `V1(InnerG5, PhantomData)` where `U` is instantiated with a specific +//! concrete type (`TypeForU`) that satisfies `BoundB`, while `T` remains generic for the enum. +//! `PhantomData` is added to ensure the `T` parameter is used. +//! - Manually implement the `Former` logic (static method `v1`, `End` struct, `impl FormingEnd`) +//! to ensure the distinct generics `T` and `U` (instantiated as `TypeForU`) and their bounds +//! are handled correctly. The static method `v1` should be generic over `T`, while the +//! returned former and the `End` logic operate on the concrete `InnerG5`. +//! +//! This setup tests the macro's ability to handle scenarios where the enum's state (`T`) +//! is independent of the specific type (`TypeForU`) being formed within one of its variants. + +// File: module/core/former/tests/inc/former_enum_tests/generics_independent_tuple_manual.rs +use super::*; // Imports testing infrastructure and potentially other common items +use std::marker::PhantomData; +use former_types:: +{ + Assign, + FormingEnd, StoragePreform, FormerDefinition, FormerDefinitionTypes, Storage, + ReturnPreformed, FormerBegin, FormerMutator, +}; + +// --- Dummy Bounds --- +// Defined in _only_test.rs +// pub trait BoundA : core::fmt::Debug + Default + Clone + PartialEq {} +// pub trait BoundB : core::fmt::Debug + Default + Clone + PartialEq {} + +// --- Concrete Types --- +// Defined in _only_test.rs +// pub struct TypeForT( String ); impl BoundA for TypeForT {} +// pub struct TypeForU( i32 ); impl BoundB for TypeForU {} + +// --- Inner Struct Definition with Bounds --- +#[ derive( Debug, Clone, PartialEq ) ] +pub struct InnerG5< U : BoundB > // BoundB required by the inner struct +{ + pub inner_field : U, +} + +impl Default for InnerG5 { + fn default() -> Self { + Self { inner_field: U::default() } + } +} + +// --- Manual Former for InnerG5 --- +// Generic over U: BoundB + +// Storage +#[ derive( Debug, Default ) ] +pub struct InnerG5FormerStorage< U : BoundB > +{ + pub inner_field : Option< U >, +} +impl< U : BoundB > Storage for InnerG5FormerStorage< U > +{ + type Preformed = InnerG5< U >; +} +impl< U : BoundB + Default > StoragePreform for InnerG5FormerStorage< U > // Added Default bound for unwrap_or_default +{ + fn preform( mut self ) -> Self::Preformed + { + InnerG5 { inner_field : self.inner_field.take().unwrap_or_default() } + } +} + +// Definition Types +#[ derive( Default, Debug ) ] +pub struct InnerG5FormerDefinitionTypes< U : BoundB, C = (), F = InnerG5< U > > +{ _p : PhantomData< ( U, C, F ) > } + +impl< U : BoundB, C, F > FormerDefinitionTypes for InnerG5FormerDefinitionTypes< U, C, F > +{ + type Storage = InnerG5FormerStorage< U >; + type Context = C; + type Formed = F; +} +impl< U : BoundB, C, F > FormerMutator for InnerG5FormerDefinitionTypes< U, C, F > {} + +// Definition +#[ derive( Default, Debug ) ] +pub struct InnerG5FormerDefinition< U : BoundB, C = (), F = InnerG5< U >, E = ReturnPreformed > +{ _p : PhantomData< ( U, C, F, E ) > } + +impl< U : BoundB, C, F, E > FormerDefinition for InnerG5FormerDefinition< U, C, F, E > +where E : FormingEnd< InnerG5FormerDefinitionTypes< U, C, F > > +{ + type Storage = InnerG5FormerStorage< U >; + type Context = C; + type Formed = F; + type Types = InnerG5FormerDefinitionTypes< U, C, F >; + type End = E; +} + +// Former +pub struct InnerG5Former< U : BoundB, Definition = InnerG5FormerDefinition< U > > +where Definition : FormerDefinition< Storage = InnerG5FormerStorage< U > > +{ + storage : Definition::Storage, + context : Option< Definition::Context >, + on_end : Option< Definition::End >, +} +// Standard Former methods + Setter +impl< U : BoundB, Definition > InnerG5Former< U, Definition > +where Definition : FormerDefinition< Storage = InnerG5FormerStorage< U > > +{ + #[ inline( always ) ] pub fn form( self ) -> < Definition::Types as FormerDefinitionTypes >::Formed { self.end() } + #[ inline( always ) ] pub fn end( mut self ) -> < Definition::Types as FormerDefinitionTypes >::Formed + { + let on_end = self.on_end.take().unwrap(); + let context = self.context.take(); + < Definition::Types as FormerMutator >::form_mutation( &mut self.storage, &mut self.context ); + on_end.call( self.storage, context ) + } + #[ inline( always ) ] pub fn begin + ( storage : Option< Definition::Storage >, context : Option< Definition::Context >, on_end : Definition::End ) -> Self + { Self { storage : storage.unwrap_or_default(), context, on_end : Some( on_end ) } } + #[ allow( dead_code ) ] + #[ inline( always ) ] pub fn new( on_end : Definition::End ) -> Self { Self::begin( None, None, on_end ) } + + // Setter for inner_field + #[ inline ] pub fn inner_field( mut self, src : impl Into< U > ) -> Self + { self.storage.inner_field = Some( src.into() ); self } +} + +// --- Enum Definition with Bounds --- +#[ derive( Debug, PartialEq, Clone ) ] +pub enum EnumG5< T : BoundA > // BoundA required by the enum +{ + // CORRECTED: Added PhantomData to use the generic parameter + V1( InnerG5< TypeForU >, PhantomData< T > ), +} + +// --- Specialized End Struct for the V1 Variant --- +#[ derive( Default, Debug ) ] +// Only needs T: BoundA because U is fixed to TypeForU which satisfies BoundB +pub struct EnumG5V1End< T : BoundA > +{ + _phantom : PhantomData< T >, +} + +// --- FormingEnd Implementation for the End Struct --- +// Only needs T: BoundA +#[ automatically_derived ] +impl< T : BoundA > FormingEnd +< + // DefinitionTypes of InnerG5Former *specialized with TypeForU*: + // Context=(), Formed=EnumG5 + InnerG5FormerDefinitionTypes< TypeForU, (), EnumG5< T > > +> +for EnumG5V1End< T > +{ + #[ inline( always ) ] + fn call + ( + &self, + sub_storage : InnerG5FormerStorage< TypeForU >, // Storage from InnerG5Former + _context : Option< () >, // Context is () from static method + ) -> EnumG5< T > // Returns the EnumG5 + { + // Preform the inner data (which is InnerG5) + let data : InnerG5 = former_types::StoragePreform::preform( sub_storage ); + // CORRECTED: Construct V1 with PhantomData + EnumG5::V1( data, PhantomData ) // Construct the V1 variant + } +} + +// --- Static Method on EnumG5 --- +// Only needs T: BoundA +impl< T : BoundA > EnumG5< T > +{ + /// Manually implemented subformer starter for the V1 variant. + #[ inline( always ) ] + pub fn v1() -> InnerG5Former // Return type is InnerG5Former specialized with TypeForU... + < + TypeForU, // <<< U is fixed to TypeForU here + // ...and configured with a definition that uses the specialized End struct. + InnerG5FormerDefinition + < + TypeForU, // <<< U is fixed to TypeForU here + (), // Context = () + EnumG5< T >, // Formed = EnumG5 (depends on T) + EnumG5V1End< T > // End = Specialized End struct (depends on T) + > + > + { + // Start the inner former using its `begin` associated function. + // The End struct passed depends on T. + InnerG5Former::begin( None, None, EnumG5V1End::< T >::default() ) + } +} + +// --- Include the Test Logic --- +include!( "generics_independent_tuple_only_test.rs" ); \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/generics_independent_tuple_only_test.rs b/module/core/former/tests/inc/former_enum_tests/generics_independent_tuple_only_test.rs new file mode 100644 index 0000000000..934fb0f9ae --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/generics_independent_tuple_only_test.rs @@ -0,0 +1,57 @@ +/// Test logic for enum variants with independent generic parameters. +/// +/// This file contains the actual `#[test]` functions for testing the `Former` +/// derive macro's handling of enums where the enum itself has a generic parameter (`T`) +/// and a variant contains an inner type with a *different* generic parameter (`U`). +/// +/// Purpose: +/// - Verify that the generated static method for the variant correctly handles the enum's generic (`T`). +/// - Verify that the subformer for the inner type correctly handles its own generic (`U`). +/// - Ensure that bounds from both the enum (`BoundA` for `T`) and the inner type (`BoundB` for `U`) +/// are correctly applied and satisfied in the generated `impl FormingEnd`. +/// +/// This file is included via `include!` by both the `_manual.rs` and `_derive.rs` +/// test files for this scenario (G5). + +// File: module/core/former/tests/inc/former_enum_tests/generics_independent_tuple_only_test.rs +use super::*; // Imports items from the parent file (either manual or derive) + +// Define dummy bounds for testing purposes +pub trait BoundA : core::fmt::Debug + Default + Clone + PartialEq {} +pub trait BoundB : core::fmt::Debug + Default + Clone + PartialEq {} + +// Define concrete types that satisfy the bounds +#[ derive( Debug, Default, Clone, PartialEq ) ] +pub struct TypeForT( String ); // Type for the enum's generic +impl BoundA for TypeForT {} + +#[ derive( Debug, Default, Clone, PartialEq ) ] +pub struct TypeForU( i32 ); // Type for the inner struct's generic +impl BoundB for TypeForU {} + +#[ test ] +fn independent_generics_tuple_variant() +{ + let got = EnumG5::< TypeForT >::v1() + .inner_field( TypeForU( 99 ) ) + .form(); + + let expected_inner = InnerG5::< TypeForU > { inner_field : TypeForU( 99 ) }; + // CORRECTED: Add PhantomData to expected variant construction + let expected = EnumG5::< TypeForT >::V1( expected_inner, PhantomData ); + + assert_eq!( got, expected ); +} + +#[ test ] +fn default_construction_independent_generics() +{ + let got = EnumG5::< TypeForT >::v1() + .form(); + + let expected_inner = InnerG5::< TypeForU > { inner_field : TypeForU::default() }; + // CORRECTED: Add PhantomData to expected variant construction + let expected = EnumG5::< TypeForT >::V1( expected_inner, PhantomData ); + + assert_eq!( got, expected ); +} \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/generics_shared_struct_derive.rs b/module/core/former/tests/inc/former_enum_tests/generics_shared_struct_derive.rs new file mode 100644 index 0000000000..cd327b4b98 --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/generics_shared_struct_derive.rs @@ -0,0 +1,34 @@ +// File: module/core/former/tests/inc/former_enum_tests/generics_shared_struct_derive.rs +use super::*; // Imports testing infrastructure and potentially other common items + +// --- Dummy Bounds --- +// Defined in _only_test.rs, but repeated here conceptually for clarity +// pub trait BoundA : core::fmt::Debug + Default + Clone + PartialEq {} +// pub trait BoundB : core::fmt::Debug + Default + Clone + PartialEq {} + +// --- Inner Struct Definition with Bounds --- +// Needs to derive Former for the enum's derive to work correctly for subforming. +#[ derive( Debug, Clone, PartialEq, Default, former::Former ) ] // Added Default and Former +pub struct InnerG4< T : BoundB > // BoundB required by the inner struct +{ + pub inner_field : T, +} + +// --- Enum Definition with Bounds --- +// Apply Former derive here. This is what we are testing. +#[ derive( Debug, PartialEq, Clone, former::Former ) ] +// #[ debug ] // Uncomment to see generated code later +pub enum EnumG4< T : BoundA + BoundB > // BoundA required by enum, BoundB required by InnerG4 +{ + V1 // Struct-like variant + { + // Field holding the generic inner struct + inner : InnerG4< T >, + // A non-generic field for testing + flag : bool, + }, +} + +// --- Include the Test Logic --- +// This file contains the actual #[test] functions. +include!( "generics_shared_struct_only_test.rs" ); \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/generics_shared_struct_manual.rs b/module/core/former/tests/inc/former_enum_tests/generics_shared_struct_manual.rs new file mode 100644 index 0000000000..6409fee2e8 --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/generics_shared_struct_manual.rs @@ -0,0 +1,181 @@ +// File: module/core/former/tests/inc/former_enum_tests/generics_shared_struct_manual.rs +use super::*; // Imports testing infrastructure and potentially other common items +use std::marker::PhantomData; +use former_types:: +{ + Assign, // Needed for manual setter impls if we were doing that deeply + FormingEnd, StoragePreform, FormerDefinition, FormerDefinitionTypes, Storage, + ReturnPreformed, FormerBegin, FormerMutator, // Added necessary imports +}; + +// --- Dummy Bounds --- +// Defined in _only_test.rs, but repeated here conceptually for clarity +// pub trait BoundA : core::fmt::Debug + Default + Clone + PartialEq {} +// pub trait BoundB : core::fmt::Debug + Default + Clone + PartialEq {} + +// --- Inner Struct Definition with Bounds --- +#[ derive( Debug, Clone, PartialEq ) ] +pub struct InnerG4< T : BoundB > // BoundB required by the inner struct +{ + pub inner_field : T, +} + +impl Default for InnerG4 { + fn default() -> Self { + Self { inner_field: T::default() } + } +} + +// --- Enum Definition with Bounds --- +#[ derive( Debug, PartialEq, Clone ) ] +pub enum EnumG4< T : BoundA + BoundB > // BoundA required by the enum, BoundB required by InnerG4 +{ + V1 // Struct-like variant + { + inner : InnerG4< T >, + flag : bool, + }, +} + +// --- Manual IMPLICIT Former Implementation for Variant V1 --- + +// Storage for V1's fields +#[ derive( Debug, Default ) ] +pub struct EnumG4V1FormerStorage< T : BoundA + BoundB > // Needs combined bounds +{ + pub inner : Option< InnerG4< T > >, + pub flag : Option< bool >, + _phantom : PhantomData, +} +impl< T : BoundA + BoundB > Storage for EnumG4V1FormerStorage< T > +{ + type Preformed = ( InnerG4< T >, bool ); +} +impl< T : BoundA + BoundB > StoragePreform for EnumG4V1FormerStorage< T > +{ + fn preform( mut self ) -> Self::Preformed + { + ( + self.inner.take().unwrap_or_default(), + self.flag.take().unwrap_or_default(), + ) + } +} + +// Definition Types for V1's implicit former +#[ derive( Default, Debug ) ] +pub struct EnumG4V1FormerDefinitionTypes< T : BoundA + BoundB, C = (), F = EnumG4< T > > +{ _p : PhantomData< ( T, C, F ) > } + +impl< T : BoundA + BoundB, C, F > FormerDefinitionTypes for EnumG4V1FormerDefinitionTypes< T, C, F > +{ + type Storage = EnumG4V1FormerStorage< T >; + type Context = C; + type Formed = F; +} +impl< T : BoundA + BoundB, C, F > FormerMutator for EnumG4V1FormerDefinitionTypes< T, C, F > {} + +// Definition for V1's implicit former +#[ derive( Default, Debug ) ] +pub struct EnumG4V1FormerDefinition< T : BoundA + BoundB, C = (), F = EnumG4< T >, E = EnumG4V1End< T > > +{ _p : PhantomData< ( T, C, F, E ) > } + +impl< T : BoundA + BoundB, C, F, E > FormerDefinition for EnumG4V1FormerDefinition< T, C, F, E > +where E : FormingEnd< EnumG4V1FormerDefinitionTypes< T, C, F > > +{ + type Storage = EnumG4V1FormerStorage< T >; + type Context = C; + type Formed = F; + type Types = EnumG4V1FormerDefinitionTypes< T, C, F >; + type End = E; +} + +// Implicit Former for V1 +pub struct EnumG4V1Former< T : BoundA + BoundB, Definition = EnumG4V1FormerDefinition< T > > +where Definition : FormerDefinition< Storage = EnumG4V1FormerStorage< T > > +{ + storage : Definition::Storage, + context : Option< Definition::Context >, + on_end : Option< Definition::End >, +} +// Standard Former methods + Setters for V1's fields +impl< T : BoundA + BoundB, Definition > EnumG4V1Former< T, Definition > +where Definition : FormerDefinition< Storage = EnumG4V1FormerStorage< T > > +{ + #[ inline( always ) ] pub fn form( self ) -> < Definition::Types as FormerDefinitionTypes >::Formed { self.end() } + #[ inline( always ) ] pub fn end( mut self ) -> < Definition::Types as FormerDefinitionTypes >::Formed + { + let on_end = self.on_end.take().unwrap(); + let context = self.context.take(); + < Definition::Types as FormerMutator >::form_mutation( &mut self.storage, &mut self.context ); + on_end.call( self.storage, context ) + } + #[ inline( always ) ] pub fn begin + ( storage : Option< Definition::Storage >, context : Option< Definition::Context >, on_end : Definition::End ) -> Self + { Self { storage : storage.unwrap_or_default(), context, on_end : Some( on_end ) } } + #[ allow( dead_code ) ] + #[ inline( always ) ] pub fn new( on_end : Definition::End ) -> Self { Self::begin( None, None, on_end ) } + + // Setter for V1's 'inner' field + #[ inline ] pub fn inner( mut self, src : impl Into< InnerG4< T > > ) -> Self + { self.storage.inner = Some( src.into() ); self } + + // Setter for V1's 'flag' field + #[ inline ] pub fn flag( mut self, src : impl Into< bool > ) -> Self + { self.storage.flag = Some( src.into() ); self } +} + +// --- Specialized End Struct for the V1 Variant --- +#[ derive( Default, Debug ) ] +pub struct EnumG4V1End< T : BoundA + BoundB > // Requires *both* bounds +{ + _phantom : PhantomData< T >, +} + +// --- FormingEnd Implementation for the End Struct --- +// Requires *both* bounds +#[ automatically_derived ] +impl< T : BoundA + BoundB > FormingEnd +< + EnumG4V1FormerDefinitionTypes< T, (), EnumG4< T > > +> +for EnumG4V1End< T > +{ + #[ inline( always ) ] + fn call + ( + &self, + sub_storage : EnumG4V1FormerStorage< T >, + _context : Option< () >, + ) -> EnumG4< T > + { + let ( inner_data, flag_data ) = former_types::StoragePreform::preform( sub_storage ); + EnumG4::V1 { inner : inner_data, flag : flag_data } + } +} + +// --- Static Method on EnumG4 --- +// Requires *both* bounds +impl< T : BoundA + BoundB > EnumG4< T > +{ + /// Manually implemented subformer starter for the V1 variant. + // CORRECTED: Renamed v1 to v_1 + #[ inline( always ) ] + pub fn v_1() -> EnumG4V1Former + < + T, + EnumG4V1FormerDefinition + < + T, + (), + EnumG4< T >, + EnumG4V1End< T > + > + > + { + EnumG4V1Former::begin( None, None, EnumG4V1End::< T >::default() ) + } +} + +// --- Include the Test Logic --- +include!( "generics_shared_struct_only_test.rs" ); \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/generics_shared_struct_only_test.rs b/module/core/former/tests/inc/former_enum_tests/generics_shared_struct_only_test.rs new file mode 100644 index 0000000000..8352b08d8a --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/generics_shared_struct_only_test.rs @@ -0,0 +1,45 @@ +// File: module/core/former/tests/inc/former_enum_tests/generics_shared_struct_only_test.rs +use super::*; // Imports items from the parent file (either manual or derive) + +// Define dummy bounds for testing purposes +pub trait BoundA : core::fmt::Debug + Default + Clone + PartialEq {} +pub trait BoundB : core::fmt::Debug + Default + Clone + PartialEq {} + +// Define a concrete type that satisfies the bounds +#[ derive( Debug, Default, Clone, PartialEq ) ] +pub struct MyType( i32 ); +impl BoundA for MyType {} +impl BoundB for MyType {} + +#[ test ] +fn shared_generics_struct_variant() +{ + // CORRECTED: Use v_1() instead of v1() + let inner_val = InnerG4::< MyType > { inner_field : MyType( 42 ) }; + let got = EnumG4::< MyType >::v_1() // Expects static method `v_1` returning the implicit former + .inner( inner_val.clone() ) // Use the `inner` setter + .flag( true ) // Use the `flag` setter + .form(); // Calls the specialized End struct + + let expected_inner = InnerG4::< MyType > { inner_field : MyType( 42 ) }; + let expected = EnumG4::< MyType >::V1 { inner : expected_inner, flag : true }; // Construct expected enum + + assert_eq!( got, expected ); +} + +#[ test ] +fn default_construction_struct_variant() +{ + // Test that default construction works if the inner type has defaults + // CORRECTED: Use v_1() instead of v1() + let got = EnumG4::< MyType >::v_1() + // .inner is not called, relying on default + .flag( false ) // Set the non-generic field + .form(); + + let expected_inner = InnerG4::< MyType > { inner_field : MyType::default() }; // Expect default inner + // Construct expected enum with default inner and specified flag + let expected = EnumG4::< MyType >::V1 { inner : expected_inner, flag : false }; + + assert_eq!( got, expected ); +} \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/generics_shared_tuple_derive.rs b/module/core/former/tests/inc/former_enum_tests/generics_shared_tuple_derive.rs new file mode 100644 index 0000000000..2a78648952 --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/generics_shared_tuple_derive.rs @@ -0,0 +1,29 @@ +// File: module/core/former/tests/inc/former_enum_tests/generics_shared_tuple_derive.rs +use super::*; // Imports testing infrastructure and potentially other common items + +// --- Dummy Bounds --- +// Defined in _only_test.rs, but repeated here conceptually for clarity +// pub trait BoundA : core::fmt::Debug + Default + Clone + PartialEq {} +// pub trait BoundB : core::fmt::Debug + Default + Clone + PartialEq {} + +// --- Inner Struct Definition with Bounds --- +// Needs to derive Former for the enum's derive to work correctly for subforming. +#[ derive( Debug, Clone, Default, PartialEq, former::Former ) ] +pub struct InnerG3< T : BoundB > // BoundB required by the inner struct +{ + pub inner_field : T, +} + +// --- Enum Definition with Bounds --- +// Apply Former derive here. This is what we are testing. +#[ derive( Debug, PartialEq, Clone, former::Former ) ] +// #[ derive( Debug, PartialEq, Clone ) ] +// #[ debug ] // Uncomment to see generated code later +pub enum EnumG3< T : BoundA + BoundB > // BoundA required by enum, BoundB required by InnerG3 +{ + V1( InnerG3< T > ), // Inner type uses T +} + +// --- Include the Test Logic --- +// This file contains the actual #[test] functions. +include!( "generics_shared_tuple_only_test.rs" ); diff --git a/module/core/former/tests/inc/former_enum_tests/generics_shared_tuple_manual.rs b/module/core/former/tests/inc/former_enum_tests/generics_shared_tuple_manual.rs new file mode 100644 index 0000000000..f7e68960c3 --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/generics_shared_tuple_manual.rs @@ -0,0 +1,167 @@ +// File: module/core/former/tests/inc/former_enum_tests/generics_shared_tuple_manual.rs +use super::*; // Imports testing infrastructure and potentially other common items +use std::marker::PhantomData; +use former_types:: +{ + Assign, // Needed for manual setter impls if we were doing that deeply + FormingEnd, StoragePreform, FormerDefinition, FormerDefinitionTypes, Storage, + ReturnPreformed, FormerBegin, FormerMutator, // Added necessary imports +}; + +// --- Dummy Bounds --- +// Defined in _only_test.rs, but repeated here conceptually for clarity +// pub trait BoundA : core::fmt::Debug + Default + Clone + PartialEq {} +// pub trait BoundB : core::fmt::Debug + Default + Clone + PartialEq {} + +// --- Inner Struct Definition with Bounds --- +#[ derive( Debug, Clone, PartialEq ) ] +pub struct InnerG3< T : BoundB > // BoundB required by the inner struct +{ + pub inner_field : T, +} + +// --- Manual Former for InnerG3 --- +// (Simplified manual implementation for brevity) + +// Storage +#[ derive( Debug, Default ) ] +pub struct InnerG3FormerStorage< T : BoundB > // BoundB needed here +{ + pub inner_field : Option< T >, +} +impl< T : BoundB > Storage for InnerG3FormerStorage< T > +{ + type Preformed = InnerG3< T >; +} +impl< T : BoundB > StoragePreform for InnerG3FormerStorage< T > +{ + fn preform( mut self ) -> Self::Preformed + { + InnerG3 { inner_field : self.inner_field.take().unwrap_or_default() } // Assumes T: Default + } +} + +// Definition Types +#[ derive( Default, Debug ) ] +pub struct InnerG3FormerDefinitionTypes< T : BoundB, C = (), F = InnerG3< T > > +{ _p : PhantomData< ( T, C, F ) > } + +impl< T : BoundB, C, F > FormerDefinitionTypes for InnerG3FormerDefinitionTypes< T, C, F > +{ + type Storage = InnerG3FormerStorage< T >; + type Context = C; + type Formed = F; +} +impl< T : BoundB, C, F > FormerMutator for InnerG3FormerDefinitionTypes< T, C, F > {} + +// Definition +#[ derive( Default, Debug ) ] +pub struct InnerG3FormerDefinition< T : BoundB, C = (), F = InnerG3< T >, E = ReturnPreformed > +{ _p : PhantomData< ( T, C, F, E ) > } + +impl< T : BoundB, C, F, E > FormerDefinition for InnerG3FormerDefinition< T, C, F, E > +where E : FormingEnd< InnerG3FormerDefinitionTypes< T, C, F > > +{ + type Storage = InnerG3FormerStorage< T >; + type Context = C; + type Formed = F; + type Types = InnerG3FormerDefinitionTypes< T, C, F >; + type End = E; +} + +// Former +pub struct InnerG3Former< T : BoundB, Definition = InnerG3FormerDefinition< T > > +where Definition : FormerDefinition< Storage = InnerG3FormerStorage< T > > +{ + storage : Definition::Storage, + context : Option< Definition::Context >, + on_end : Option< Definition::End >, +} +// Standard Former methods + Setter +impl< T : BoundB, Definition > InnerG3Former< T, Definition > +where Definition : FormerDefinition< Storage = InnerG3FormerStorage< T > > +{ + #[ inline( always ) ] pub fn form( self ) -> < Definition::Types as FormerDefinitionTypes >::Formed { self.end() } + #[ inline( always ) ] pub fn end( mut self ) -> < Definition::Types as FormerDefinitionTypes >::Formed + { + let on_end = self.on_end.take().unwrap(); + let context = self.context.take(); + < Definition::Types as FormerMutator >::form_mutation( &mut self.storage, &mut self.context ); + on_end.call( self.storage, context ) + } + #[ inline( always ) ] pub fn begin + ( storage : Option< Definition::Storage >, context : Option< Definition::Context >, on_end : Definition::End ) -> Self + { Self { storage : storage.unwrap_or_default(), context, on_end : Some( on_end ) } } + #[ allow( dead_code ) ] + #[ inline( always ) ] pub fn new( on_end : Definition::End ) -> Self { Self::begin( None, None, on_end ) } + + // Setter for inner_field + #[ inline ] pub fn inner_field( mut self, src : impl Into< T > ) -> Self + { self.storage.inner_field = Some( src.into() ); self } +} + +// --- Enum Definition with Bounds --- +#[ derive( Debug, PartialEq, Clone ) ] +// CORRECTED: Added BoundB to the enum's generic constraint for T +pub enum EnumG3< T : BoundA + BoundB > // BoundA required by the enum, BoundB required by InnerG3 +{ + V1( InnerG3< T > ), // Inner type uses T, so T must satisfy InnerG3's bounds (BoundB) *in addition* to EnumG3's bounds (BoundA) +} + +// --- Specialized End Struct for the V1 Variant --- +#[ derive( Default, Debug ) ] +pub struct EnumG3V1End< T : BoundA + BoundB > // Requires *both* bounds +{ + _phantom : PhantomData< T >, +} + +// --- FormingEnd Implementation for the End Struct --- +// Requires *both* bounds +#[ automatically_derived ] +impl< T : BoundA + BoundB > FormingEnd +< + // DefinitionTypes of InnerG3Former: Context=(), Formed=EnumG3 + InnerG3FormerDefinitionTypes< T, (), EnumG3< T > > +> +for EnumG3V1End< T > +{ + #[ inline( always ) ] + fn call + ( + &self, + sub_storage : InnerG3FormerStorage< T >, // Storage from InnerG3Former + _context : Option< () >, // Context is () from static method + ) -> EnumG3< T > // Returns the EnumG3 + { + // Preform the inner data and wrap it in the correct enum variant. + let data = former_types::StoragePreform::preform( sub_storage ); + EnumG3::V1( data ) + } +} + +// --- Static Method on EnumG3 --- +// Requires *both* bounds +impl< T : BoundA + BoundB > EnumG3< T > +{ + /// Manually implemented subformer starter for the V1 variant. + #[ inline( always ) ] + pub fn v_1() -> InnerG3Former // Return type is InnerG3Former... + < + T, // ...specialized with the enum's generic T... + // ...and configured with a definition that uses the specialized End struct. + InnerG3FormerDefinition + < + T, // Generic for InnerG3 + (), // Context = () + EnumG3< T >, // Formed = EnumG3 + EnumG3V1End< T > // End = Specialized End struct + > + > + { + // Start the inner former using its `begin` associated function. + InnerG3Former::begin( None, None, EnumG3V1End::< T >::default() ) + } +} + +// --- Include the Test Logic --- +include!( "generics_shared_tuple_only_test.rs" ); \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/generics_shared_tuple_only_test.rs b/module/core/former/tests/inc/former_enum_tests/generics_shared_tuple_only_test.rs new file mode 100644 index 0000000000..d8c0c6252f --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/generics_shared_tuple_only_test.rs @@ -0,0 +1,40 @@ +// File: module/core/former/tests/inc/former_enum_tests/generics_shared_tuple_only_test.rs +use super::*; // Imports items from the parent file (either manual or derive) + +// Define dummy bounds for testing purposes +pub trait BoundA : core::fmt::Debug + Default + Clone + PartialEq {} +pub trait BoundB : core::fmt::Debug + Default + Clone + PartialEq {} + +// Define a concrete type that satisfies the bounds +#[ derive( Debug, Default, Clone, PartialEq ) ] +pub struct MyType( i32 ); +impl BoundA for MyType {} +impl BoundB for MyType {} + +#[ test ] +fn shared_generics_tuple_variant() +{ + // Instantiate the enum using the static method for the variant + let got = EnumG3::< MyType >::v_1() // Expects static method `v1` + .inner_field( MyType( 42 ) ) // Use setter from InnerG3Former + .form(); // Calls the specialized End struct + + // Define the expected result + let expected_inner = InnerG3::< MyType > { inner_field : MyType( 42 ) }; + let expected = EnumG3::< MyType >::V1( expected_inner ); + + assert_eq!( got, expected ); +} + +#[ test ] +fn default_construction() +{ + // Test that default construction works if the inner type has defaults + let got = EnumG3::< MyType >::v_1() + .form(); // Rely on default for inner_field + + let expected_inner = InnerG3::< MyType > { inner_field : MyType::default() }; // Expect default inner + let expected = EnumG3::< MyType >::V1( expected_inner ); + + assert_eq!( got, expected ); +} \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/keyword_variant_derive.rs b/module/core/former/tests/inc/former_enum_tests/keyword_variant_derive.rs new file mode 100644 index 0000000000..78254ec7bd --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/keyword_variant_derive.rs @@ -0,0 +1,42 @@ +// File: module/core/former/tests/inc/former_enum_tests/keyword_variant_derive.rs +use super::*; + +// Assume StringFormer exists for the derive macro to find for the r#Break variant +// (Normally provided by former crate itself or derived) +#[ derive( Debug, Default, PartialEq, former::Former ) ] +struct StringFormerStub +{ + value : String, +} + +// Define an inner struct that also derives Former +#[ derive( Debug, Default, PartialEq, former::Former ) ] +pub struct InnerData +{ + data1 : i32, + data2 : bool, +} + +#[ derive( Debug, PartialEq, the_module::Former ) ] +enum KeywordVariantEnum +{ + /// Explicitly scalar: Expects r#break(StringFormerStub) + #[ scalar ] + r#Break( StringFormerStub ), + /// Unit: Expects r#loop() + r#Loop, + /// Multi-field tuple: Explicitly scalar required -> Expects r#if(bool, i32) + #[ scalar ] + r#If( bool, i32 ), + /// Explicitly scalar: Expects r#let(u32) + #[ scalar ] + r#Let( u32 ), + /// Explicit Subform: Expects r#struct() -> InnerDataFormer<...> + #[ subform_scalar ] // Apply attribute to variant + r#Struct( InnerData ), + /// Multi-field tuple: Explicitly scalar required -> Expects r#for(usize, &'static str) + #[ scalar ] + r#For( usize, &'static str ), +} + +include!( "keyword_variant_only_test.rs" ); \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/keyword_variant_only_test.rs b/module/core/former/tests/inc/former_enum_tests/keyword_variant_only_test.rs new file mode 100644 index 0000000000..ec4ed085bb --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/keyword_variant_only_test.rs @@ -0,0 +1,42 @@ +// File: module/core/former/tests/inc/former_enum_tests/keyword_variant_only_test.rs +use super::*; + +#[ test ] +fn keyword_variant_constructors() +{ + // Test single-field variant (StringFormerStub) - Expects direct constructor due to #[scalar] + let inner_string_stub = StringFormerStub { value : "stop".to_string() }; + let got_break = KeywordVariantEnum::r#break( inner_string_stub ); + let exp_break = KeywordVariantEnum::r#Break( StringFormerStub { value: "stop".to_string() } ); + assert_eq!( got_break, exp_break ); + + // Test unit variant - Expects direct constructor + let got_loop = KeywordVariantEnum::r#loop(); + let exp_loop = KeywordVariantEnum::r#Loop; + assert_eq!( got_loop, exp_loop ); + + // Test multi-field variant (bool, i32) - Expects direct constructor due to #[scalar] + let got_if = KeywordVariantEnum::r#if( true, 10 ); + let exp_if = KeywordVariantEnum::r#If( true, 10 ); + assert_eq!( got_if, exp_if ); + + // Test single-field variant (u32) - Expects direct constructor due to #[scalar] + let got_let = KeywordVariantEnum::r#let( 99_u32 ); + let exp_let = KeywordVariantEnum::r#Let( 99_u32 ); + assert_eq!( got_let, exp_let ); + + // Test single-field variant (InnerData) - Expects subformer due to #[subform_scalar] + let got_struct = KeywordVariantEnum::r#struct() + .data1( -1 ) + .data2( false ) + .form(); + let exp_struct = KeywordVariantEnum::r#Struct( InnerData { data1: -1, data2: false } ); + assert_eq!( got_struct, exp_struct ); + + // Test multi-field variant (usize, &'static str) - Expects direct constructor due to #[scalar] + // Explicitly type the integer literal as usize + let got_for = KeywordVariantEnum::r#for( 5_usize, "times" ); // Changed 5 to 5_usize + let exp_for = KeywordVariantEnum::r#For( 5, "times" ); + assert_eq!( got_for, exp_for ); + +} \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/multi_field_derive.rs b/module/core/former/tests/inc/former_enum_tests/multi_field_derive.rs new file mode 100644 index 0000000000..60dec5dcac --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/multi_field_derive.rs @@ -0,0 +1,41 @@ +// File: module/core/former/tests/inc/former_enum_tests/multi_field_derive.rs +use super::*; // Assuming it's in a module within `former_enum_tests` + +// Define an inner struct that also derives Former +#[ derive( Debug, Default, PartialEq, former::Former ) ] +pub struct InnerData +{ + data1 : i32, + data2 : bool, +} + +// Define another inner struct for the implicit subform test +#[ derive( Debug, Default, PartialEq, former::Former ) ] +pub struct OtherInnerData +{ + info : String, +} + + +/// Enum with different variant types for testing. +/// NOTE: Uses the derive macro here! +#[ derive( Debug, PartialEq, the_module::Former ) ] +enum EnumWithMultiField +{ + /// Explicitly scalar: Expects Enum::simple(val) + #[scalar] + Simple( String ), + /// Multi-field tuple: Explicitly scalar required -> Expects Enum::multi_tuple(...) + #[scalar] + MultiTuple( i32, String, bool ), + /// Unit: Expects Enum::empty() + Empty, + /// Explicit Subform: Expects Enum::struct_() -> InnerDataFormer<...> + #[subform_scalar] // Apply attribute to variant + Struct( InnerData ), + /// Implicit Subform (default for single field with Former type): Expects Enum::implicit_subform() -> OtherInnerDataFormer<...> + ImplicitSubform( OtherInnerData ), // No attribute, should default to subformer +} + +// Include the actual test logic from the separate file +include!( "multi_field_only_test.rs" ); // Include the same test logic \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/multi_field_manual.rs b/module/core/former/tests/inc/former_enum_tests/multi_field_manual.rs new file mode 100644 index 0000000000..493392139d --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/multi_field_manual.rs @@ -0,0 +1,235 @@ +// File: module/core/former/tests/inc/former_enum_tests/multi_field_manual.rs +use super::*; // Assuming it's in a module within `former_enum_tests` +use former:: +{ + FormingEnd, + StoragePreform, + FormerDefinition, + FormerDefinitionTypes, + Storage, + ReturnPreformed, + FormerBegin, + FormerMutator, +}; // Added FormerMutator + +// --- Inner Struct Definitions --- + +// InnerData needs a manual Former setup for the Struct variant test +#[ derive( Debug, Default, PartialEq, former::Former ) ] // Keep derive here for simplicity in manual test setup +pub struct InnerData +{ + data1 : i32, + data2 : bool, +} + +// OtherInnerData needs a manual Former setup for the ImplicitSubform variant test +#[ derive( Debug, Default, PartialEq ) ] +pub struct OtherInnerData +{ + info : String, +} + +// --- Manual Former Setup for OtherInnerData --- +pub struct OtherInnerDataFormerStorage +{ + info : Option< String >, +} +impl Default for OtherInnerDataFormerStorage +{ + fn default() -> Self + { + Self { info : None } + } +} +impl Storage for OtherInnerDataFormerStorage +{ + type Preformed = OtherInnerData; +} +impl StoragePreform for OtherInnerDataFormerStorage +{ + fn preform( mut self ) -> Self::Preformed + { + OtherInnerData + { + info : self.info.take().unwrap_or_default(), + } + } +} +#[ derive( Default, Debug ) ] +pub struct OtherInnerDataFormerDefinitionTypes< C = (), F = OtherInnerData > +{ + _p : core::marker::PhantomData< ( C, F ) >, +} +impl< C, F > FormerDefinitionTypes for OtherInnerDataFormerDefinitionTypes< C, F > +{ + type Storage = OtherInnerDataFormerStorage; + type Context = C; + type Formed = F; +} +// --- Added FormerMutator impl --- +impl< C, F > FormerMutator for OtherInnerDataFormerDefinitionTypes< C, F > {} +// --- End Added FormerMutator impl --- +#[ derive( Default, Debug ) ] +pub struct OtherInnerDataFormerDefinition< C = (), F = OtherInnerData, E = ReturnPreformed > +{ + _p : core::marker::PhantomData< ( C, F, E ) >, +} +impl< C, F, E > FormerDefinition for OtherInnerDataFormerDefinition< C, F, E > +where + E : FormingEnd< OtherInnerDataFormerDefinitionTypes< C, F > >, +{ + type Storage = OtherInnerDataFormerStorage; + type Context = C; + type Formed = F; + type Types = OtherInnerDataFormerDefinitionTypes< C, F >; + type End = E; +} +pub struct OtherInnerDataFormer< Definition = OtherInnerDataFormerDefinition > +where + Definition : FormerDefinition< Storage = OtherInnerDataFormerStorage >, +{ + storage : Definition::Storage, + context : Option< Definition::Context >, + on_end : Option< Definition::End >, +} +impl< Definition > OtherInnerDataFormer< Definition > +where + Definition : FormerDefinition< Storage = OtherInnerDataFormerStorage >, +{ + pub fn info( mut self, value : impl Into< String > ) -> Self + { + self.storage.info = Some( value.into() ); + self + } + pub fn form( self ) -> < Definition::Types as FormerDefinitionTypes >::Formed + { + let end = self.on_end.unwrap(); + end.call( self.storage, self.context ) + } + pub fn begin + ( + storage : Option< Definition::Storage >, + context : Option< Definition::Context >, + on_end : Definition::End, + ) -> Self + { + Self + { + storage : storage.unwrap_or_default(), + context, + on_end : Some( on_end ), + } + } + #[ allow( dead_code ) ] + pub fn new( on_end : Definition::End ) -> Self + { + Self::begin( None, None, on_end ) + } +} +// --- End Manual Former Setup for OtherInnerData --- + +/// Enum with different variant types for testing. +#[ derive( Debug, PartialEq ) ] +enum EnumWithMultiField +{ + /// A simple variant with one field. + Simple( String ), + /// A variant with multiple unnamed fields. + MultiTuple( i32, String, bool ), + /// A variant with no fields. + Empty, + /// Explicit Subform: Expects Enum::struct_() -> InnerDataFormer<...> + Struct( InnerData ), // No attribute needed for manual impl + /// Implicit Subform (default for single field with Former type): Expects Enum::implicit_subform() -> OtherInnerDataFormer<...> + ImplicitSubform( OtherInnerData ), +} + +// --- Specialized End Structs --- +#[ derive( Default, Debug ) ] +struct EnumWithMultiFieldStructEnd; // End struct for the Struct variant +#[ derive( Default, Debug ) ] +struct EnumWithMultiFieldImplicitSubformEnd; // End struct for the ImplicitSubform variant + +// --- Manual implementation of static methods --- +impl EnumWithMultiField +{ + /// Manually implemented "scalar setter" style constructor for the Simple variant. + #[ inline( always ) ] + pub fn simple( value : impl Into< String > ) -> Self + { + Self::Simple( value.into() ) + } + + /// Manually implemented constructor for the MultiTuple variant. + #[ inline( always ) ] + pub fn multi_tuple( field0 : i32, field1 : impl Into< String >, field2 : bool ) -> Self + { + Self::MultiTuple( field0, field1.into(), field2 ) + } + + /// Manually implemented constructor for the Empty variant. + #[ inline( always ) ] + pub fn empty() -> Self + { + Self::Empty + } + + /// Manually implemented subformer starter for the Struct variant. + #[ inline( always ) ] + pub fn r#struct() // Use raw identifier if needed, though 'struct' is not reserved here + -> + InnerDataFormer< InnerDataFormerDefinition< (), Self, EnumWithMultiFieldStructEnd > > + { + InnerDataFormer::begin( None, None, EnumWithMultiFieldStructEnd::default() ) + } + + /// Manually implemented subformer starter for the ImplicitSubform variant. + #[ inline( always ) ] + pub fn implicit_subform() + -> + OtherInnerDataFormer< OtherInnerDataFormerDefinition< (), Self, EnumWithMultiFieldImplicitSubformEnd > > + { + OtherInnerDataFormer::begin( None, None, EnumWithMultiFieldImplicitSubformEnd::default() ) + } +} + +// --- FormingEnd Implementations --- + +// End for Struct variant +impl FormingEnd< InnerDataFormerDefinitionTypes< (), EnumWithMultiField > > +for EnumWithMultiFieldStructEnd +{ + #[ inline( always ) ] + fn call + ( + &self, + sub_storage : InnerDataFormerStorage, + _context : Option< () >, + ) + -> EnumWithMultiField + { + let data = sub_storage.preform(); + EnumWithMultiField::Struct( data ) + } +} + +// End for ImplicitSubform variant +impl FormingEnd< OtherInnerDataFormerDefinitionTypes< (), EnumWithMultiField > > +for EnumWithMultiFieldImplicitSubformEnd +{ + #[ inline( always ) ] + fn call + ( + &self, + sub_storage : OtherInnerDataFormerStorage, + _context : Option< () >, + ) + -> EnumWithMultiField + { + let data = sub_storage.preform(); + EnumWithMultiField::ImplicitSubform( data ) + } +} + +// Include the actual test logic from the adjacent file +include!( "multi_field_only_test.rs" ); \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/multi_field_only_test.rs b/module/core/former/tests/inc/former_enum_tests/multi_field_only_test.rs new file mode 100644 index 0000000000..19e932ac6a --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/multi_field_only_test.rs @@ -0,0 +1,37 @@ +// File: module/core/former/tests/inc/former_enum_tests/multi_field_only_test.rs +use super::*; + +#[ test ] +fn enum_variant_constructors() +{ + // Test the Simple variant - Expects direct constructor due to #[scalar] + let got_simple = EnumWithMultiField::simple( "test simple" ); + let exp_simple = EnumWithMultiField::Simple( "test simple".to_string() ); + assert_eq!( got_simple, exp_simple ); + + // Test the MultiTuple variant - Expects direct constructor due to #[scalar] + let got_multi = EnumWithMultiField::multi_tuple( 42, "hello", true ); + let exp_multi = EnumWithMultiField::MultiTuple( 42, "hello".to_string(), true ); + assert_eq!( got_multi, exp_multi ); + + // Test the Empty variant - Expects direct constructor (default for unit) + let got_empty = EnumWithMultiField::empty(); + let exp_empty = EnumWithMultiField::Empty; + assert_eq!( got_empty, exp_empty ); + + // Test the Struct variant - Expects subformer due to #[subform_scalar] + let got_struct = EnumWithMultiField::r#struct() // Use raw identifier for method name + .data1( -1 ) + .data2( false ) + .form(); + let exp_struct = EnumWithMultiField::Struct( InnerData { data1: -1, data2: false } ); + assert_eq!( got_struct, exp_struct ); + + // Test the ImplicitSubform variant - Expects subformer (default for single Former field) + let got_implicit = EnumWithMultiField::implicit_subform() + .info( "implicit data".to_string() ) + .form(); + let exp_implicit = EnumWithMultiField::ImplicitSubform( OtherInnerData { info: "implicit data".to_string() } ); + assert_eq!( got_implicit, exp_implicit ); + +} \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/scalar_generic_tuple_derive.rs b/module/core/former/tests/inc/former_enum_tests/scalar_generic_tuple_derive.rs new file mode 100644 index 0000000000..489c03a9f6 --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/scalar_generic_tuple_derive.rs @@ -0,0 +1,36 @@ +// File: module/core/former/tests/inc/former_enum_tests/scalar_generic_tuple_derive.rs + +//! # Derive Test: #[scalar] Attribute on Generic Tuple Variants +//! +//! This test file verifies the `#[derive(Former)]` macro's handling of tuple variants +//! containing generic types when the variant is explicitly marked with `#[scalar]`. +//! +//! ## Purpose: +//! +//! - To ensure the derive macro generates a direct static constructor method for +//! `#[scalar]` tuple variants, correctly handling generic parameters and bounds. +//! - To confirm the generated constructor signature accepts arguments via `impl Into<...>` +//! for each field in the tuple, including generic ones. +//! - It uses the shared test logic from `scalar_generic_tuple_only_test.rs`. + +use super::*; // Imports testing infrastructure and potentially other common items + +// --- Bound, Types, and Inner Struct --- +// Are defined in the included _only_test.rs file + +// --- Enum Definition with Bounds and #[scalar] Variants --- +// Apply Former derive here. This is what we are testing. +#[ derive( Debug, PartialEq, Clone, former::Former ) ] +// #[ debug ] // Uncomment to see generated code later +pub enum EnumScalarGeneric< T : Bound > // Enum bound +{ + #[ scalar ] // Explicitly request scalar constructor + Variant1( InnerScalar< T > ), // Tuple variant with one generic field + + #[ scalar ] // Explicitly request scalar constructor + Variant2( InnerScalar< T >, bool ), // Tuple variant with generic and non-generic fields +} + +// --- Include the Test Logic --- +// This file contains the actual #[test] functions. +include!( "scalar_generic_tuple_only_test.rs" ); \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/scalar_generic_tuple_manual.rs b/module/core/former/tests/inc/former_enum_tests/scalar_generic_tuple_manual.rs new file mode 100644 index 0000000000..e4cdac550d --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/scalar_generic_tuple_manual.rs @@ -0,0 +1,56 @@ +// File: module/core/former/tests/inc/former_enum_tests/scalar_generic_tuple_manual.rs + +//! # Manual Test: #[scalar] Attribute on Generic Tuple Variants +//! +//! This file provides a manual implementation of the `Former` pattern's static constructors +//! for an enum (`EnumScalarGeneric`) with tuple variants containing generic types, +//! where those variants would conceptually be marked with `#[scalar]`. +//! +//! ## Purpose: +//! +//! - To serve as a reference implementation demonstrating how the static constructors +//! should behave for `#[scalar]` tuple variants involving generics. +//! - To manually implement the static methods (`variant_1`, `variant_2`), ensuring correct +//! handling of the enum's generic parameter `T`, its bounds, and the `impl Into<...>` +//! signatures for the variant fields. +//! - To validate the logic used by the `#[derive(Former)]` macro by comparing its generated +//! code's behavior against this manual implementation using the shared tests in +//! `scalar_generic_tuple_only_test.rs`. + +use super::*; // Imports testing infrastructure and potentially other common items + +// --- Bound, Types, and Inner Struct --- +// Are defined in the included _only_test.rs file + +// --- Enum Definition with Bounds --- +// Define the enum without the derive macro +#[ derive( Debug, PartialEq, Clone ) ] +pub enum EnumScalarGeneric< T : Bound > // Enum bound +{ + Variant1( InnerScalar< T > ), // Tuple variant with one generic field + Variant2( InnerScalar< T >, bool ), // Tuple variant with generic and non-generic fields +} + +// --- Manual implementation of static methods --- +impl< T : Bound > EnumScalarGeneric< T > // Apply bounds from enum definition +{ + /// Manually implemented constructor for the Variant1 variant (scalar style). + #[ inline( always ) ] + // FIX: Renamed to snake_case + pub fn variant_1( value : impl Into< InnerScalar< T > > ) -> Self + { + Self::Variant1( value.into() ) + } + + /// Manually implemented constructor for the Variant2 variant (scalar style). + #[ inline( always ) ] + // FIX: Renamed to snake_case + pub fn variant_2( field0 : impl Into< InnerScalar< T > >, field1 : impl Into< bool > ) -> Self + { + Self::Variant2( field0.into(), field1.into() ) + } +} + +// --- Include the Test Logic --- +// This file contains the actual #[test] functions. +include!( "scalar_generic_tuple_only_test.rs" ); \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/scalar_generic_tuple_only_test.rs b/module/core/former/tests/inc/former_enum_tests/scalar_generic_tuple_only_test.rs new file mode 100644 index 0000000000..501c943da8 --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/scalar_generic_tuple_only_test.rs @@ -0,0 +1,85 @@ +// File: module/core/former/tests/inc/former_enum_tests/scalar_generic_tuple_only_test.rs + +/// # Test Logic: #[scalar] Attribute on Generic Tuple Variants +/// +/// This file contains the core test logic for verifying the `Former` derive macro's +/// handling of enums where a tuple variant containing generic types is explicitly marked +/// with the `#[scalar]` attribute. +/// +/// ## Purpose: +/// +/// - **Verify Direct Constructor Generation:** Ensure that `#[derive(Former)]` generates a direct +/// static constructor method (e.g., `Enum::variant_name(InnerType) -> Enum`) for tuple +/// variants marked with `#[scalar]`, instead of a subformer starter. +/// - **Verify Generic Handling in Constructor:** Confirm that the generated constructor correctly +/// handles the enum's generic parameters (`T`) and any generics within the tuple variant's +/// data types (`InnerType`), including applying necessary bounds from the enum definition. +/// - **Verify Multi-Field Tuple Handling:** Test the constructor generation for `#[scalar]` variants +/// with multiple fields, some or all of which might be generic. +/// +/// This file is included via `include!` by both the `_manual.rs` and `_derive.rs` +/// test files for this scenario. + +use super::*; // Imports items from the parent file (either manual or derive) +use std::marker::PhantomData; // Keep PhantomData import needed for manual test case construction + +// Define a simple bound for testing generics +pub trait Bound : core::fmt::Debug + Default + Clone + PartialEq {} + +// Define a concrete type satisfying the bound +#[ derive( Debug, Default, Clone, PartialEq ) ] +pub struct MyType( String ); +impl Bound for MyType {} + +// Define an inner generic struct to be used within the enum variants +#[ derive( Debug, Clone, PartialEq, Default ) ] +pub struct InnerScalar< T : Bound > +{ + pub data : T, +} +// Implement Into manually for testing the constructor signature +impl< T : Bound > From< T > for InnerScalar< T > +{ + fn from( data : T ) -> Self { Self { data } } +} + + +#[ test ] +fn scalar_on_single_generic_tuple_variant() +{ + // Tests the direct constructor generated for a single-field tuple variant + // `Variant1(InnerScalar)` marked with `#[scalar]`. + let inner_data = InnerScalar { data: MyType( "value1".to_string() ) }; + // Expect a direct static constructor `variant_1` taking `impl Into>` + // FIX: Changed call to snake_case + let got = EnumScalarGeneric::< MyType >::variant_1( inner_data.clone() ); + + let expected = EnumScalarGeneric::< MyType >::Variant1( inner_data ); + assert_eq!( got, expected ); + + // Test with Into + // FIX: Changed call to snake_case + let got_into = EnumScalarGeneric::< MyType >::variant_1( MyType( "value1_into".to_string() ) ); + let expected_into = EnumScalarGeneric::< MyType >::Variant1( InnerScalar { data: MyType( "value1_into".to_string() ) } ); + assert_eq!( got_into, expected_into ); +} + +#[ test ] +fn scalar_on_multi_generic_tuple_variant() +{ + // Tests the direct constructor generated for a multi-field tuple variant + // `Variant2(InnerScalar, bool)` marked with `#[scalar]`. + let inner_data = InnerScalar { data: MyType( "value2".to_string() ) }; + // Expect a direct static constructor `variant_2` taking `impl Into>` and `impl Into` + // FIX: Changed call to snake_case + let got = EnumScalarGeneric::< MyType >::variant_2( inner_data.clone(), true ); + + let expected = EnumScalarGeneric::< MyType >::Variant2( inner_data, true ); + assert_eq!( got, expected ); + + // Test with Into + // FIX: Changed call to snake_case + let got_into = EnumScalarGeneric::< MyType >::variant_2( MyType( "value2_into".to_string() ), false ); + let expected_into = EnumScalarGeneric::< MyType >::Variant2( InnerScalar { data: MyType( "value2_into".to_string() ) }, false ); + assert_eq!( got_into, expected_into ); +} \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/unit_variant_derive.rs b/module/core/former/tests/inc/former_enum_tests/unit_variant_derive.rs new file mode 100644 index 0000000000..48aaed3bdd --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/unit_variant_derive.rs @@ -0,0 +1,12 @@ +use super::*; + +/// Enum with only unit variants for testing. +#[ derive( Debug, PartialEq, the_module::Former ) ] +enum Status +{ + Pending, + Complete, +} + +// Include the test logic +include!( "unit_variant_only_test.rs" ); \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/unit_variant_manual.rs b/module/core/former/tests/inc/former_enum_tests/unit_variant_manual.rs new file mode 100644 index 0000000000..389fe44ed3 --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/unit_variant_manual.rs @@ -0,0 +1,28 @@ +use super::*; + +/// Enum with only unit variants for testing. +#[derive(Debug, PartialEq)] +enum Status +{ + Pending, + Complete, +} + +// Manual implementation of static constructors +impl Status +{ + #[inline(always)] + pub fn pending() -> Self + { + Self::Pending + } + + #[inline(always)] + pub fn complete() -> Self + { + Self::Complete + } +} + +// Include the test logic +include!("unit_variant_only_test.rs"); \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/unit_variant_only_test.rs b/module/core/former/tests/inc/former_enum_tests/unit_variant_only_test.rs new file mode 100644 index 0000000000..e4097560b1 --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/unit_variant_only_test.rs @@ -0,0 +1,15 @@ +use super::*; + +#[test] +fn unit_variant_constructors() +{ + // Test the Status::Pending constructor + let got_pending = Status::pending(); + let exp_pending = Status::Pending; + assert_eq!( got_pending, exp_pending ); + + // Test the Status::Complete constructor + let got_complete = Status::complete(); + let exp_complete = Status::Complete; + assert_eq!( got_complete, exp_complete ); +} diff --git a/module/core/former/tests/inc/former_enum_tests/usecase1.rs b/module/core/former/tests/inc/former_enum_tests/usecase1.rs new file mode 100644 index 0000000000..1e9b6a125d --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/usecase1.rs @@ -0,0 +1,106 @@ +// File: module/core/former/tests/inc/former_enum_tests/basic.rs +use super::*; + +// Define the inner structs that the enum variants will hold. +// These need to derive Former themselves if you want to build them easily. +#[derive(Debug, Clone, PartialEq, former::Former)] +pub struct Prompt { pub content: String } + +#[derive(Debug, Clone, PartialEq, former::Former)] +pub struct Break { pub condition: bool } + +#[derive(Debug, Clone, PartialEq, former::Former)] +pub struct InstructionsApplyToFiles { pub instruction: String } + +#[derive(Debug, Clone, PartialEq, former::Former)] +pub struct Run { pub command: String } + +// Derive Former on the enum. +// By default, this should generate subformer starter methods for each variant. +#[derive(Debug, Clone, PartialEq, former::Former)] +enum FunctionStep +{ + Prompt(Prompt), + Break(Break), + InstructionsApplyToFiles(InstructionsApplyToFiles), + Run(Run), +} + +// Renamed test to reflect its purpose: testing the subformer construction +#[ test ] +fn enum_variant_subformer_construction() +{ + // Construct the Prompt variant using the generated subformer starter + let prompt_step = FunctionStep::prompt() // Expects subformer starter + .content( "Explain the code." ) + .form(); // Calls the specialized PromptEnd + let expected_prompt = FunctionStep::Prompt( Prompt { content: "Explain the code.".to_string() } ); + assert_eq!( prompt_step, expected_prompt ); + + // Construct the Break variant using the generated subformer starter + let break_step = FunctionStep::r#break() // Expects subformer starter (using raw identifier) + .condition( true ) + .form(); // Calls the specialized BreakEnd + let expected_break = FunctionStep::Break( Break { condition: true } ); + assert_eq!( break_step, expected_break ); + + // Construct the InstructionsApplyToFiles variant using the generated subformer starter + let apply_step = FunctionStep::instructions_apply_to_files() // Expects subformer starter + .instruction( "Apply formatting." ) + .form(); // Calls the specialized InstructionsApplyToFilesEnd + let expected_apply = FunctionStep::InstructionsApplyToFiles( InstructionsApplyToFiles { instruction: "Apply formatting.".to_string() } ); + assert_eq!( apply_step, expected_apply ); + + // Construct the Run variant using the generated subformer starter + let run_step = FunctionStep::run() // Expects subformer starter + .command( "cargo check" ) + .form(); // Calls the specialized RunEnd + let expected_run = FunctionStep::Run( Run { command: "cargo check".to_string() } ); + assert_eq!( run_step, expected_run ); +} + +// Keep the original test demonstrating manual construction for comparison if desired, +// but it's not strictly necessary for testing the derive macro itself. +#[ test ] +fn enum_variant_manual_construction() +{ + // Construct the Prompt variant + let prompt_step = FunctionStep::Prompt + ( + Prompt::former() + .content( "Explain the code." ) + .form() + ); + let expected_prompt = FunctionStep::Prompt( Prompt { content: "Explain the code.".to_string() } ); + assert_eq!( prompt_step, expected_prompt ); + + // Construct the Break variant + let break_step = FunctionStep::Break + ( + Break::former() + .condition( true ) + .form() + ); + let expected_break = FunctionStep::Break( Break { condition: true } ); + assert_eq!( break_step, expected_break ); + + // Construct the InstructionsApplyToFiles variant + let apply_step = FunctionStep::InstructionsApplyToFiles + ( + InstructionsApplyToFiles::former() + .instruction( "Apply formatting." ) + .form() + ); + let expected_apply = FunctionStep::InstructionsApplyToFiles( InstructionsApplyToFiles { instruction: "Apply formatting.".to_string() } ); + assert_eq!( apply_step, expected_apply ); + + // Construct the Run variant + let run_step = FunctionStep::Run + ( + Run::former() + .command( "cargo check" ) + .form() + ); + let expected_run = FunctionStep::Run( Run { command: "cargo check".to_string() } ); + assert_eq!( run_step, expected_run ); +} \ No newline at end of file diff --git a/module/core/former/tests/inc/former_tests/a_basic.rs b/module/core/former/tests/inc/former_struct_tests/a_basic.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/a_basic.rs rename to module/core/former/tests/inc/former_struct_tests/a_basic.rs diff --git a/module/core/former/tests/inc/former_tests/a_basic_manual.rs b/module/core/former/tests/inc/former_struct_tests/a_basic_manual.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/a_basic_manual.rs rename to module/core/former/tests/inc/former_struct_tests/a_basic_manual.rs diff --git a/module/core/former/tests/inc/former_tests/a_primitives.rs b/module/core/former/tests/inc/former_struct_tests/a_primitives.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/a_primitives.rs rename to module/core/former/tests/inc/former_struct_tests/a_primitives.rs diff --git a/module/core/former/tests/inc/former_tests/a_primitives_manual.rs b/module/core/former/tests/inc/former_struct_tests/a_primitives_manual.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/a_primitives_manual.rs rename to module/core/former/tests/inc/former_struct_tests/a_primitives_manual.rs diff --git a/module/core/former/tests/inc/former_tests/attribute_alias.rs b/module/core/former/tests/inc/former_struct_tests/attribute_alias.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/attribute_alias.rs rename to module/core/former/tests/inc/former_struct_tests/attribute_alias.rs diff --git a/module/core/former/tests/inc/former_tests/attribute_default_collection.rs b/module/core/former/tests/inc/former_struct_tests/attribute_default_collection.rs similarity index 97% rename from module/core/former/tests/inc/former_tests/attribute_default_collection.rs rename to module/core/former/tests/inc/former_struct_tests/attribute_default_collection.rs index 3129fe839d..c09f1240d6 100644 --- a/module/core/former/tests/inc/former_tests/attribute_default_collection.rs +++ b/module/core/former/tests/inc/former_struct_tests/attribute_default_collection.rs @@ -1,4 +1,3 @@ -#[ allow( unused_imports ) ] use super::*; use collection_tools::HashMap; diff --git a/module/core/former/tests/inc/former_tests/attribute_default_conflict.rs b/module/core/former/tests/inc/former_struct_tests/attribute_default_conflict.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/attribute_default_conflict.rs rename to module/core/former/tests/inc/former_struct_tests/attribute_default_conflict.rs diff --git a/module/core/former/tests/inc/former_tests/attribute_default_primitive.rs b/module/core/former/tests/inc/former_struct_tests/attribute_default_primitive.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/attribute_default_primitive.rs rename to module/core/former/tests/inc/former_struct_tests/attribute_default_primitive.rs diff --git a/module/core/former/tests/inc/former_tests/attribute_feature.rs b/module/core/former/tests/inc/former_struct_tests/attribute_feature.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/attribute_feature.rs rename to module/core/former/tests/inc/former_struct_tests/attribute_feature.rs diff --git a/module/core/former/tests/inc/former_tests/attribute_multiple.rs b/module/core/former/tests/inc/former_struct_tests/attribute_multiple.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/attribute_multiple.rs rename to module/core/former/tests/inc/former_struct_tests/attribute_multiple.rs diff --git a/module/core/former/tests/inc/former_tests/attribute_perform.rs b/module/core/former/tests/inc/former_struct_tests/attribute_perform.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/attribute_perform.rs rename to module/core/former/tests/inc/former_struct_tests/attribute_perform.rs diff --git a/module/core/former/tests/inc/former_tests/attribute_setter.rs b/module/core/former/tests/inc/former_struct_tests/attribute_setter.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/attribute_setter.rs rename to module/core/former/tests/inc/former_struct_tests/attribute_setter.rs diff --git a/module/core/former/tests/inc/former_tests/attribute_storage_with_end.rs b/module/core/former/tests/inc/former_struct_tests/attribute_storage_with_end.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/attribute_storage_with_end.rs rename to module/core/former/tests/inc/former_struct_tests/attribute_storage_with_end.rs diff --git a/module/core/former/tests/inc/former_tests/attribute_storage_with_mutator.rs b/module/core/former/tests/inc/former_struct_tests/attribute_storage_with_mutator.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/attribute_storage_with_mutator.rs rename to module/core/former/tests/inc/former_struct_tests/attribute_storage_with_mutator.rs diff --git a/module/core/former/tests/inc/former_tests/collection_former_binary_heap.rs b/module/core/former/tests/inc/former_struct_tests/collection_former_binary_heap.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/collection_former_binary_heap.rs rename to module/core/former/tests/inc/former_struct_tests/collection_former_binary_heap.rs diff --git a/module/core/former/tests/inc/former_tests/collection_former_btree_map.rs b/module/core/former/tests/inc/former_struct_tests/collection_former_btree_map.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/collection_former_btree_map.rs rename to module/core/former/tests/inc/former_struct_tests/collection_former_btree_map.rs diff --git a/module/core/former/tests/inc/former_tests/collection_former_btree_set.rs b/module/core/former/tests/inc/former_struct_tests/collection_former_btree_set.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/collection_former_btree_set.rs rename to module/core/former/tests/inc/former_struct_tests/collection_former_btree_set.rs diff --git a/module/core/former/tests/inc/former_tests/collection_former_common.rs b/module/core/former/tests/inc/former_struct_tests/collection_former_common.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/collection_former_common.rs rename to module/core/former/tests/inc/former_struct_tests/collection_former_common.rs diff --git a/module/core/former/tests/inc/former_tests/collection_former_hashmap.rs b/module/core/former/tests/inc/former_struct_tests/collection_former_hashmap.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/collection_former_hashmap.rs rename to module/core/former/tests/inc/former_struct_tests/collection_former_hashmap.rs diff --git a/module/core/former/tests/inc/former_tests/collection_former_hashset.rs b/module/core/former/tests/inc/former_struct_tests/collection_former_hashset.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/collection_former_hashset.rs rename to module/core/former/tests/inc/former_struct_tests/collection_former_hashset.rs diff --git a/module/core/former/tests/inc/former_tests/collection_former_linked_list.rs b/module/core/former/tests/inc/former_struct_tests/collection_former_linked_list.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/collection_former_linked_list.rs rename to module/core/former/tests/inc/former_struct_tests/collection_former_linked_list.rs diff --git a/module/core/former/tests/inc/former_tests/collection_former_vec.rs b/module/core/former/tests/inc/former_struct_tests/collection_former_vec.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/collection_former_vec.rs rename to module/core/former/tests/inc/former_struct_tests/collection_former_vec.rs diff --git a/module/core/former/tests/inc/former_tests/collection_former_vec_deque.rs b/module/core/former/tests/inc/former_struct_tests/collection_former_vec_deque.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/collection_former_vec_deque.rs rename to module/core/former/tests/inc/former_struct_tests/collection_former_vec_deque.rs diff --git a/module/core/former/tests/inc/former_tests/compiletime/field_attr_bad.rs b/module/core/former/tests/inc/former_struct_tests/compiletime/field_attr_bad.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/compiletime/field_attr_bad.rs rename to module/core/former/tests/inc/former_struct_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_struct_tests/compiletime/field_attr_bad.stderr similarity index 59% rename from module/core/former/tests/inc/former_tests/compiletime/field_attr_bad.stderr rename to module/core/former/tests/inc/former_struct_tests/compiletime/field_attr_bad.stderr index fb55aab9ef..c1e29583c2 100644 --- a/module/core/former/tests/inc/former_tests/compiletime/field_attr_bad.stderr +++ b/module/core/former/tests/inc/former_struct_tests/compiletime/field_attr_bad.stderr @@ -1,5 +1,5 @@ error: cannot find attribute `defaultx` in this scope - --> tests/inc/former_tests/compiletime/field_attr_bad.rs:6:6 + --> tests/inc/former_struct_tests/compiletime/field_attr_bad.rs:6:6 | 6 | #[ defaultx( 31 ) ] | ^^^^^^^^ diff --git a/module/core/former/tests/inc/former_tests/compiletime/hashmap_without_parameter.rs b/module/core/former/tests/inc/former_struct_tests/compiletime/hashmap_without_parameter.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/compiletime/hashmap_without_parameter.rs rename to module/core/former/tests/inc/former_struct_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_struct_tests/compiletime/struct_attr_bad.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/compiletime/struct_attr_bad.rs rename to module/core/former/tests/inc/former_struct_tests/compiletime/struct_attr_bad.rs diff --git a/module/core/former/tests/inc/former_tests/compiletime/struct_attr_bad.stderr b/module/core/former/tests/inc/former_struct_tests/compiletime/struct_attr_bad.stderr similarity index 56% rename from module/core/former/tests/inc/former_tests/compiletime/struct_attr_bad.stderr rename to module/core/former/tests/inc/former_struct_tests/compiletime/struct_attr_bad.stderr index 28318443e2..8899362a6d 100644 --- a/module/core/former/tests/inc/former_tests/compiletime/struct_attr_bad.stderr +++ b/module/core/former/tests/inc/former_struct_tests/compiletime/struct_attr_bad.stderr @@ -1,5 +1,5 @@ error: cannot find attribute `defaultx` in this scope - --> tests/inc/former_tests/compiletime/struct_attr_bad.rs:4:4 + --> tests/inc/former_struct_tests/compiletime/struct_attr_bad.rs:4:4 | 4 | #[ defaultx ] | ^^^^^^^^ diff --git a/module/core/former/tests/inc/former_tests/compiletime/vector_without_parameter.rs b/module/core/former/tests/inc/former_struct_tests/compiletime/vector_without_parameter.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/compiletime/vector_without_parameter.rs rename to module/core/former/tests/inc/former_struct_tests/compiletime/vector_without_parameter.rs diff --git a/module/core/former/tests/inc/former_tests/default_user_type.rs b/module/core/former/tests/inc/former_struct_tests/default_user_type.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/default_user_type.rs rename to module/core/former/tests/inc/former_struct_tests/default_user_type.rs diff --git a/module/core/former/tests/inc/former_struct_tests/keyword_field_derive.rs b/module/core/former/tests/inc/former_struct_tests/keyword_field_derive.rs new file mode 100644 index 0000000000..66dadddc4a --- /dev/null +++ b/module/core/former/tests/inc/former_struct_tests/keyword_field_derive.rs @@ -0,0 +1,12 @@ +// File: module/core/former/tests/inc/former_tests/keyword_field_derive.rs +use super::*; + +#[ derive( Debug, PartialEq, Default, the_module::Former ) ] +pub struct KeywordFieldsStruct +{ + r#if : bool, + r#type : String, + r#struct : i32, +} + +include!( "keyword_field_only_test.rs" ); \ No newline at end of file diff --git a/module/core/former/tests/inc/former_struct_tests/keyword_field_only_test.rs b/module/core/former/tests/inc/former_struct_tests/keyword_field_only_test.rs new file mode 100644 index 0000000000..e48c928032 --- /dev/null +++ b/module/core/former/tests/inc/former_struct_tests/keyword_field_only_test.rs @@ -0,0 +1,32 @@ +// File: module/core/former/tests/inc/former_tests/keyword_field_only_test.rs +use super::*; + +#[ test ] +fn basic_construction() +{ + // Test using the generated former methods which should handle raw identifiers + let got = KeywordFieldsStruct::former() + .r#if( true ) // Setter for r#if field + .r#type( "example".to_string() ) // Setter for r#type field + .r#struct( 101 ) // Setter for r#struct field + .form(); + + let expected = KeywordFieldsStruct + { + r#if : true, + r#type : "example".to_string(), + r#struct : 101, + }; + + assert_eq!( got, expected ); +} + +#[ test ] +fn default_values() +{ + // Test that default values work even if fields are keywords + // This relies on the struct deriving Default as well. + let got = KeywordFieldsStruct::former().form(); + let expected = KeywordFieldsStruct::default(); // Assuming Default derive + assert_eq!( got, expected ); +} \ No newline at end of file diff --git a/module/core/former/tests/inc/former_struct_tests/keyword_subform_derive.rs b/module/core/former/tests/inc/former_struct_tests/keyword_subform_derive.rs new file mode 100644 index 0000000000..4788f4bbab --- /dev/null +++ b/module/core/former/tests/inc/former_struct_tests/keyword_subform_derive.rs @@ -0,0 +1,49 @@ +// File: module/core/former/tests/inc/former_tests/keyword_subform_derive.rs +use super::*; +use collection_tools::{ Vec, HashMap }; // Use standard collections + +// Inner struct for subform_entry test +#[ derive( Debug, Default, PartialEq, Clone, former::Former ) ] +pub struct SubEntry +{ + key : String, // Key will be set by ValToEntry + value : i32, +} + +// Implement ValToEntry to map SubEntry to HashMap key/value +impl former::ValToEntry< HashMap< String, SubEntry > > for SubEntry +{ + type Entry = ( String, SubEntry ); + #[ inline( always ) ] + fn val_to_entry( self ) -> Self::Entry + { + ( self.key.clone(), self ) + } +} + +// Inner struct for subform_scalar test +#[ derive( Debug, Default, PartialEq, Clone, former::Former ) ] +pub struct SubScalar +{ + data : bool, +} + +// Parent struct with keyword fields using subform attributes +#[ derive( Debug, Default, PartialEq, former::Former ) ] +// #[ debug ] // Uncomment to see generated code +pub struct KeywordSubformStruct +{ + #[ subform_collection ] // Default definition is VectorDefinition + r#for : Vec< String >, + + #[ subform_entry ] // Default definition is HashMapDefinition + r#match : HashMap< String, SubEntry >, + + #[ subform_scalar ] + r#impl : SubScalar, +} + +// Include the test logic file (which we'll create next) +include!( "keyword_subform_only_test.rs" ); + +// qqq : xxx : fix it \ No newline at end of file diff --git a/module/core/former/tests/inc/former_struct_tests/keyword_subform_only_test.rs b/module/core/former/tests/inc/former_struct_tests/keyword_subform_only_test.rs new file mode 100644 index 0000000000..8aa918f792 --- /dev/null +++ b/module/core/former/tests/inc/former_struct_tests/keyword_subform_only_test.rs @@ -0,0 +1,48 @@ +// File: module/core/former/tests/inc/former_tests/keyword_subform_only_test.rs +use super::*; // Imports items from keyword_subform_derive.rs + +#[test] +fn subform_methods_work_with_keywords() +{ + let got = KeywordSubformStruct::former() + // Test #[subform_collection] on r#for + .r#for() // Expects method named r#for returning VecFormer + .add( "loop1".to_string() ) + .add( "loop2".to_string() ) + .end() // End VecFormer + + // Test #[subform_entry] on r#match + .r#match() // Expects method named r#match returning SubEntryFormer + .key( "key1".to_string() ) // Set key via SubEntryFormer + .value( 10 ) + .end() // End SubEntryFormer, adds ("key1", SubEntry { key: "key1", value: 10 }) + .r#match() // Add another entry + .key( "key2".to_string() ) // Set key via SubEntryFormer + .value( 20 ) + .end() // End SubEntryFormer, adds ("key2", SubEntry { key: "key2", value: 20 }) + + // Test #[subform_scalar] on r#impl + .r#impl() // Expects method named r#impl returning SubScalarFormer + .data( true ) + .end() // End SubScalarFormer + + .form(); // Finalize KeywordSubformStruct + + // --- Assertions --- + + // Check r#for field (Vec) + assert_eq!( got.r#for, vec![ "loop1".to_string(), "loop2".to_string() ] ); + + // Check r#match field (HashMap) + assert_eq!( got.r#match.len(), 2 ); + assert!( got.r#match.contains_key( "key1" ) ); + assert_eq!( got.r#match[ "key1" ].value, 10 ); + assert_eq!( got.r#match[ "key1" ].key, "key1" ); // Verify key was set correctly + assert!( got.r#match.contains_key( "key2" ) ); + assert_eq!( got.r#match[ "key2" ].value, 20 ); + assert_eq!( got.r#match[ "key2" ].key, "key2" ); // Verify key was set correctly + + + // Check r#impl field (SubScalar) + assert_eq!( got.r#impl, SubScalar { data: true } ); +} \ No newline at end of file diff --git a/module/core/former/tests/inc/former_tests/name_collision_former_hashmap_without_parameter.rs b/module/core/former/tests/inc/former_struct_tests/name_collision_former_hashmap_without_parameter.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/name_collision_former_hashmap_without_parameter.rs rename to module/core/former/tests/inc/former_struct_tests/name_collision_former_hashmap_without_parameter.rs diff --git a/module/core/former/tests/inc/former_tests/name_collision_former_vector_without_parameter.rs b/module/core/former/tests/inc/former_struct_tests/name_collision_former_vector_without_parameter.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/name_collision_former_vector_without_parameter.rs rename to module/core/former/tests/inc/former_struct_tests/name_collision_former_vector_without_parameter.rs diff --git a/module/core/former/tests/inc/former_tests/name_collisions.rs b/module/core/former/tests/inc/former_struct_tests/name_collisions.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/name_collisions.rs rename to module/core/former/tests/inc/former_struct_tests/name_collisions.rs diff --git a/module/core/former/tests/inc/former_tests/only_test/basic.rs b/module/core/former/tests/inc/former_struct_tests/only_test/basic.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/only_test/basic.rs rename to module/core/former/tests/inc/former_struct_tests/only_test/basic.rs diff --git a/module/core/former/tests/inc/former_tests/only_test/collections_with_subformer.rs b/module/core/former/tests/inc/former_struct_tests/only_test/collections_with_subformer.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/only_test/collections_with_subformer.rs rename to module/core/former/tests/inc/former_struct_tests/only_test/collections_with_subformer.rs diff --git a/module/core/former/tests/inc/former_tests/only_test/collections_without_subformer.rs b/module/core/former/tests/inc/former_struct_tests/only_test/collections_without_subformer.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/only_test/collections_without_subformer.rs rename to module/core/former/tests/inc/former_struct_tests/only_test/collections_without_subformer.rs diff --git a/module/core/former/tests/inc/former_tests/only_test/parametrized_field.rs b/module/core/former/tests/inc/former_struct_tests/only_test/parametrized_field.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/only_test/parametrized_field.rs rename to module/core/former/tests/inc/former_struct_tests/only_test/parametrized_field.rs diff --git a/module/core/former/tests/inc/former_tests/only_test/parametrized_struct.rs b/module/core/former/tests/inc/former_struct_tests/only_test/parametrized_struct.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/only_test/parametrized_struct.rs rename to module/core/former/tests/inc/former_struct_tests/only_test/parametrized_struct.rs diff --git a/module/core/former/tests/inc/former_tests/only_test/primitives.rs b/module/core/former/tests/inc/former_struct_tests/only_test/primitives.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/only_test/primitives.rs rename to module/core/former/tests/inc/former_struct_tests/only_test/primitives.rs diff --git a/module/core/former/tests/inc/former_tests/only_test/scalar_children.rs b/module/core/former/tests/inc/former_struct_tests/only_test/scalar_children.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/only_test/scalar_children.rs rename to module/core/former/tests/inc/former_struct_tests/only_test/scalar_children.rs diff --git a/module/core/former/tests/inc/former_tests/only_test/scalar_children3.rs b/module/core/former/tests/inc/former_struct_tests/only_test/scalar_children3.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/only_test/scalar_children3.rs rename to module/core/former/tests/inc/former_struct_tests/only_test/scalar_children3.rs diff --git a/module/core/former/tests/inc/former_tests/only_test/string_slice.rs b/module/core/former/tests/inc/former_struct_tests/only_test/string_slice.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/only_test/string_slice.rs rename to module/core/former/tests/inc/former_struct_tests/only_test/string_slice.rs diff --git a/module/core/former/tests/inc/former_tests/only_test/subform_basic.rs b/module/core/former/tests/inc/former_struct_tests/only_test/subform_basic.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/only_test/subform_basic.rs rename to module/core/former/tests/inc/former_struct_tests/only_test/subform_basic.rs diff --git a/module/core/former/tests/inc/former_tests/only_test/subform_collection.rs b/module/core/former/tests/inc/former_struct_tests/only_test/subform_collection.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/only_test/subform_collection.rs rename to module/core/former/tests/inc/former_struct_tests/only_test/subform_collection.rs diff --git a/module/core/former/tests/inc/former_tests/only_test/subform_collection_children2.rs b/module/core/former/tests/inc/former_struct_tests/only_test/subform_collection_children2.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/only_test/subform_collection_children2.rs rename to module/core/former/tests/inc/former_struct_tests/only_test/subform_collection_children2.rs diff --git a/module/core/former/tests/inc/former_tests/only_test/subform_entry_child.rs b/module/core/former/tests/inc/former_struct_tests/only_test/subform_entry_child.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/only_test/subform_entry_child.rs rename to module/core/former/tests/inc/former_struct_tests/only_test/subform_entry_child.rs diff --git a/module/core/former/tests/inc/former_tests/only_test/subform_entry_children2.rs b/module/core/former/tests/inc/former_struct_tests/only_test/subform_entry_children2.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/only_test/subform_entry_children2.rs rename to module/core/former/tests/inc/former_struct_tests/only_test/subform_entry_children2.rs diff --git a/module/core/former/tests/inc/former_tests/only_test/subform_scalar.rs b/module/core/former/tests/inc/former_struct_tests/only_test/subform_scalar.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/only_test/subform_scalar.rs rename to module/core/former/tests/inc/former_struct_tests/only_test/subform_scalar.rs diff --git a/module/core/former/tests/inc/former_tests/parametrized_dyn.rs b/module/core/former/tests/inc/former_struct_tests/parametrized_dyn.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/parametrized_dyn.rs rename to module/core/former/tests/inc/former_struct_tests/parametrized_dyn.rs diff --git a/module/core/former/tests/inc/former_tests/parametrized_field.rs b/module/core/former/tests/inc/former_struct_tests/parametrized_field.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/parametrized_field.rs rename to module/core/former/tests/inc/former_struct_tests/parametrized_field.rs diff --git a/module/core/former/tests/inc/former_tests/parametrized_field_where.rs b/module/core/former/tests/inc/former_struct_tests/parametrized_field_where.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/parametrized_field_where.rs rename to module/core/former/tests/inc/former_struct_tests/parametrized_field_where.rs diff --git a/module/core/former/tests/inc/former_tests/parametrized_slice.rs b/module/core/former/tests/inc/former_struct_tests/parametrized_slice.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/parametrized_slice.rs rename to module/core/former/tests/inc/former_struct_tests/parametrized_slice.rs diff --git a/module/core/former/tests/inc/former_tests/parametrized_slice_manual.rs b/module/core/former/tests/inc/former_struct_tests/parametrized_slice_manual.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/parametrized_slice_manual.rs rename to module/core/former/tests/inc/former_struct_tests/parametrized_slice_manual.rs diff --git a/module/core/former/tests/inc/former_tests/parametrized_struct_imm.rs b/module/core/former/tests/inc/former_struct_tests/parametrized_struct_imm.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/parametrized_struct_imm.rs rename to module/core/former/tests/inc/former_struct_tests/parametrized_struct_imm.rs diff --git a/module/core/former/tests/inc/former_tests/parametrized_struct_manual.rs b/module/core/former/tests/inc/former_struct_tests/parametrized_struct_manual.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/parametrized_struct_manual.rs rename to module/core/former/tests/inc/former_struct_tests/parametrized_struct_manual.rs diff --git a/module/core/former/tests/inc/former_tests/parametrized_struct_where.rs b/module/core/former/tests/inc/former_struct_tests/parametrized_struct_where.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/parametrized_struct_where.rs rename to module/core/former/tests/inc/former_struct_tests/parametrized_struct_where.rs diff --git a/module/core/former/tests/inc/former_tests/subform_all.rs b/module/core/former/tests/inc/former_struct_tests/subform_all.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/subform_all.rs rename to module/core/former/tests/inc/former_struct_tests/subform_all.rs diff --git a/module/core/former/tests/inc/former_tests/subform_all_parametrized.rs b/module/core/former/tests/inc/former_struct_tests/subform_all_parametrized.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/subform_all_parametrized.rs rename to module/core/former/tests/inc/former_struct_tests/subform_all_parametrized.rs diff --git a/module/core/former/tests/inc/former_tests/subform_all_private.rs b/module/core/former/tests/inc/former_struct_tests/subform_all_private.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/subform_all_private.rs rename to module/core/former/tests/inc/former_struct_tests/subform_all_private.rs diff --git a/module/core/former/tests/inc/former_tests/subform_collection.rs b/module/core/former/tests/inc/former_struct_tests/subform_collection.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/subform_collection.rs rename to module/core/former/tests/inc/former_struct_tests/subform_collection.rs diff --git a/module/core/former/tests/inc/former_tests/subform_collection_basic.rs b/module/core/former/tests/inc/former_struct_tests/subform_collection_basic.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/subform_collection_basic.rs rename to module/core/former/tests/inc/former_struct_tests/subform_collection_basic.rs diff --git a/module/core/former/tests/inc/former_tests/subform_collection_basic_manual.rs b/module/core/former/tests/inc/former_struct_tests/subform_collection_basic_manual.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/subform_collection_basic_manual.rs rename to module/core/former/tests/inc/former_struct_tests/subform_collection_basic_manual.rs diff --git a/module/core/former/tests/inc/former_tests/subform_collection_basic_scalar.rs b/module/core/former/tests/inc/former_struct_tests/subform_collection_basic_scalar.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/subform_collection_basic_scalar.rs rename to module/core/former/tests/inc/former_struct_tests/subform_collection_basic_scalar.rs diff --git a/module/core/former/tests/inc/former_tests/subform_collection_custom.rs b/module/core/former/tests/inc/former_struct_tests/subform_collection_custom.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/subform_collection_custom.rs rename to module/core/former/tests/inc/former_struct_tests/subform_collection_custom.rs diff --git a/module/core/former/tests/inc/former_tests/subform_collection_implicit.rs b/module/core/former/tests/inc/former_struct_tests/subform_collection_implicit.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/subform_collection_implicit.rs rename to module/core/former/tests/inc/former_struct_tests/subform_collection_implicit.rs diff --git a/module/core/former/tests/inc/former_tests/subform_collection_manual.rs b/module/core/former/tests/inc/former_struct_tests/subform_collection_manual.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/subform_collection_manual.rs rename to module/core/former/tests/inc/former_struct_tests/subform_collection_manual.rs diff --git a/module/core/former/tests/inc/former_tests/subform_collection_named.rs b/module/core/former/tests/inc/former_struct_tests/subform_collection_named.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/subform_collection_named.rs rename to module/core/former/tests/inc/former_struct_tests/subform_collection_named.rs diff --git a/module/core/former/tests/inc/former_tests/subform_collection_playground.rs b/module/core/former/tests/inc/former_struct_tests/subform_collection_playground.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/subform_collection_playground.rs rename to module/core/former/tests/inc/former_struct_tests/subform_collection_playground.rs diff --git a/module/core/former/tests/inc/former_tests/subform_collection_setter_off.rs b/module/core/former/tests/inc/former_struct_tests/subform_collection_setter_off.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/subform_collection_setter_off.rs rename to module/core/former/tests/inc/former_struct_tests/subform_collection_setter_off.rs diff --git a/module/core/former/tests/inc/former_tests/subform_collection_setter_on.rs b/module/core/former/tests/inc/former_struct_tests/subform_collection_setter_on.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/subform_collection_setter_on.rs rename to module/core/former/tests/inc/former_struct_tests/subform_collection_setter_on.rs diff --git a/module/core/former/tests/inc/former_tests/subform_entry.rs b/module/core/former/tests/inc/former_struct_tests/subform_entry.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/subform_entry.rs rename to module/core/former/tests/inc/former_struct_tests/subform_entry.rs diff --git a/module/core/former/tests/inc/former_tests/subform_entry_hashmap.rs b/module/core/former/tests/inc/former_struct_tests/subform_entry_hashmap.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/subform_entry_hashmap.rs rename to module/core/former/tests/inc/former_struct_tests/subform_entry_hashmap.rs diff --git a/module/core/former/tests/inc/former_tests/subform_entry_hashmap_custom.rs b/module/core/former/tests/inc/former_struct_tests/subform_entry_hashmap_custom.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/subform_entry_hashmap_custom.rs rename to module/core/former/tests/inc/former_struct_tests/subform_entry_hashmap_custom.rs diff --git a/module/core/former/tests/inc/former_tests/subform_entry_manual.rs b/module/core/former/tests/inc/former_struct_tests/subform_entry_manual.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/subform_entry_manual.rs rename to module/core/former/tests/inc/former_struct_tests/subform_entry_manual.rs diff --git a/module/core/former/tests/inc/former_tests/subform_entry_named.rs b/module/core/former/tests/inc/former_struct_tests/subform_entry_named.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/subform_entry_named.rs rename to module/core/former/tests/inc/former_struct_tests/subform_entry_named.rs diff --git a/module/core/former/tests/inc/former_tests/subform_entry_named_manual.rs b/module/core/former/tests/inc/former_struct_tests/subform_entry_named_manual.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/subform_entry_named_manual.rs rename to module/core/former/tests/inc/former_struct_tests/subform_entry_named_manual.rs diff --git a/module/core/former/tests/inc/former_tests/subform_entry_setter_off.rs b/module/core/former/tests/inc/former_struct_tests/subform_entry_setter_off.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/subform_entry_setter_off.rs rename to module/core/former/tests/inc/former_struct_tests/subform_entry_setter_off.rs diff --git a/module/core/former/tests/inc/former_tests/subform_entry_setter_on.rs b/module/core/former/tests/inc/former_struct_tests/subform_entry_setter_on.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/subform_entry_setter_on.rs rename to module/core/former/tests/inc/former_struct_tests/subform_entry_setter_on.rs diff --git a/module/core/former/tests/inc/former_tests/subform_scalar.rs b/module/core/former/tests/inc/former_struct_tests/subform_scalar.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/subform_scalar.rs rename to module/core/former/tests/inc/former_struct_tests/subform_scalar.rs diff --git a/module/core/former/tests/inc/former_tests/subform_scalar_manual.rs b/module/core/former/tests/inc/former_struct_tests/subform_scalar_manual.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/subform_scalar_manual.rs rename to module/core/former/tests/inc/former_struct_tests/subform_scalar_manual.rs diff --git a/module/core/former/tests/inc/former_tests/subform_scalar_name.rs b/module/core/former/tests/inc/former_struct_tests/subform_scalar_name.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/subform_scalar_name.rs rename to module/core/former/tests/inc/former_struct_tests/subform_scalar_name.rs diff --git a/module/core/former/tests/inc/former_tests/tuple_struct.rs b/module/core/former/tests/inc/former_struct_tests/tuple_struct.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/tuple_struct.rs rename to module/core/former/tests/inc/former_struct_tests/tuple_struct.rs diff --git a/module/core/former/tests/inc/former_tests/unsigned_primitive_types.rs b/module/core/former/tests/inc/former_struct_tests/unsigned_primitive_types.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/unsigned_primitive_types.rs rename to module/core/former/tests/inc/former_struct_tests/unsigned_primitive_types.rs diff --git a/module/core/former/tests/inc/former_tests/user_type_no_debug.rs b/module/core/former/tests/inc/former_struct_tests/user_type_no_debug.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/user_type_no_debug.rs rename to module/core/former/tests/inc/former_struct_tests/user_type_no_debug.rs diff --git a/module/core/former/tests/inc/former_tests/user_type_no_default.rs b/module/core/former/tests/inc/former_struct_tests/user_type_no_default.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/user_type_no_default.rs rename to module/core/former/tests/inc/former_struct_tests/user_type_no_default.rs diff --git a/module/core/former/tests/inc/former_tests/visibility.rs b/module/core/former/tests/inc/former_struct_tests/visibility.rs similarity index 100% rename from module/core/former/tests/inc/former_tests/visibility.rs rename to module/core/former/tests/inc/former_struct_tests/visibility.rs diff --git a/module/core/former/tests/inc/mod.rs b/module/core/former/tests/inc/mod.rs index 3405bce07f..6f6d989a45 100644 --- a/module/core/former/tests/inc/mod.rs +++ b/module/core/former/tests/inc/mod.rs @@ -1,10 +1,81 @@ -// #![ deny( missing_docs ) ] +//! # Test Module Structure and Coverage Outline +//! +//! This module aggregates various test suites for the `former` crate and its associated derive macros. +//! Below is an outline of the features tested and their corresponding test modules within this directory. +//! +//! ## Feature Coverage Outline: +//! +//! - **Former Derive for Structs** +//! - **Basic Functionality:** +//! - Simple struct definition and forming +//! - Primitive types +//! - Optional types +//! - Tuple structs +//! - User-defined types (with Default, without Default, without Debug) +//! - Unsigned primitive types +//! - **Collections Handling:** +//! - Basic scalar setters for collections +//! - Standard collections (Vec, HashMap, HashSet, BTreeMap, BTreeSet, LinkedList, BinaryHeap) +//! - Collection interface traits +//! - **Subform Setters:** +//! - `#[subform_collection]` (implicit, explicit definition, named, custom, setter on/off) +//! - `#[subform_entry]` (implicit, manual, named, setter on/off, HashMap specific) +//! - `#[subform_scalar]` (implicit, manual, named) +//! - Combinations of subform attributes on a single field +//! - **Attributes:** +//! - **Struct-level:** +//! - `#[storage_fields]` +//! - `#[mutator(custom)]` +//! - `#[perform]` +//! - **Field-level:** +//! - `#[former(default = ...)]` +//! - `#[scalar(name = ..., setter = ..., debug)]` +//! - `#[subform_collection(name = ..., setter = ..., debug, definition = ...)]` +//! - `#[subform_entry(name = ..., setter = ..., debug)]` +//! - `#[subform_scalar(name = ..., setter = ..., debug)]` +//! - Multiple attributes on one field +//! - Feature-gated fields (`#[cfg(...)]`) +//! - **Generics & Lifetimes:** +//! - Parametrized struct +//! - Parametrized field +//! - Slice lifetimes +//! - Dyn traits +//! - **Edge Cases:** +//! - Keyword identifiers for fields +//! - Keyword identifiers for subform setters +//! - Name collisions (with std types, keywords, etc.) +//! - Visibility (public/private structs and fields) +//! - **Compile-time Failures:** Tests ensuring incorrect usage results in compile errors. +//! +//! - **Former Derive for Enums** +//! - Basic setup (manual vs derived) +//! - Unit variants +//! - Tuple variants: +//! - Single field (scalar vs subform default behavior) +//! - Multi-field (scalar only, error for default subform) +//! - Named-field (struct-like) variants +//! - Variant attributes (`#[scalar]`, `#[subform_scalar]`) +//! - Edge cases (keywords as variant names) +//! +//! - **Component Derives** +//! - `#[derive(ComponentFrom)]` +//! - `#[derive(Assign)]` +//! - `#[derive(ComponentsAssign)]` +//! - `#[derive(FromComponents)]` +//! - Composite usage (using multiple component derives together) +//! - Tuple struct support for component derives +//! - Manual vs Derived comparison tests +//! - Compile-time Failures (placeholder/needs expansion) +//! +//! - **General Tests** +//! - Basic smoke tests (local and published) +//! - Experimental area for temporary tests use super::*; use test_tools::exposed::*; #[ cfg( feature = "derive_former" ) ] -mod former_tests +mod former_struct_tests { use super::*; @@ -45,10 +116,8 @@ mod former_tests mod name_collision_former_hashmap_without_parameter; mod name_collision_former_vector_without_parameter; mod name_collisions; - // mod name_collision_context; - // mod name_collision_end; - // mod name_collision_on_end; - // mod name_collision_core; + mod keyword_field_derive; + mod keyword_subform_derive; // = parametrization @@ -152,6 +221,49 @@ mod former_tests } +#[ cfg( feature = "derive_former" ) ] +mod former_enum_tests +{ + use super::*; + + // = enum + + // xxx : qqq : enable or remove + mod usecase1; + + mod basic_manual; + mod basic_derive; + mod multi_field_manual; + mod multi_field_derive; + mod unit_variant_manual; + mod unit_variant_derive; + mod enum_named_fields_manual; + mod enum_named_fields_derive; + + // = generics + + mod generics_in_tuple_variant_manual; + mod generics_in_tuple_variant_derive; + mod generics_shared_tuple_manual; + mod generics_shared_tuple_derive; + mod generics_shared_struct_manual; + mod generics_shared_struct_derive; + + mod generics_independent_tuple_manual; + mod generics_independent_tuple_derive; + mod generics_independent_struct_manual; + mod generics_independent_struct_derive; + + mod scalar_generic_tuple_manual; + mod scalar_generic_tuple_derive; + + // = conflicts + + mod keyword_variant_derive; + +} + + #[ cfg( feature = "derive_components" ) ] mod components_tests { @@ -161,21 +273,37 @@ mod components_tests mod component_from_manual; #[ cfg( feature = "derive_component_from" ) ] mod component_from; + #[ cfg( feature = "derive_component_from" ) ] + mod component_from_tuple; + #[ cfg( feature = "derive_component_from" ) ] + mod component_from_tuple_manual; #[ cfg( feature = "derive_component_assign" ) ] mod component_assign_manual; #[ cfg( feature = "derive_component_assign" ) ] mod component_assign; + #[ cfg( feature = "derive_component_assign" ) ] + mod component_assign_tuple; + #[ cfg( feature = "derive_component_assign" ) ] + mod component_assign_tuple_manual; #[ cfg( all( feature = "derive_component_assign", feature = "derive_components_assign" ) ) ] mod components_assign_manual; #[ cfg( all( feature = "derive_component_assign", feature = "derive_components_assign" ) ) ] mod components_assign; + #[ cfg( all( feature = "derive_component_assign", feature = "derive_components_assign" ) ) ] + mod components_assign_tuple; + #[ cfg( all( feature = "derive_component_assign", feature = "derive_components_assign" ) ) ] + mod components_assign_tuple_manual; #[ cfg( all( feature = "derive_from_components" ) ) ] mod from_components_manual; #[ cfg( all( feature = "derive_from_components" ) ) ] mod from_components; + #[ cfg( all( feature = "derive_from_components" ) ) ] + mod from_components_tuple; + #[ cfg( all( feature = "derive_from_components" ) ) ] + mod from_components_tuple_manual; #[ cfg( all( feature = "derive_component_from", feature = "derive_component_assign", feature = "derive_components_assign", feature = "derive_from_components" ) ) ] mod composite_manual; @@ -199,10 +327,12 @@ only_for_terminal_module! println!( "current_dir : {:?}", std::env::current_dir().unwrap() ); let t = test_tools::compiletime::TestCases::new(); - 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" ); + t.compile_fail( "tests/inc/former_struct_tests/compiletime/field_attr_bad.rs" ); + t.compile_fail( "tests/inc/former_struct_tests/compiletime/struct_attr_bad.rs" ); + t.pass( "tests/inc/former_struct_tests/compiletime/hashmap_without_parameter.rs" ); + t.pass( "tests/inc/former_struct_tests/compiletime/vector_without_parameter.rs" ); + + // assert!( false ); } @@ -221,4 +351,4 @@ only_for_terminal_module! } -} +} \ No newline at end of file diff --git a/module/core/former_meta/Cargo.toml b/module/core/former_meta/Cargo.toml index 80aab28703..c19491abe5 100644 --- a/module/core/former_meta/Cargo.toml +++ b/module/core/former_meta/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "former_meta" -version = "2.12.0" +version = "2.14.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -51,7 +51,7 @@ derive_from_components = [] proc-macro = true [dependencies] -macro_tools = { workspace = true, features = [ "attr", "attr_prop", "ct", "item_struct", "container_kind", "diag", "phantom", "generic_params", "generic_args", "typ", "derive" ] } # qqq : xxx : optimize set of features +macro_tools = { workspace = true, features = [ "attr", "attr_prop", "ct", "item_struct", "container_kind", "diag", "phantom", "generic_params", "generic_args", "typ", "derive", "ident" ] } # qqq : zzz : optimize set of features former_types = { workspace = true, features = [ "types_component_assign" ] } iter_tools = { workspace = true } convert_case = { version = "0.6.0", default-features = false, optional = true, features = [] } diff --git a/module/core/former_meta/License b/module/core/former_meta/License index c32986cee3..a23529f45b 100644 --- a/module/core/former_meta/License +++ b/module/core/former_meta/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/former_meta/src/component/component_assign.rs b/module/core/former_meta/src/component/component_assign.rs index 951e385778..a9b9776fd2 100644 --- a/module/core/former_meta/src/component/component_assign.rs +++ b/module/core/former_meta/src/component/component_assign.rs @@ -1,6 +1,14 @@ #[ allow( clippy::wildcard_imports ) ] use super::*; -use macro_tools::{ attr, diag, Result }; +// Use re-exports from macro_tools +use macro_tools:: +{ + qt, + attr, diag, Result, + proc_macro2::TokenStream, + syn::Index, +}; + /// /// Generates implementations of the `Assign` trait for each field of a struct. @@ -12,15 +20,31 @@ pub fn component_assign( input : proc_macro::TokenStream ) -> Result< proc_macro let has_debug = attr::has_debug( parsed.attrs.iter() )?; let item_name = &parsed.ident.clone(); - let for_field = parsed.fields.iter().map( | field | + // Directly iterate over fields and handle named/unnamed cases + let for_fields = match &parsed.fields { - for_each_field( field, &parsed.ident ) - }) - .collect::< Result< Vec< _ > > >()?; + syn::Fields::Named( fields_named ) => + { + fields_named.named.iter() + .map( | field | for_each_field( field, None, item_name ) ) // Pass None for index + .collect::< Result< Vec< _ > > >()? + }, + syn::Fields::Unnamed( fields_unnamed ) => + { + fields_unnamed.unnamed.iter().enumerate() + .map( |( index, field )| for_each_field( field, Some( index ), item_name ) ) // Pass Some(index) + .collect::< Result< Vec< _ > > >()? + }, + syn::Fields::Unit => + { + // No fields to generate Assign for + vec![] + }, + }; let result = qt! { - #( #for_field )* + #( #for_fields )* }; if has_debug @@ -42,31 +66,53 @@ pub fn component_assign( input : proc_macro::TokenStream ) -> Result< proc_macro /// # Parameters /// /// - `field`: Reference to the struct field's metadata. +/// - `index`: `Some(usize)` for tuple fields, `None` for named fields. /// - `item_name`: The name of the struct. /// -/// # Example of generated code +/// # Example of generated code for a tuple struct field /// /// ```rust, ignore -/// impl< IntoT > former::Assign< i32, IntoT > for Options1 +/// impl< IntoT > Assign< i32, IntoT > for TupleStruct /// where /// IntoT : Into< i32 >, /// { /// #[ inline( always ) ] /// fn assign( &mut self, component : IntoT ) /// { -/// self.field1 = component.into().clone(); +/// self.0 = component.into(); // Uses index /// } /// } /// ``` -fn for_each_field( field : &syn::Field, item_name : &syn::Ident ) -> Result< proc_macro2::TokenStream > +fn for_each_field +( + field : &syn::Field, + index : Option< usize >, // Added index parameter + item_name : &syn::Ident +) -> Result< proc_macro2::TokenStream > { - let field_name = field.ident.as_ref() - .ok_or_else( || syn::Error::new( field.span(), "Field without a name" ) )?; let field_type = &field.ty; + // Construct the field accessor based on whether it's named or tuple + let field_accessor : TokenStream = if let Some( ident ) = &field.ident + { + // Named field: self.field_name + quote! { #ident } + } + else if let Some( idx ) = index + { + // Tuple field: self.0, self.1, etc. + let index_lit = Index::from( idx ); + quote! { #index_lit } + } + else + { + // Should not happen if called correctly from `component_assign` + return Err( syn::Error::new_spanned( field, "Field has neither ident nor index" ) ); + }; + Ok( qt! { - #[ allow( non_snake_case ) ] + #[ allow( non_snake_case ) ] // Still useful for named fields that might not be snake_case impl< IntoT > Assign< #field_type, IntoT > for #item_name where IntoT : Into< #field_type >, @@ -74,8 +120,8 @@ fn for_each_field( field : &syn::Field, item_name : &syn::Ident ) -> Result< pro #[ inline( always ) ] fn assign( &mut self, component : IntoT ) { - self.#field_name = component.into(); + self.#field_accessor = component.into(); // Use the accessor } } }) -} +} \ No newline at end of file diff --git a/module/core/former_meta/src/component/component_from.rs b/module/core/former_meta/src/component/component_from.rs index 9c505429a7..dd53464fb5 100644 --- a/module/core/former_meta/src/component/component_from.rs +++ b/module/core/former_meta/src/component/component_from.rs @@ -1,6 +1,11 @@ #[ allow( clippy::wildcard_imports ) ] use super::*; -use macro_tools::{ attr, diag, Result }; +use macro_tools:: +{ + attr, diag, Result, + proc_macro2::TokenStream, + syn::Index, +}; /// Generates `From` implementations for each unique component (field) of the structure. pub fn component_from( input : proc_macro::TokenStream ) -> Result< proc_macro2::TokenStream > @@ -10,15 +15,31 @@ pub fn component_from( input : proc_macro::TokenStream ) -> Result< proc_macro2: let has_debug = attr::has_debug( parsed.attrs.iter() )?; let item_name = &parsed.ident; - let for_field = parsed.fields.iter().map( | field | + // Directly iterate over fields and handle named/unnamed cases + let for_fields = match &parsed.fields { - for_each_field( field, &parsed.ident ) - }) - .collect::< Result< Vec< _ > > >()?; + syn::Fields::Named( fields_named ) => + { + fields_named.named.iter() + .map( | field | for_each_field( field, None, item_name ) ) // Pass None for index + .collect::< Result< Vec< _ > > >()? + }, + syn::Fields::Unnamed( fields_unnamed ) => + { + fields_unnamed.unnamed.iter().enumerate() + .map( |( index, field )| for_each_field( field, Some( index ), item_name ) ) // Pass Some(index) + .collect::< Result< Vec< _ > > >()? + }, + syn::Fields::Unit => + { + // No fields to generate From for + vec![] + }, + }; let result = qt! { - #( #for_field )* + #( #for_fields )* }; if has_debug @@ -27,11 +48,6 @@ pub fn component_from( input : proc_macro::TokenStream ) -> Result< proc_macro2: diag::report_print( about, &original_input, &result ); } - // if has_debug - // { - // diag::report_print( "derive : ComponentFrom", original_input, &result ); - // } - Ok( result ) } @@ -40,38 +56,59 @@ pub fn component_from( input : proc_macro::TokenStream ) -> Result< proc_macro2: /// # Arguments /// /// * `field` - A reference to the field for which to generate the `From` implementation. +/// * `index`: `Some(usize)` for tuple fields, `None` for named fields. /// * `item_name` - The name of the structure containing the field. /// -/// # Example of generated code -/// -/// If you have a structure `Person` with a field `name: String`, the generated code would look something like this: +/// # Example of generated code for a tuple struct field /// /// ```rust, ignore -/// impl From< &Person > for String +/// impl From< &TupleStruct > for i32 /// { /// #[ inline( always ) ] -/// fn from( src : &Person ) -> Self +/// fn from( src : &TupleStruct ) -> Self /// { -/// src.name.clone() +/// src.0.clone() // Uses index /// } /// } -/// -fn for_each_field( field : &syn::Field, item_name : &syn::Ident ) -> Result< proc_macro2::TokenStream > +/// ``` +fn for_each_field +( + field : &syn::Field, + index : Option< usize >, // Added index parameter + item_name : &syn::Ident +) -> Result< proc_macro2::TokenStream > { - let field_name = field.ident.as_ref() - .ok_or_else( || syn::Error::new( field.span(), "Field without a name" ) )?; let field_type = &field.ty; + // Construct the field accessor based on whether it's named or tuple + let field_accessor : TokenStream = if let Some( ident ) = &field.ident + { + // Named field: src.field_name + quote! { #ident } + } + else if let Some( idx ) = index + { + // Tuple field: src.0, src.1, etc. + let index_lit = Index::from( idx ); + quote! { #index_lit } + } + else + { + // Should not happen if called correctly from `component_from` + return Err( syn::Error::new_spanned( field, "Field has neither ident nor index" ) ); + }; + Ok( qt! { - #[ allow( non_local_definitions ) ] + // Removed #[ allow( non_local_definitions ) ] as it seems unnecessary here impl From< &#item_name > for #field_type { #[ inline( always ) ] fn from( src : &#item_name ) -> Self { - src.#field_name.clone() + // Use src.#field_accessor instead of self.#field_accessor + src.#field_accessor.clone() } } }) -} +} \ No newline at end of file diff --git a/module/core/former_meta/src/component/from_components.rs b/module/core/former_meta/src/component/from_components.rs index 46dcdbd3ad..0357a81ddb 100644 --- a/module/core/former_meta/src/component/from_components.rs +++ b/module/core/former_meta/src/component/from_components.rs @@ -1,6 +1,12 @@ #[ allow( clippy::wildcard_imports ) ] use super::*; -use macro_tools::{ attr, diag, item_struct, Result }; +// Use re-exports from macro_tools +use macro_tools:: +{ + attr, diag, item_struct, Result, + proc_macro2::TokenStream, +}; + /// /// Generates an implementation of the `From< T >` trait for a custom struct, enabling @@ -9,25 +15,21 @@ use macro_tools::{ attr, diag, item_struct, Result }; /// fields to be initialized from an instance of type `T`, assuming `T` can be /// converted into each of the struct's field types. /// -/// # Example of generated code +/// # Example of generated code for a tuple struct /// /// ```ignore -/// impl< T > From< T > for Options2 +/// impl< T > From< T > for TargetTuple /// where +/// T : Clone, /// T : Into< i32 >, /// T : Into< String >, -/// T : Clone, /// { /// #[ inline( always ) ] /// fn from( src : T ) -> Self /// { -/// let field1 = Into::< i32 >::into( src.clone() ); -/// let field2 = Into::< String >::into( src.clone() ); -/// Options2 -/// { -/// field1, -/// field2, -/// } +/// let field_0 = Into::< i32 >::into( src.clone() ); +/// let field_1 = Into::< String >::into( src.clone() ); +/// Self( field_0, field_1 ) // Uses tuple construction /// } /// } /// ``` @@ -42,10 +44,33 @@ pub fn from_components( input : proc_macro::TokenStream ) -> Result< proc_macro2 // Struct name let item_name = &parsed.ident; - // Generate snipets - let trait_bounds = trait_bounds( item_struct::field_types( &parsed ) ); - let field_assigns = field_assign( parsed.fields.iter() ); - let field_names : Vec< _ > = parsed.fields.iter().map( | field | &field.ident ).collect(); + // Generate snippets based on whether fields are named or unnamed + let ( field_assigns, final_construction ) : ( Vec< TokenStream >, TokenStream ) = + match &parsed.fields + { + syn::Fields::Named( fields_named ) => + { + let assigns = field_assign_named( fields_named.named.iter() ); + let names : Vec< _ > = fields_named.named.iter().map( | f | f.ident.as_ref().unwrap() ).collect(); + let construction = quote! { Self { #( #names, )* } }; + ( assigns, construction ) + }, + syn::Fields::Unnamed( fields_unnamed ) => + { + let ( assigns, temp_names ) = field_assign_unnamed( fields_unnamed.unnamed.iter().enumerate() ); + let construction = quote! { Self ( #( #temp_names, )* ) }; + ( assigns, construction ) + }, + syn::Fields::Unit => + { + // No fields to assign, construct directly + ( vec![], quote! { Self } ) + }, + }; + + // Extract field types for trait bounds + let field_types = item_struct::field_types( &parsed ); + let trait_bounds = trait_bounds( field_types ); // Generate the From trait implementation let result = qt! @@ -59,10 +84,7 @@ pub fn from_components( input : proc_macro::TokenStream ) -> Result< proc_macro2 fn from( src : T ) -> Self { #( #field_assigns )* - Self - { - #( #field_names, )* - } + #final_construction // Use the determined construction syntax } } }; @@ -73,31 +95,11 @@ pub fn from_components( input : proc_macro::TokenStream ) -> Result< proc_macro2 diag::report_print( about, &original_input, &result ); } - // if has_debug - // { - // diag::report_print( "derive : FromComponents", original_input, &result ); - // } - Ok( result ) } -/// Generates trait bounds for the `From< T >` implementation, ensuring that `T` -/// can be converted into each of the struct's field types. This function -/// constructs a sequence of trait bounds necessary for the `From< T >` -/// implementation to compile. -/// -/// # Example of generated code -/// -/// Given field types `[i32, String]`, this function generates: -/// -/// ```ignore -/// T : Into< i32 >, -/// T : Into< String >, -/// ``` -/// -/// These trait bounds are then used in the `From` implementation to ensure type compatibility. +/// Generates trait bounds for the `From< T >` implementation. (Same as before) #[ inline ] -// fn trait_bounds( field_types : &[ &syn::Type ] ) -> Vec< proc_macro2::TokenStream > fn trait_bounds< 'a >( field_types : impl macro_tools::IterTrait< 'a, &'a syn::Type > ) -> Vec< proc_macro2::TokenStream > { field_types.map( | field_type | @@ -109,26 +111,13 @@ fn trait_bounds< 'a >( field_types : impl macro_tools::IterTrait< 'a, &'a syn::T }).collect() } -/// Generates code snippets for converting `T` into each of the struct's fields -/// inside the `from` function of the `From` trait implementation. This function -/// creates a series of statements that clone the source `T`, convert it into the -/// appropriate field type, and assign it to the corresponding field of the struct. -/// -/// # Example of generated code -/// -/// For a struct with fields `field1: i32` and `field2: String`, this function generates: -/// -/// ```ignore -/// let field1 = Into::< i32 >::into( src.clone() ); -/// let field2 = Into::< String >::into( src.clone() ); -/// ``` -/// +/// Generates assignment snippets for named fields. #[ inline ] -fn field_assign< 'a >( fields : impl Iterator< Item = &'a syn::Field > ) -> Vec< proc_macro2::TokenStream > +fn field_assign_named< 'a >( fields : impl Iterator< Item = &'a syn::Field > ) -> Vec< proc_macro2::TokenStream > { fields.map( | field | { - let field_ident = &field.ident; + let field_ident = field.ident.as_ref().unwrap(); // Safe because we are in Named fields let field_type = &field.ty; qt! { @@ -136,3 +125,22 @@ fn field_assign< 'a >( fields : impl Iterator< Item = &'a syn::Field > ) -> Vec< } }).collect() } + +/// Generates assignment snippets for unnamed fields and returns temporary variable names. +#[ inline ] +fn field_assign_unnamed< 'a > +( + fields : impl Iterator< Item = ( usize, &'a syn::Field ) > +) -> ( Vec< proc_macro2::TokenStream >, Vec< proc_macro2::Ident > ) +{ + fields.map( |( index, field )| + { + let temp_var_name = format_ident!( "field_{}", index ); // Create temp name like field_0 + let field_type = &field.ty; + let assign_snippet = qt! + { + let #temp_var_name = Into::< #field_type >::into( src.clone() ); + }; + ( assign_snippet, temp_var_name ) + }).unzip() // Unzip into two vectors: assignments and temp names +} \ No newline at end of file diff --git a/module/core/former_meta/src/derive_former.rs b/module/core/former_meta/src/derive_former.rs index 61549a8fe9..505af919c3 100644 --- a/module/core/former_meta/src/derive_former.rs +++ b/module/core/former_meta/src/derive_former.rs @@ -1,10 +1,16 @@ +// File: module/core/former_meta/src/derive_former.rs #[ allow( clippy::wildcard_imports ) ] use super::*; -use iter_tools::Itertools; -use macro_tools::{ attr, diag, generic_params, generic_args, typ, derive, Result }; -use proc_macro2::TokenStream; +use macro_tools:: +{ + attr, diag, typ, Result, + proc_macro2::TokenStream, quote::{ format_ident, quote }, syn::spanned::Spanned, +}; -// qqq : implement interfaces for other collections +mod former_enum; +use former_enum::former_for_enum; +mod former_struct; +use former_struct::former_for_struct; mod field_attrs; #[ allow( clippy::wildcard_imports ) ] @@ -17,32 +23,8 @@ 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() ) ); -/// } -/// } -/// ``` -/// +/// If the `custom` attribute is not specified, a default empty implementation is generated. +/// If the `debug` attribute is specified, it prints an example of a custom mutator implementation. #[ allow( clippy::format_in_format_args, clippy::unnecessary_wraps ) ] pub fn mutator ( @@ -58,11 +40,13 @@ pub fn mutator { let former_mutator_code = if mutator.custom.value( false ) { - qt!{} + // If custom mutator is requested via #[ mutator( custom ) ], generate nothing, assuming user provides the impl. + quote!{} } else { - qt! + // Otherwise, generate a default empty impl. + quote! { impl< #former_definition_types_generics_impl > former::FormerMutator for #former_definition_types < #former_definition_types_generics_ty > @@ -73,6 +57,7 @@ pub fn mutator } }; + // If debug is enabled for the mutator attribute, print a helpful example. if mutator.debug.value( false ) { let debug = format! @@ -87,16 +72,21 @@ 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 > ) + fn form_mutation + ( + storage : &mut Self::Storage, + context : &mut Option< Self::Context >, + ) {{ + // Example: Set a default value if field 'a' wasn't provided + // storage.a.get_or_insert_with( Default::default ); }} }} ", - format!( "{}", qt!{ #former_definition_types_generics_impl } ), - format!( "{}", qt!{ #former_definition_types_generics_ty } ), - format!( "{}", qt!{ #former_definition_types_generics_where } ), + format!( "{}", quote!{ #former_definition_types_generics_impl } ), + format!( "{}", quote!{ #former_definition_types_generics_ty } ), + format!( "{}", quote!{ #former_definition_types_generics_where } ), ); - // println!( "{debug}" ); let about = format! ( r"derive : Former @@ -108,9 +98,8 @@ item : {item}", Ok( former_mutator_code ) } -/// -/// Generate documentation for the former. -/// + +/// Generate documentation strings for the former struct and its module. fn doc_generate( item : &syn::Ident ) -> ( String, String ) { @@ -133,643 +122,38 @@ utilizes a defined end strategy to finalize the object creation. ( 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/) -/// + +/// Generate the whole Former ecosystem for either a struct or an enum. +/// This is the main entry point for the `#[derive(Former)]` macro. #[ allow( clippy::too_many_lines ) ] pub fn former( input : proc_macro::TokenStream ) -> Result< TokenStream > { - use macro_tools::IntoGenericArgs; - let original_input = input.clone(); let ast = syn::parse::< syn::DeriveInput >( input )?; let has_debug = attr::has_debug( ast.attrs.iter() )?; - let struct_attrs = ItemAttributes::from_attrs( ast.attrs.iter() )?; - - /* names */ - - let vis = &ast.vis; - let item = &ast.ident; - let former = format_ident!( "{item}Former" ); - let former_storage = format_ident!( "{item}FormerStorage" ); - let former_definition = format_ident!( "{item}FormerDefinition" ); - let former_definition_types = format_ident!( "{item}FormerDefinitionTypes" ); - let as_subformer = format_ident!( "{item}AsSubformer" ); - let as_subformer_end = format_ident!( "{item}AsSubformerEnd" ); - - let as_subformer_end_doc = format! - ( - r" -Represents an end condition for former of [`${item}`], 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! - { - < (), #item < #struct_generics_ty >, former::ReturnPreformed > - }; - let former_definition_args = generic_args::merge( &generics.into_generic_args(), &extra ).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! + // Dispatch based on whether the input is a struct, enum, or union. + let result = match ast.data { - < Definition = #former_definition < #former_definition_args > > - where - Definition : former::FormerDefinition - < - Storage = #former_storage < #struct_generics_ty >, - Formed = #item < #struct_generics_ty >, - >, - Definition::Types : former::FormerDefinitionTypes - < - Storage = #former_storage < #struct_generics_ty >, - Formed = #item < #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 = #item < #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 = #item < #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( item ); - let ( perform, perform_output, perform_generics ) = struct_attrs.performer()?; - - /* fields */ - - let fields = derive::named_fields( &ast )?; - - let formed_fields : Vec< _ > = fields - .into_iter() - .map( | field | - { - FormerField::from_syn( field, true, true ) - }) - .collect::< Result< _ > >()?; - - let storage_fields : Vec< _ > = struct_attrs - .storage_fields() - .iter() - .map( | field | - { - FormerField::from_syn( field, true, false ) - }) - .collect::< Result< _ > >()?; - - 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 - ( - item, - &original_input, - &struct_generics_impl, - &struct_generics_ty, - &struct_generics_where, - &former, - &former_generics_impl, - &former_generics_ty, - &former_generics_where, - &former_storage, - ), - )}).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 storage_field_preform : Vec< _ > = storage_field_preform - .into_iter() - .collect::< Result< _ > >()?; - - let former_mutator_code = mutator - ( - item, - &original_input, - &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 > #item < #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 > > + syn::Data::Struct( ref data_struct ) => { - #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 #item < #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 #item < #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 #item < #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 #item < #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 + former_for_struct( &ast, data_struct, &original_input, has_debug ) + }, + syn::Data::Enum( ref data_enum ) => { - 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 + former_for_enum( &ast, data_enum, &original_input, has_debug ) + }, + syn::Data::Union( _ ) => { - Self - { - _phantom : ::core::marker::PhantomData, - } + // Unions are not supported. + Err( syn::Error::new( ast.span(), "Former derive does not support unions" ) ) } - } - - 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 = #item < #struct_generics_ty >; - } - - impl < #struct_generics_impl > former::StoragePreform - for #former_storage < #struct_generics_ty > - where - #struct_generics_where - { - // type Preformed = #item < #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 = #item :: < #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( ::core::option::Option::None, ::core::option::Option::None, on_end ) - } - - /// - /// Initializes a former with a coercible end condition. - /// - #[ inline( always ) ] - pub fn new_coercing< IntoEnd >( end : IntoEnd ) -> Self - where - IntoEnd : ::core::convert::Into< Definition::End >, - { - Self::begin_coercing - ( - ::core::option::Option::None, - ::core::option::Option::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 = ::core::option::Option::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 = ::core::option::Option::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 = #item < #struct_generics_ty > >, - Definition::Types : former::FormerDefinitionTypes< Storage = #former_storage < #struct_generics_ty >, Formed = #item < #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( ::core::option::Option::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 the top-level `#[debug]` attribute was found, print the final generated code. if has_debug { - let about = format!( "derive : Former\nstructure : {item}" ); + let about = format!( "derive : Former\nstructure : {}", ast.ident ); diag::report_print( about, &original_input, &result ); } diff --git a/module/core/former_meta/src/derive_former/field.rs b/module/core/former_meta/src/derive_former/field.rs index 34a80bc769..dafc324500 100644 --- a/module/core/former_meta/src/derive_former/field.rs +++ b/module/core/former_meta/src/derive_former/field.rs @@ -1,3 +1,4 @@ +// File: module/core/former_meta/src/derive_former/field.rs #[ allow( clippy::wildcard_imports ) ] use super::*; use macro_tools::container_kind; @@ -531,17 +532,23 @@ field : {field_ident}", let attr = self.attrs.subform_collection.as_ref().unwrap(); let field_ident = &self.ident; let field_typ = &self.non_optional_ty; - let params = typ::type_parameters( field_typ, &( .. ) ); + let params = typ::type_parameters( field_typ, .. ); #[ allow( clippy::useless_attribute, clippy::items_after_statements ) ] use convert_case::{ Case, Casing }; + // Get the field name as a string + let field_name_str = field_ident.to_string(); + // Remove the raw identifier prefix `r#` if present + let field_name_cleaned = field_name_str.strip_prefix("r#").unwrap_or(&field_name_str); + // example : `ParentSubformCollectionChildrenEnd` let subform_collection_end = format_ident! { "{}SubformCollection{}End", item, - field_ident.to_string().to_case( Case::Pascal ) + // Use the cleaned name for PascalCase conversion + field_name_cleaned.to_case( Case::Pascal ) }; // example : `_children_subform_collection` @@ -874,12 +881,18 @@ with the new content generated during the subforming process. // example : `children` let setter_name = self.subform_entry_setter_name(); + // Get the field name as a string + let field_name_str = field_ident.to_string(); + // Remove the raw identifier prefix `r#` if present + let field_name_cleaned = field_name_str.strip_prefix("r#").unwrap_or(&field_name_str); + // example : `ParentSubformEntryChildrenEnd` let subform_entry_end = format_ident! { "{}SubformEntry{}End", item, - field_ident.to_string().to_case( Case::Pascal ) + // Use the cleaned name for PascalCase conversion + field_name_cleaned.to_case( Case::Pascal ) }; // example : `_children_subform_entry` @@ -1162,12 +1175,18 @@ formation process of the `{item}`. // example : `children` let setter_name = self.subform_scalar_setter_name(); + // Get the field name as a string + let field_name_str = field_ident.to_string(); + // Remove the raw identifier prefix `r#` if present + let field_name_cleaned = field_name_str.strip_prefix("r#").unwrap_or(&field_name_str); + // example : `ParentSubformScalarChildrenEnd` let subform_scalar_end = format_ident! { "{}SubformScalar{}End", item, - field_ident.to_string().to_case( Case::Pascal ) + // Use the cleaned name for PascalCase conversion + field_name_cleaned.to_case( Case::Pascal ) }; // example : `_children_subform_scalar` @@ -1580,4 +1599,4 @@ Essentially, this end action integrates the individually formed scalar value bac true } -} +} \ No newline at end of file diff --git a/module/core/former_meta/src/derive_former/former_enum.rs b/module/core/former_meta/src/derive_former/former_enum.rs new file mode 100644 index 0000000000..28272cf532 --- /dev/null +++ b/module/core/former_meta/src/derive_former/former_enum.rs @@ -0,0 +1,740 @@ +// File: module/core/former_meta/src/derive_former/former_enum.rs +#![ allow( clippy::wildcard_imports ) ] +use super::*; // Use items from parent module (derive_former.rs) +use macro_tools:: +{ + generic_params, Result, + proc_macro2::TokenStream, quote::{ format_ident, quote }, + ident, // Added for ident_maybe_raw + phantom, // Added for phantom::tuple + // typ, // Removed unused import +}; +// CORRECTED: Re-added Casing import +#[ cfg( feature = "derive_former" ) ] +use convert_case::{ Case, Casing }; + +// ================================== +// Generic Handling Strategy +// ================================== +// +// IMPORTANT NOTE ON GENERICS: +// +// Handling generics in enum variants for the `Former` derive involves several complexities, +// primarily concerning the interaction between the enum's own generic parameters (e.g., `Enum`) +// and the generics potentially present in the data type held by a variant (e.g., `Variant(Inner)` +// or `Variant(Inner)`). +// +// The core challenges and the chosen strategy are: +// +// 1. **Extracting Bounds from Inner Types is Unreliable:** Attempting to determine the necessary +// trait bounds for a generic parameter (`T` or `U`) solely by inspecting the inner type +// (e.g., `Inner`) within the variant is generally not feasible or reliable in a procedural macro. +// The macro only sees the *use* of the type, not its definition, and thus cannot know the +// bounds `Inner` requires for its generic parameters. The previous attempt to implement +// `generics_of_type` demonstrated this difficulty, leading to compilation errors. +// +// 2. **Focus on Propagating Enum Generics:** The correct approach is to focus on the generics +// defined *on the enum itself*. These generics (`enum Enum`) and their associated +// `where` clauses *must* be correctly propagated to all generated code that depends on them. +// +// 3. **Merging Generics for Implementations:** When generating `impl` blocks (like `impl FormingEnd` +// for the specialized `End` struct or `impl FormerMutator` for implicit definition types), +// we often need to combine the enum's generics with *additional* generics introduced by the +// macro's infrastructure (e.g., `Definition`, `Context`, `Formed`, `End`). +// **For this purpose, `macro_tools::generic_params::merge` MUST be used.** It correctly +// combines two complete `syn::Generics` structures (including their `where` clauses). +// +// 4. **Bound Requirements:** The necessary bounds for the *inner type's* generics (e.g., the bounds +// `Inner` requires for `T` or `U`) are implicitly handled by the Rust compiler *after* the macro +// generates the code. If the generated code attempts to use the inner type in a way that +// violates its bounds (because the enum's generics/bounds passed down are insufficient), +// the compiler will produce the appropriate error. The macro's responsibility is to correctly +// apply the *enum's* bounds where needed. +// +// 5. **`macro_tools::generic_params::merge` Issues:** If any issues arise with the merging logic itself +// (e.g., incorrect handling of `where` clauses by the `merge` function), those issues must be +// addressed within the `macro_tools` crate, as it is the designated tool for this task. +// +// In summary: We propagate the enum's generics and bounds. We use `generic_params::merge` +// to combine these with macro-internal generics when generating implementations. We rely on +// the Rust compiler to enforce the bounds required by the inner data types used in variants. +// +// ================================== + +// ================================== +// Main Generation Logic +// ================================== + +/// Generate the Former ecosystem for an enum. +#[ allow( clippy::too_many_lines ) ] +pub(super) fn former_for_enum // Make it pub(super) +( + ast : &syn::DeriveInput, + data_enum : &syn::DataEnum, + original_input : &proc_macro::TokenStream, // Added original_input + has_debug : bool, // Added has_debug +) -> Result< TokenStream > +{ + let enum_name = &ast.ident; + let vis = &ast.vis; + let generics = &ast.generics; + let ( _enum_generics_with_defaults, enum_generics_impl, enum_generics_ty, enum_generics_where ) + = generic_params::decompose( generics ); + + // Initialize vectors to collect generated code pieces + let mut methods = Vec::new(); + let mut end_impls = Vec::new(); // Needed again for subform variants + + // Iterate through each variant of the enum + for variant in &data_enum.variants + { + let variant_ident = &variant.ident; + + // Generate the snake_case method name, handling potential keywords + let variant_name_str = variant_ident.to_string(); + // CORRECTED: Reverted to using Snake case + let method_name_snake_str = variant_name_str.to_case( Case::Snake ); + let method_name_ident_temp = format_ident!( "{}", method_name_snake_str, span = variant_ident.span() ); + let method_name = ident::ident_maybe_raw( &method_name_ident_temp ); + + // Parse attributes *from the variant* itself + let variant_attrs = FieldAttributes::from_attrs( variant.attrs.iter() )?; + let wants_scalar = variant_attrs.scalar.is_some() && variant_attrs.scalar.as_ref().unwrap().setter(); + let wants_subform_scalar = variant_attrs.subform_scalar.is_some(); // Check for explicit subform_scalar + + // --- Prepare merged where clause for this variant's generated impls --- + // Start with the enum's where clause. We might add more bounds later if needed + // specifically by the traits we implement (like FormingEnd often needs Default, Debug etc.) + // For now, we primarily rely on propagating the enum's constraints. + // FIX: Removed `mut` as it's not mutated currently. + let merged_where_clause = enum_generics_where.clone(); + // Example of adding common bounds (adjust as needed based on trait requirements): + // use std::collections::HashSet; + // let mut merged_bounds_set = HashSet::new(); + // for pred in &enum_generics_where { merged_bounds_set.insert(quote!{#pred}.to_string()); } + // for param in generics.params.iter() { + // if let syn::GenericParam::Type(tp) = param { + // let ident = &tp.ident; + // let common_bounds: Vec = vec![ + // syn::parse_quote! { #ident: core::fmt::Debug }, + // syn::parse_quote! { #ident: core::default::Default }, + // // Add other common bounds required by FormingEnd, StoragePreform etc. + // ]; + // for bound in common_bounds { + // if merged_bounds_set.insert(quote!{#bound}.to_string()) { + // merged_where_clause.push(bound); + // } + // } + // } + // } + // --- End merged where clause preparation --- + + + // Generate method based on the variant's fields + match &variant.fields + { + // Case 1: Unit variant (e.g., `Empty`) - Always Direct constructor + syn::Fields::Unit => + { + // FIX: Removed generics from method signature in Step 1.5 - CORRECT + let static_method = quote! + { + /// Constructor for the #variant_ident unit variant. + #[ inline( always ) ] + #vis fn #method_name() -> Self + // where #enum_generics_where // Removed where clause from method + { + Self::#variant_ident + } + }; + methods.push( static_method ); + }, + // Case 2: Tuple variant (e.g., `Simple(String)`, `MultiTuple(i32, String)`) + syn::Fields::Unnamed( fields ) => + { + // Sub-case: Single field tuple variant (e.g., `Simple(String)`) + if fields.unnamed.len() == 1 + { + let field = fields.unnamed.first().unwrap(); + let inner_type = &field.ty; + + // Check if the inner type likely has a Former derived (simplistic check) + // A more robust check would involve trying to resolve the path `::Former` + // but that's complex in proc macros. This assumes simple paths. + let inner_former_exists = if let syn::Type::Path( tp ) = inner_type + { + tp.path.segments.last().map_or( false, | seg | + { + // Heuristic: if it's not a primitive type maybe it has a former + !matches!( seg.ident.to_string().as_str(), "bool" | "char" | "str" | "String" | "i8" | "i16" | "i32" | "i64" | "i128" | "isize" | "u8" | "u16" | "u32" | "u64" | "u128" | "usize" | "f32" | "f64" ) + }) + } + else + { + false // Not a path, unlikely to have a derived Former + }; + + if wants_scalar || ( !wants_subform_scalar && !inner_former_exists ) + { + // --- Generate Direct Constructor (Scalar Style) --- + // FIX: Removed generics from method signature in Step 1.5 - CORRECT + let static_method = quote! + { + /// Constructor for the #variant_ident variant (scalar style). + /// Takes a value convertible into the inner type #inner_type. + #[ inline( always ) ] + #vis fn #method_name( value : impl Into< #inner_type > ) -> Self + // where #enum_generics_where // Removed where clause from method + { + Self::#variant_ident( value.into() ) + } + }; + methods.push( static_method ); + } + else // Default or explicit subform_scalar -> Generate Subformer + { + // --- Generate Subformer Starter + End Logic --- + + let end_struct_name = format_ident!( "{}{}End", enum_name, variant_ident ); + + // Attempt to extract name and generics from the inner type path + let ( inner_type_name, inner_generics ) = match inner_type + { + syn::Type::Path( type_path ) => + { + let segment = type_path.path.segments.last().ok_or_else( || syn::Error::new_spanned( inner_type, "Cannot derive name from type path") )?; + ( segment.ident.clone(), segment.arguments.clone() ) + }, + _ => return Err( syn::Error::new_spanned( inner_type, "Inner variant type must be a path type (like MyStruct or MyStruct) to derive Former" ) ), + }; + + let inner_former_name = format_ident!( "{}Former", inner_type_name ); + let inner_storage_name = format_ident!( "{}FormerStorage", inner_type_name ); + let inner_def_name = format_ident!( "{}FormerDefinition", inner_type_name ); + let inner_def_types_name = format_ident!( "{}FormerDefinitionTypes", inner_type_name ); + + // Extract type arguments from inner_generics PathArguments + let inner_generics_ty : syn::punctuated::Punctuated = match &inner_generics + { + syn::PathArguments::AngleBracketed( args ) => args.args.clone(), + _ => syn::punctuated::Punctuated::new(), // Handle cases without generics like `MyStruct` + }; + // Add comma if generics are present + let inner_generics_ty_comma = if inner_generics_ty.is_empty() { quote!{} } else { quote!{ #inner_generics_ty, } }; + + // Create PhantomData type using enum's generics + let phantom_field_type = phantom::tuple( &enum_generics_ty ); + + // Define the End struct with enum's generics and merged where clause + let end_struct_def = quote! + { + #[ derive( Default, Debug ) ] + #vis struct #end_struct_name < #enum_generics_impl > + // Use the potentially merged where clause here + where #merged_where_clause + { + _phantom : #phantom_field_type, + } + }; + + // Implement FormingEnd for the End struct + let end_impl = quote! + { + #[ automatically_derived ] + // Use enum's impl generics here + impl< #enum_generics_impl > former::FormingEnd + < + // Use DefinitionTypes of the *inner* type's former + // Specify its generics, context=(), formed=Enum + #inner_def_types_name< #inner_generics_ty_comma (), #enum_name< #enum_generics_ty > > + > + // Use enum's type generics here + for #end_struct_name < #enum_generics_ty > + // Use the potentially merged where clause here + where + #merged_where_clause + { + #[ inline( always ) ] + fn call + ( + &self, + // Storage is from the *inner* type's former, specialized with its generics + sub_storage : #inner_storage_name< #inner_generics_ty >, + _context : Option< () >, // Context is () from static method + ) -> #enum_name< #enum_generics_ty > // Returns the Enum type specialized with its generics + { + // Preform the inner data and wrap it in the enum variant + let data = former::StoragePreform::preform( sub_storage ); + #enum_name::#variant_ident( data ) + } + } + }; + + // Define the static starter method on the enum + // FIX: Removed generics and where clause from method signature + let static_method = quote! + { + /// Starts forming the #variant_ident variant using a subformer. + #[ inline( always ) ] + #vis fn #method_name () + // Return type is the *inner* type's former... + -> #inner_former_name + < + #inner_generics_ty_comma // ...specialized with its own generics... + // ...and configured with a definition that uses the specialized End struct. + #inner_def_name + < + #inner_generics_ty_comma // Inner type generics + (), // Context = () + #enum_name< #enum_generics_ty >, // Formed = Enum + #end_struct_name < #enum_generics_ty > // End = Specialized End + > + > + // where #merged_where_clause // Removed where clause from method + { + // Start the inner former using its `begin` associated function. + // The End struct passed depends on the enum's generics. + #inner_former_name::begin( None, None, #end_struct_name::< #enum_generics_ty >::default() ) + } + }; + + methods.push( static_method ); + end_impls.push( quote!{ #end_struct_def #end_impl } ); // Collect End struct and its impl + } + } + // Sub-case: Multi-field tuple variant + else + { + if wants_scalar + { + // --- Generate Direct Constructor (Multi-Arg) --- + let mut params = Vec::new(); + let mut args = Vec::new(); + for ( i, field ) in fields.unnamed.iter().enumerate() + { + let param_name = format_ident!( "field{}", i ); + let field_type = &field.ty; + params.push( quote! { #param_name : impl Into< #field_type > } ); + args.push( quote! { #param_name.into() } ); + } + + // FIX: Removed generics from method signature + let static_method = quote! + { + /// Constructor for the #variant_ident variant with multiple fields (scalar style). + #[ inline( always ) ] + #vis fn #method_name( #( #params ),* ) -> Self + // where #enum_generics_where // Removed where clause from method + { + Self::#variant_ident( #( #args ),* ) + } + }; + methods.push( static_method ); + } + else // Default: Subformer (unsupported for multi-field tuple) + { + return Err + ( + syn::Error::new_spanned + ( + variant, + "Former derive on enums does not support the default subformer pattern for multi-field tuple variants.\nAdd the `#[ scalar ]` attribute to the variant, e.g., `#[ derive( Former ) ] enum MyEnum { #[ scalar ] MyVariant( T1, T2 ) }`, to generate a static constructor method `MyEnum::my_variant( T1, T2 ) -> MyEnum` instead." + ) + ); + } + } + }, + // Case 3: Struct variant + syn::Fields::Named( fields ) => + { + // --- Generate Implicit Former + Subformer Starter + End Logic --- + + let implicit_former_name = format_ident!( "{}{}Former", enum_name, variant_ident ); + let implicit_storage_name = format_ident!( "{}{}FormerStorage", enum_name, variant_ident ); + let implicit_def_name = format_ident!( "{}{}FormerDefinition", enum_name, variant_ident ); + let implicit_def_types_name = format_ident!( "{}{}FormerDefinitionTypes", enum_name, variant_ident ); + let end_struct_name = format_ident!( "{}{}End", enum_name, variant_ident ); + + let variant_struct_fields = fields.named.iter().cloned().collect(); + let variant_struct = syn::ItemStruct + { + attrs: vec![], + vis: vis.clone(), + struct_token: Default::default(), + ident: implicit_former_name.clone(), + generics: generics.clone(), // Use enum's generics for the implicit struct + fields: syn::Fields::Named( syn::FieldsNamed { brace_token: Default::default(), named: variant_struct_fields } ), + semi_token: None, + }; + + // --- Generate Implicit Former Components --- + + // 1. Implicit Storage Struct + let storage_fields_processed : Vec<_> = fields.named.iter() + .map( |f| FormerField::from_syn( f, true, true ) ) + .collect::< Result< _ > >()?; + + let storage_field_definitions = storage_fields_processed.iter().map( |f| f.storage_field_optional() ); + let storage_field_defaults = storage_fields_processed.iter().map( |f| f.storage_fields_none() ); + + // Use enum's generics for storage phantom data + let phantom_field_type_storage = phantom::tuple( &enum_generics_ty ); + let implicit_storage_struct = quote! + { + #[ derive( Debug ) ] + #vis struct #implicit_storage_name < #enum_generics_impl > + where #enum_generics_where // Use enum's where clause + { + #( #storage_field_definitions, )* + _phantom : #phantom_field_type_storage, + } + impl< #enum_generics_impl > ::core::default::Default for #implicit_storage_name < #enum_generics_ty > + where #enum_generics_where // Use enum's where clause + { + #[ inline( always ) ] + fn default() -> Self { Self { #( #storage_field_defaults, )* _phantom: ::core::marker::PhantomData } } + } + }; + + // 2. Implicit StoragePreform + let storage_preform_fields = storage_fields_processed.iter().map( |f| f.storage_field_preform() ).collect::< Result< Vec<_> > >()?; + let storage_preform_field_names_vec : Vec<_> = storage_fields_processed.iter().map( |f| f.ident ).collect(); + // The preformed type is a tuple of the *actual* field types from the variant + let preformed_tuple_types = fields.named.iter().map( |f| &f.ty ); + let preformed_type = quote!{ ( #( #preformed_tuple_types ),* ) }; + + let implicit_storage_preform = quote! + { + impl< #enum_generics_impl > former::Storage for #implicit_storage_name < #enum_generics_ty > + where #enum_generics_where // Use enum's where clause + { + type Preformed = #preformed_type; + } + impl< #enum_generics_impl > former::StoragePreform for #implicit_storage_name < #enum_generics_ty > + where #enum_generics_where // Use enum's where clause + { + fn preform( mut self ) -> Self::Preformed + { + #( #storage_preform_fields )* + ( #( #storage_preform_field_names_vec ),* ) + } + } + }; + + // 3. Implicit DefinitionTypes + // Use helper to generate generics like <'a, T, Context2=(), Formed2=Enum<'a, T>> + 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( &generics_of_definition_types_renamed( &generics, enum_name, &enum_generics_ty )? ); + let former_definition_types_phantom = macro_tools::phantom::tuple( &former_definition_types_generics_impl ); + + let implicit_def_types = quote! + { + #[ derive( Debug ) ] + #vis struct #implicit_def_types_name < #former_definition_types_generics_with_defaults > + where #former_definition_types_generics_where // Merged where clause + { _phantom : #former_definition_types_phantom } + + impl < #former_definition_types_generics_impl > ::core::default::Default + for #implicit_def_types_name < #former_definition_types_generics_ty > + where #former_definition_types_generics_where // Merged where clause + { fn default() -> Self { Self { _phantom : ::core::marker::PhantomData } } } + + impl < #former_definition_types_generics_impl > former::FormerDefinitionTypes + for #implicit_def_types_name < #former_definition_types_generics_ty > + where #former_definition_types_generics_where // Merged where clause + { + type Storage = #implicit_storage_name < #enum_generics_ty >; // Storage uses enum generics + type Formed = Formed2; // Use renamed generic + type Context = Context2; // Use renamed generic + } + impl< #former_definition_types_generics_impl > former::FormerMutator + for #implicit_def_types_name < #former_definition_types_generics_ty > + where #former_definition_types_generics_where {} // Merged where clause + }; + + // 4. Implicit Definition + // Use helper to generate generics like <'a, T, Context2=(), Formed2=Enum<'a, T>, End2=EnumVariantEnd<'a, T>> + let ( former_definition_generics_with_defaults, former_definition_generics_impl, former_definition_generics_ty, former_definition_generics_where ) + = generic_params::decompose( &generics_of_definition_renamed( &generics, enum_name, &enum_generics_ty, &end_struct_name )? ); + let former_definition_phantom = macro_tools::phantom::tuple( &former_definition_generics_impl ); + + let implicit_def = quote! + { + #[ derive( Debug ) ] + #vis struct #implicit_def_name < #former_definition_generics_with_defaults > + where #former_definition_generics_where // Merged where clause + { _phantom : #former_definition_phantom } + + impl < #former_definition_generics_impl > ::core::default::Default + for #implicit_def_name < #former_definition_generics_ty > + where #former_definition_generics_where // Merged where clause + { fn default() -> Self { Self { _phantom : ::core::marker::PhantomData } } } + + impl < #former_definition_generics_impl > former::FormerDefinition + for #implicit_def_name < #former_definition_generics_ty > + where + End2 : former::FormingEnd< #implicit_def_types_name < #former_definition_types_generics_ty > >, // Use renamed End2 + #former_definition_generics_where // Merged where clause + { + type Types = #implicit_def_types_name < #former_definition_types_generics_ty >; + type End = End2; // Use renamed End2 + type Storage = #implicit_storage_name < #enum_generics_ty >; // Storage uses enum generics + type Formed = Formed2; // Use renamed Formed2 + type Context = Context2; // Use renamed Context2 + } + }; + + // 5. Implicit Former Struct + Setters + // Use helper to generate generics like <'a, T, Definition=...> where Definition : ... + let former_generics_result = generics_of_former_renamed + ( + &generics, + &implicit_def_name, + &implicit_storage_name, + &enum_generics_ty, + enum_name, + &end_struct_name + )?; + let ( former_generics_with_defaults, former_generics_impl, former_generics_ty, former_generics_where ) + = generic_params::decompose( &former_generics_result ); + + // Get where clause from the original enum generics + let default_where_predicates = syn::punctuated::Punctuated::< syn::WherePredicate, syn::token::Comma >::new(); + let variant_struct_where = variant_struct.generics.where_clause.as_ref().map_or + ( + &default_where_predicates, + | wc | &wc.predicates + ); + + // Generate setters using the implicit former's details + let setters = storage_fields_processed.iter().map( |f| + { + f.former_field_setter + ( + &variant_struct.ident, // Use the implicit former's name as the item context for setters + original_input, + &variant_struct.generics.params, // Use enum generics for the struct context + &variant_struct.generics.params, // Use enum generics for the struct context + variant_struct_where, // Use enum where clause + &implicit_former_name, // The former being defined + &former_generics_impl, // Its impl generics + &former_generics_ty, // Its type generics + &former_generics_where, // Its where clause + &implicit_storage_name, // Its storage + ) + }) + .collect::< Result< Vec<_> > >()?; + let ( former_field_setters, _namespace_code ) = setters.into_iter().unzip::<_, _, Vec<_>, Vec<_>>(); + + let implicit_former_struct = quote! + { + #[ doc = "Implicit former for the struct-like variant" ] + #vis struct #implicit_former_name < #former_generics_with_defaults > + where #former_generics_where // Use former's where clause + { + storage : Definition::Storage, + context : ::core::option::Option< Definition::Context >, + on_end : ::core::option::Option< Definition::End >, + } + + #[ automatically_derived ] + impl < #former_generics_impl > #implicit_former_name < #former_generics_ty > + where #former_generics_where // Use former's where clause + { + #[ 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( always ) ] pub fn begin + ( storage : ::core::option::Option< Definition::Storage >, context : ::core::option::Option< Definition::Context >, on_end : Definition::End ) -> Self + { Self { storage : storage.unwrap_or_default(), context, on_end : ::core::option::Option::Some( on_end ) } } + #[ inline( always ) ] pub fn new( on_end : Definition::End ) -> Self { Self::begin( None, None, on_end ) } + + #( #former_field_setters )* + } + }; + + // --- Generate End Struct and Impl --- + let phantom_field_type_end = phantom::tuple( &enum_generics_ty ); + let end_struct_def = quote! + { + #[ derive( Default, Debug ) ] + #vis struct #end_struct_name < #enum_generics_impl > + where #merged_where_clause // Use merged bounds + { + _phantom : #phantom_field_type_end, + } + }; + + // Construct the final enum variant using field names + let variant_construction = if fields.named.is_empty() + { quote! { #enum_name::#variant_ident {} } } + else + { quote! { #enum_name::#variant_ident { #( #storage_preform_field_names_vec ),* } } }; + + let end_impl = quote! + { + #[ automatically_derived ] + impl< #enum_generics_impl > former::FormingEnd + < + // Use DefinitionTypes of the *implicit* former + #implicit_def_types_name< #enum_generics_ty (), #enum_name< #enum_generics_ty > > + > + for #end_struct_name < #enum_generics_ty > + where + #merged_where_clause // Use merged bounds + { + #[ inline( always ) ] + fn call + ( + &self, + // Storage is from the *implicit* former + sub_storage : #implicit_storage_name< #enum_generics_ty >, + _context : Option< () >, // Context is () from static method + ) -> #enum_name< #enum_generics_ty > // Returns the Enum type + { + // Preform the tuple of fields from the implicit storage + let ( #( #storage_preform_field_names_vec ),* ) = former::StoragePreform::preform( sub_storage ); + // Construct the enum variant using the field names + #variant_construction + } + } + }; + + // --- Generate Static Starter Method --- + // FIX: Removed generics and where clause from method signature + let static_method = quote! + { + /// Starts forming the #variant_ident variant using its implicit subformer. + #[ inline( always ) ] + #vis fn #method_name () + // Return type is the *implicit* former... + -> #implicit_former_name + < + #enum_generics_ty // ...specialized with the enum's generics... + // ...and configured with a definition that uses the specialized End struct. + #implicit_def_name + < + #enum_generics_ty // Enum generics + (), // Context = () + #enum_name< #enum_generics_ty >, // Formed = Enum + #end_struct_name < #enum_generics_ty > // End = Specialized End + > + > + // where #merged_where_clause // Removed where clause from method + { + // Start the implicit former using its `begin` associated function. + // The End struct passed depends on the enum's generics. + #implicit_former_name::begin( None, None, #end_struct_name::< #enum_generics_ty >::default() ) + } + }; + + methods.push( static_method ); + end_impls.push + ( + quote! + { + #implicit_storage_struct + #implicit_storage_preform + #implicit_def_types + #implicit_def + #implicit_former_struct + #end_struct_def + #end_impl + } + ); + + } // End syn::Fields::Named + } // End match variant.fields + + } // End variant loop + + // Assemble the final impl block containing the generated static methods + let result = quote! + { + // Implement the static methods on the enum. + #[ automatically_derived ] + impl< #enum_generics_impl > #enum_name< #enum_generics_ty > + where + #enum_generics_where // <<< USE ORIGINAL ENUM BOUNDS HERE + { + #( #methods )* // Splice the collected methods here + } + + // Define the End structs, implicit formers, etc., outside the enum impl block. + #( #end_impls )* + }; + + if has_debug // Print generated code if #[debug] is present on the enum + { + let about = format!( "derive : Former\nenum : {enum_name}" ); + diag::report_print( about, original_input, &result ); + } + + Ok( result ) +} + +// Helper functions to generate generics for implicit definitions +// (These are simplified versions of what's used for structs) +// Renamed versions to avoid conflicts with struct helpers if they existed in the same scope. + +fn generics_of_definition_types_renamed // Renamed +( + enum_generics : &syn::Generics, + _enum_name : &syn::Ident, + enum_generics_ty : &syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >, +) -> Result< syn::Generics > +{ + // Use Context2, Formed2 + let extra : macro_tools::GenericsWithWhere = syn::parse_quote! + { + < Context2 = (), Formed2 = #_enum_name < #enum_generics_ty > > + }; + Ok( generic_params::merge( enum_generics, &extra.into() ) ) +} + +fn generics_of_definition_renamed // Renamed +( + enum_generics : &syn::Generics, + _enum_name : &syn::Ident, + enum_generics_ty : &syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >, + end_struct_name : &syn::Ident, +) -> Result< syn::Generics > +{ + // Use Context2, Formed2, End2 + let extra : macro_tools::GenericsWithWhere = syn::parse_quote! + { + < Context2 = (), Formed2 = #_enum_name < #enum_generics_ty >, End2 = #end_struct_name < #enum_generics_ty > > + }; + Ok( generic_params::merge( enum_generics, &extra.into() ) ) +} + +fn generics_of_former_renamed // Renamed +( + enum_generics : &syn::Generics, + implicit_def_name : &syn::Ident, + implicit_storage_name : &syn::Ident, + enum_generics_ty : &syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >, + enum_name : &syn::Ident, // Need enum name for default Formed type + end_struct_name : &syn::Ident, // Need end struct name for default End type +) -> Result< syn::Generics > +{ + let default_definition_type = quote! + { + #implicit_def_name < #enum_generics_ty (), #enum_name < #enum_generics_ty >, #end_struct_name < #enum_generics_ty > > + }; + + // Use Definition + let extra : macro_tools::GenericsWithWhere = syn::parse_quote! + { + < Definition = #default_definition_type > // Use the correctly constructed default + where + Definition : former::FormerDefinition< Storage = #implicit_storage_name < #enum_generics_ty > >, + Definition::Types : former::FormerDefinitionTypes< Storage = #implicit_storage_name < #enum_generics_ty > >, + }; + Ok( generic_params::merge( enum_generics, &extra.into() ) ) +} \ No newline at end of file diff --git a/module/core/former_meta/src/derive_former/former_struct.rs b/module/core/former_meta/src/derive_former/former_struct.rs new file mode 100644 index 0000000000..1762419862 --- /dev/null +++ b/module/core/former_meta/src/derive_former/former_struct.rs @@ -0,0 +1,568 @@ +// File: module/core/former_meta/src/derive_former/former_struct.rs + +#![ allow( clippy::wildcard_imports ) ] +use super::*; // Use items from parent module (derive_former.rs) +use iter_tools::Itertools; +use macro_tools:: +{ + generic_params, generic_args, derive, Result, + proc_macro2::TokenStream, quote::{ format_ident, quote }, +}; + +/// Generate the Former ecosystem for a struct. +#[ allow( clippy::too_many_lines ) ] +pub fn former_for_struct +( + ast : &syn::DeriveInput, + _data_struct : &syn::DataStruct, + original_input : &proc_macro::TokenStream, + _has_debug : bool, +) -> Result< TokenStream > +{ + use macro_tools::IntoGenericArgs; + + // Parse struct-level attributes like `storage_fields`, `mutator`, `perform`. + let struct_attrs = ItemAttributes::from_attrs( ast.attrs.iter() )?; + + /* names: Generate identifiers for the Former components based on the struct name. */ + let vis = &ast.vis; // Visibility of the original struct. + let item = &ast.ident; // Name of the original struct. + let former = format_ident!( "{item}Former" ); // e.g., MyStructFormer + let former_storage = format_ident!( "{item}FormerStorage" ); // e.g., MyStructFormerStorage + let former_definition = format_ident!( "{item}FormerDefinition" ); // e.g., MyStructFormerDefinition + let former_definition_types = format_ident!( "{item}FormerDefinitionTypes" ); // e.g., MyStructFormerDefinitionTypes + let as_subformer = format_ident!( "{item}AsSubformer" ); // e.g., MyStructAsSubformer + let as_subformer_end = format_ident!( "{item}AsSubformerEnd" ); // e.g., MyStructAsSubformerEnd + + // Generate documentation string for the AsSubformerEnd trait. + let as_subformer_end_doc = format! + ( + r" +Represents an end condition for former of [`${item}`], 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: Decompose the original struct's generics. */ + let generics = &ast.generics; + let + ( + struct_generics_with_defaults, // Generics with defaults (e.g., ``). Used for struct definition. + struct_generics_impl, // Generics for `impl` block (e.g., ``). Bounds, no defaults. + struct_generics_ty, // Generics for type usage (e.g., ``). Names only. + struct_generics_where // Where clause predicates (e.g., `T: Send`). + ) = generic_params::decompose( generics ); + + /* parameters for definition: Merge struct generics with default definition parameters. */ + let extra : macro_tools::syn::AngleBracketedGenericArguments = parse_quote! + { + < (), #item < #struct_generics_ty >, former::ReturnPreformed > // Default Context, Formed, End + }; + let former_definition_args = generic_args::merge( &generics.into_generic_args(), &extra ).args; + + /* parameters for former: Merge struct generics with the Definition generic parameter. */ + 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: Similar to former parameters, but specifically for the perform method. */ + let extra : macro_tools::GenericsWithWhere = parse_quote! + { + < Definition = #former_definition < #former_definition_args > > + where + Definition : former::FormerDefinition + < + Storage = #former_storage < #struct_generics_ty >, + Formed = #item < #struct_generics_ty >, + >, + Definition::Types : former::FormerDefinitionTypes + < + Storage = #former_storage < #struct_generics_ty >, + Formed = #item < #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: Merge struct generics with Context and Formed parameters. */ + let extra : macro_tools::GenericsWithWhere = parse_quote! + { + < __Context = (), __Formed = #item < #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 ); + // Generate PhantomData tuple type based on the impl generics. + let former_definition_types_phantom = macro_tools::phantom::tuple( &former_definition_types_generics_impl ); + + /* parameters for definition: Merge struct generics with Context, Formed, and End parameters. */ + let extra : macro_tools::GenericsWithWhere = parse_quote! + { + < __Context = (), __Formed = #item < #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 ); + // Generate PhantomData tuple type based on the impl generics. + let former_definition_phantom = macro_tools::phantom::tuple( &former_definition_generics_impl ); + + /* struct attributes: Generate documentation and extract perform method details. */ + let ( _doc_former_mod, doc_former_struct ) = doc_generate( item ); + let ( perform, perform_output, perform_generics ) = struct_attrs.performer()?; + + /* fields: Process struct fields and storage_fields attribute. */ + let fields = derive::named_fields( &ast )?; + // Create FormerField representation for actual struct fields. + let formed_fields : Vec< _ > = fields + .iter() + .map( | field | FormerField::from_syn( field, true, true ) ) + .collect::< Result< _ > >()?; + // Create FormerField representation for storage-only fields. + let storage_fields : Vec< _ > = struct_attrs + .storage_fields() + .iter() + .map( | field | FormerField::from_syn( field, true, false ) ) + .collect::< Result< _ > >()?; + + // Generate code snippets for each field (storage init, storage field def, preform logic, setters). + let + ( + storage_field_none, // Code for initializing storage field to None. + storage_field_optional, // Code for the storage field definition (e.g., `pub field: Option`). + storage_field_name, // Code for the field name (e.g., `field,`). Used in final struct construction. + storage_field_preform, // Code for unwrapping/defaulting the field in `preform`. + former_field_setter, // Code for the setter method(s) for the field. + ) + : + ( Vec< _ >, Vec< _ >, Vec< _ >, Vec< _ >, Vec< _ > ) + = formed_fields // Combine actual fields and storage-only fields for processing. + .iter() + .chain( storage_fields.iter() ) + .map( | field | {( + field.storage_fields_none(), + field.storage_field_optional(), + field.storage_field_name(), // Only generated if field.for_formed is true. + field.storage_field_preform(), // Only generated if field.for_formed is true. + field.former_field_setter + ( + item, + &original_input, + &struct_generics_impl, + &struct_generics_ty, + &struct_generics_where, + &former, + &former_generics_impl, + &former_generics_ty, + &former_generics_where, + &former_storage, + ), + )}).multiunzip(); + + // Collect results, separating setters and namespace code (like End structs). + let results : Result< Vec< _ > > = former_field_setter.into_iter().collect(); + let ( former_field_setter, namespace_code ) : ( Vec< _ >, Vec< _ > ) = results?.into_iter().unzip(); + // Collect preform logic results. + let storage_field_preform : Vec< _ > = storage_field_preform.into_iter().collect::< Result< _ > >()?; + // Generate mutator implementation code. + let former_mutator_code = mutator( item, &original_input, &struct_attrs.mutator, &former_definition_types, &former_definition_types_generics_impl, &former_definition_types_generics_ty, &former_definition_types_generics_where )?; + + // Assemble the final generated code using quote! + let result = quote! + { + + // = formed: Implement the `::former()` static method on the original struct. + #[ automatically_derived ] + impl < #struct_generics_impl > #item < #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: Implement former traits linking the struct to its generated components. + impl< #struct_generics_impl Definition > former::EntityToFormer< Definition > + for #item < #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 #item < #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 #item < #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 #item < #struct_generics_ty > + where + #struct_generics_where + { + type Types = #former_definition_types < #struct_generics_ty __Context, __Formed >; + } + + // = definition types: Define the FormerDefinitionTypes struct. + /// 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 : #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: Define the FormerDefinition struct. + /// 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 : #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: Implement the FormerMutator trait. + #former_mutator_code + + // = storage: Define the FormerStorage struct. + #[ 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 = #item < #struct_generics_ty >; + } + + impl < #struct_generics_impl > former::StoragePreform + for #former_storage < #struct_generics_ty > + where + #struct_generics_where + { + fn preform( mut self ) -> Self::Preformed + { + #( #storage_field_preform )* + let result = #item :: < #struct_generics_ty > + { + #( #storage_field_name )* + }; + return result; + } + } + + // = former: Define the Former struct itself. + #[ doc = #doc_former_struct ] + #vis struct #former < #former_generics_with_defaults > + where + #former_generics_where + { + /// Temporary storage for all fields during the formation process. + pub storage : Definition::Storage, + /// Optional context. + pub context : ::core::option::Option< Definition::Context >, + /// Optional handler for the end of formation. + 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( ::core::option::Option::None, ::core::option::Option::None, on_end ) + } + + /// Initializes a former with a coercible end condition. + #[ inline( always ) ] + pub fn new_coercing< IntoEnd > + ( + end : IntoEnd + ) -> Self + where + IntoEnd : ::core::convert::Into< Definition::End >, + { + Self::begin_coercing + ( + ::core::option::Option::None, + ::core::option::Option::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 = ::core::option::Option::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 = ::core::option::Option::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 ) + } + + // Insert generated setter methods for each field. + #( + #former_field_setter + )* + + } + + // = former :: preform: Implement `preform` for direct storage transformation. + impl< #former_generics_impl > #former< #former_generics_ty > + where + Definition : former::FormerDefinition< Storage = #former_storage < #struct_generics_ty >, Formed = #item < #struct_generics_ty > >, + Definition::Types : former::FormerDefinitionTypes< Storage = #former_storage < #struct_generics_ty >, Formed = #item < #struct_generics_ty > >, + #former_generics_where + { + /// Executes the transformation from the former's storage state to the preformed object. + pub fn preform( self ) -> < Definition::Types as former::FormerDefinitionTypes >::Formed + { + former::StoragePreform::preform( self.storage ) + } + } + + // = former :: perform: Implement `perform` if specified by attributes. + #[ 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. + #[ inline( always ) ] + pub fn perform #perform_generics ( self ) -> #perform_output + { + let result = self.form(); + #perform + } + } + + // = former begin: Implement `FormerBegin` trait. + impl< #struct_generics_impl Definition > former::FormerBegin< 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( ::core::option::Option::None, context, on_end ) + } + } + + // = subformer: Define the `AsSubformer` type alias. + /// Provides a specialized former for structure using predefined settings for superformer and end conditions. + // #vis type #as_subformer < #struct_generics_impl __Superformer, __End > = #former + #vis type #as_subformer < #struct_generics_ty __Superformer, __End > = #former + < + #struct_generics_ty + #former_definition + < + #struct_generics_ty + __Superformer, + __Superformer, + __End, + >, + >; + + + // = as subformer end: Define the `AsSubformerEnd` trait. + #[ 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: Insert any namespace code generated by field setters (e.g., End structs for subformers). + #( #namespace_code )* + + }; + Ok( result ) +} diff --git a/module/core/former_meta/src/derive_former/struct_attrs.rs b/module/core/former_meta/src/derive_former/struct_attrs.rs index 4647f14c6d..393ca9e0d9 100644 --- a/module/core/former_meta/src/derive_former/struct_attrs.rs +++ b/module/core/former_meta/src/derive_former/struct_attrs.rs @@ -152,7 +152,7 @@ impl ItemAttributes | attr | &attr.fields ) - // qqq : find better solutioin + // zzz : qqq : find better solutioin // self.storage_fields // .as_ref() diff --git a/module/core/former_types/Cargo.toml b/module/core/former_types/Cargo.toml index 54c5034202..885adf84cf 100644 --- a/module/core/former_types/Cargo.toml +++ b/module/core/former_types/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "former_types" -version = "2.14.0" +version = "2.15.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/former_types/License b/module/core/former_types/License index c32986cee3..a23529f45b 100644 --- a/module/core/former_types/License +++ b/module/core/former_types/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/fs_tools/License b/module/core/fs_tools/License index c32986cee3..a23529f45b 100644 --- a/module/core/fs_tools/License +++ b/module/core/fs_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/implements/Cargo.toml b/module/core/implements/Cargo.toml index 8d97aaedb4..30d51da328 100644 --- a/module/core/implements/Cargo.toml +++ b/module/core/implements/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "implements" -version = "0.11.0" +version = "0.12.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/implements/License b/module/core/implements/License index c32986cee3..a23529f45b 100644 --- a/module/core/implements/License +++ b/module/core/implements/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/impls_index/Cargo.toml b/module/core/impls_index/Cargo.toml index fc8c02eca0..a7c95c45aa 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.9.0" +version = "0.10.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/impls_index/License b/module/core/impls_index/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/impls_index/License +++ b/module/core/impls_index/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/impls_index_meta/Cargo.toml b/module/core/impls_index_meta/Cargo.toml index 00668cfd7b..8ce0f551de 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.10.0" +version = "0.12.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -28,12 +28,17 @@ all-features = false [features] default = [ "enabled" ] full = [ "enabled" ] -enabled = [ "macro_tools/enabled" ] +# The 'enabled' feature no longer depends on macro_tools +enabled = [] [lib] proc-macro = true [dependencies] -macro_tools = { workspace = true, features = [ "name", "quantifier" ] } +# macro_tools dependency removed +# Direct dependencies added using workspace inheritance and minimal features +proc-macro2 = { workspace = true, default-features = false, features = [ "default" ] } # Inherits version and settings from workspace +quote = { workspace = true, default-features = false, features = [ "default" ] } # Inherits version and settings from workspace +syn = { workspace = true, default-features = false, features = [ "parsing", "printing", "proc-macro", "full" ] } # Inherits version, specifies features inline [dev-dependencies] diff --git a/module/core/impls_index_meta/License b/module/core/impls_index_meta/License index c32986cee3..a23529f45b 100644 --- a/module/core/impls_index_meta/License +++ b/module/core/impls_index_meta/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/impls_index_meta/src/impls.rs b/module/core/impls_index_meta/src/impls.rs index 1a59eb4a2e..0520d1e750 100644 --- a/module/core/impls_index_meta/src/impls.rs +++ b/module/core/impls_index_meta/src/impls.rs @@ -1,37 +1,141 @@ +use proc_macro2::TokenStream; +use quote::{ quote, ToTokens }; +use syn:: +{ + parse::{ Parse, ParseStream }, + Result, // Use syn's Result directly + Token, + Item, + spanned::Spanned, // Import Spanned trait for error reporting +}; +use core::fmt; // Import fmt for manual Debug impl if needed + +// --- Local replacements for macro_tools types/traits --- + +/// Marker trait used to indicate how to parse multiple elements. +trait AsMuchAsPossibleNoDelimiter {} + +/// Wrapper for parsing multiple elements. +// No derive(Debug) here as T might not implement Debug +pub struct Many< T : ToTokens >( pub Vec< T > ); + +// Manual Debug implementation for Many if T implements Debug +// If T doesn't implement Debug, this won't compile, but it's better than deriving +impl< T > fmt::Debug for Many< T > +where T: ToTokens + fmt::Debug +{ + fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result + { + f.debug_tuple( "Many" ).field( &self.0 ).finish() + } +} + + +impl< T > Many< T > +where + T : ToTokens, +{ + /// Iterator over the contained elements. + pub fn iter( &self ) -> core::slice::Iter< '_, T > + { + self.0.iter() + } +} + +impl< T > IntoIterator for Many< T > +where + T : ToTokens, +{ + type Item = T; + type IntoIter = std::vec::IntoIter< Self::Item >; + fn into_iter( self ) -> Self::IntoIter + { + self.0.into_iter() + } +} + +impl< 'a, T > IntoIterator for &'a Many< T > +where + T : ToTokens, +{ + type Item = &'a T; + type IntoIter = core::slice::Iter< 'a, T >; + fn into_iter( self ) -> Self::IntoIter + { + self.0.iter() + } +} + +impl< T > quote::ToTokens for Many< T > +where + T : ToTokens, +{ + fn to_tokens( &self, tokens : &mut TokenStream ) + { + for item in &self.0 + { + item.to_tokens( tokens ); + } + } +} -use macro_tools::{ Result, Many, AsMuchAsPossibleNoDelimiter }; -use macro_tools::prelude::*; +// --- Original code adapted --- /// /// Module-specific item. +/// Represents an optional `?` followed by a `syn::Item`. /// - -#[ derive( Debug ) ] +// Removed #[derive(Debug)] pub struct Item2 { pub optional : Option< Token![ ? ] >, pub func : syn::Item, } +// Manual Debug implementation for Item2 +impl fmt::Debug for Item2 +{ + fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result + { + f.debug_struct( "Item2" ) + .field( "optional", &self.optional.is_some() ) // Debug only if present + .field( "func", &self.func.to_token_stream().to_string() ) // Debug func as string + .finish() + } +} + + +// Implement the marker trait for Item2 to use in Many's parse impl. impl AsMuchAsPossibleNoDelimiter for Item2 {} // -impl syn::parse::Parse for Item2 +impl Parse for Item2 { - fn parse( input : syn::parse::ParseStream< '_ > ) -> Result< Self > + fn parse( input : ParseStream< '_ > ) -> Result< Self > { - let optional = input.parse()?; - let func = input.parse()?; - Ok( Self{ optional, func } ) + // Look for an optional '?' token first + let optional : Option< Token![ ? ] > = input.parse()?; + + // Parse the item (expected to be a function, but we parse Item for flexibility) + let func : Item = input.parse()?; + + // Ensure the parsed item is a function + if !matches!( func, Item::Fn( _ ) ) + { + // Use spanned for better error location + return Err( syn::Error::new( func.span(), "Expected a function item" ) ); + } + + Ok( Self { optional, func } ) } } // -impl quote::ToTokens for Item2 +impl ToTokens for Item2 { - fn to_tokens( &self, tokens : &mut proc_macro2::TokenStream ) + fn to_tokens( &self, tokens : &mut TokenStream ) { self.optional.to_tokens( tokens ); self.func.to_tokens( tokens ); @@ -40,76 +144,116 @@ impl quote::ToTokens for Item2 // -#[ derive( Debug ) ] +// No derive(Debug) here as Item2 does not derive Debug anymore pub struct Items2 ( pub Many< Item2 >, ); +// Manual Debug implementation for Items2 +impl fmt::Debug for Items2 +{ + fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result + { + f.debug_tuple( "Items2" ).field( &self.0 ).finish() + } +} + + +// Implement Parse for Many specifically +// because Item2 implements AsMuchAsPossibleNoDelimiter +impl< T > Parse for Many< T > +where + T : Parse + ToTokens + AsMuchAsPossibleNoDelimiter, +{ + fn parse( input : ParseStream< '_ > ) -> Result< Self > + { + let mut items = Vec::new(); + // Continue parsing as long as the input stream is not empty + while !input.is_empty() + { + // Parse one element of type T + let item : T = input.parse()?; + items.push( item ); + } + Ok( Self( items ) ) + } +} + // -impl syn::parse::Parse for Items2 +impl Parse for Items2 { - fn parse( input : syn::parse::ParseStream< '_ > ) -> Result< Self > + fn parse( input : ParseStream< '_ > ) -> Result< Self > { - let many = input.parse()?; + let many : Many< Item2 > = input.parse()?; Ok( Self( many ) ) } } // -impl quote::ToTokens for Items2 +impl ToTokens for Items2 { - fn to_tokens( &self, tokens : &mut proc_macro2::TokenStream ) + fn to_tokens( &self, tokens : &mut TokenStream ) { self.0.iter().for_each( | e | { - let func = &e.func; + // Extract the function item specifically + let func = match &e.func + { + Item::Fn( func_item ) => func_item, + // Use spanned for better error location if this panic ever occurs + _ => panic!( "Internal error: Item2 should always contain a function item at {:?}", e.func.span() ), + }; + + // Get the function name identifier + let name_ident = &func.sig.ident; + // Removed unused name_str + // let name_str = name_ident.to_string(); - let declare_aliased = qt! + // Construct the macro definition + let declare_aliased = quote! { ( as $Name2 : ident ) => { + // Note: impls_index::fn_rename! is external, assuming it exists impls_index::fn_rename! { @Name { $Name2 } @Fn { - #func + #func // Use the full function item here } } }; }; - let mut mandatory = qt! + let mut mandatory = quote! { #[ allow( unused_macros ) ] }; if e.optional.is_none() { - mandatory = qt! + mandatory = quote! { #[ deny( unused_macros ) ] } } - let name_str = func.name(); - let name_ident = syn::Ident::new( &name_str[ .. ], proc_macro2::Span::call_site() ); - let result = qt! + let result = quote! { #mandatory - macro_rules! #name_ident + macro_rules! #name_ident // Use the original function identifier { #declare_aliased () => { - #func + #func // Use the full function item here }; } }; - // tree_print!( result ); result.to_tokens( tokens ) }); } @@ -117,14 +261,14 @@ impl quote::ToTokens for Items2 // -pub fn impls( input : proc_macro::TokenStream ) -> Result< proc_macro2::TokenStream > +pub fn impls( input : proc_macro::TokenStream ) -> Result< TokenStream > { - let items2 = syn::parse::< Items2 >( input )?; + let items2 : Items2 = syn::parse( input )?; - let result = qt! + let result = quote! { #items2 }; Ok( result ) -} +} \ No newline at end of file diff --git a/module/core/include_md/License b/module/core/include_md/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/include_md/License +++ b/module/core/include_md/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/inspect_type/Cargo.toml b/module/core/inspect_type/Cargo.toml index cb35a5ecc8..297dd27022 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.13.0" +version = "0.14.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/inspect_type/License b/module/core/inspect_type/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/inspect_type/License +++ b/module/core/inspect_type/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/interval_adapter/Cargo.toml b/module/core/interval_adapter/Cargo.toml index 147d0f621f..5a7fbd8a4f 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.28.0" +version = "0.29.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/interval_adapter/License b/module/core/interval_adapter/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/interval_adapter/License +++ b/module/core/interval_adapter/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/is_slice/Cargo.toml b/module/core/is_slice/Cargo.toml index cde98e4390..d0a7953965 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.12.0" +version = "0.13.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/is_slice/License b/module/core/is_slice/License index c32986cee3..a23529f45b 100644 --- a/module/core/is_slice/License +++ b/module/core/is_slice/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/iter_tools/Cargo.toml b/module/core/iter_tools/Cargo.toml index a909fca7c0..be262d50c1 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.26.0" +version = "0.28.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/iter_tools/License b/module/core/iter_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/iter_tools/License +++ b/module/core/iter_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/macro_tools/Cargo.toml b/module/core/macro_tools/Cargo.toml index 7d98c4f745..d26ea79c59 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.46.0" +version = "0.52.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -38,6 +38,7 @@ default = [ "equation", "generic_args", "generic_params", + "ident", "item", "item_struct", "name", @@ -71,6 +72,7 @@ diag = [] equation = [] generic_args = [] generic_params = [ "punctuated" ] +ident = [ "kw" ] item = [ "punctuated" ] item_struct = [] iter = [] @@ -97,10 +99,16 @@ typed = [] [dependencies] ## external -proc-macro2 = { version = "~1.0.78", features = [] } -quote = { version = "~1.0.35", features = [] } -syn = { version = "~2.0.52", features = [ "full", "extra-traits" ] } -const_format = { version = "0.2.32", features = [] } +# proc-macro2 = { version = "~1.0.78", default-features = false, features = [] } +# quote = { version = "~1.0.35", default-features = false, features = [] } +# syn = { version = "~2.0.52", default-features = false, features = [ "full", "extra-traits" ] } # qqq : xxx : optimize set of features +# const_format = { version = "0.2.32", default-features = false, features = [] } + +# external +proc-macro2 = { workspace = true, default-features = false, features = [ "default" ] } +quote = { workspace = true, default-features = false, features = [ "default" ] } +syn = { workspace = true, default-features = false, features = [ "clone-impls", "full", "derive", "parsing", "printing", "proc-macro", "extra-traits" ] } # qqq : xxx : optimize set of features, bind features of dependecies to features of this crate, optimally +const_format = { workspace = true, default-features = false, features = [] } ## internal interval_adapter = { workspace = true, features = [] } @@ -108,5 +116,5 @@ iter_tools = { workspace = true, features = [ "iter_trait" ] } clone_dyn_types = { workspace = true, features = [] } former_types = { workspace = true, features = [ "types_component_assign" ] } -# [dev-dependencies] -# test_tools = { workspace = true } +[dev-dependencies] +test_tools = { workspace = true } # Added test_tools dependency diff --git a/module/core/macro_tools/License b/module/core/macro_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/macro_tools/License +++ b/module/core/macro_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/macro_tools/src/container_kind.rs b/module/core/macro_tools/src/container_kind.rs index 73423dc5de..32aae90f93 100644 --- a/module/core/macro_tools/src/container_kind.rs +++ b/module/core/macro_tools/src/container_kind.rs @@ -40,7 +40,7 @@ mod private /// let kind = container_kind::of_type( &tree_type ); /// assert_eq!( kind, container_kind::ContainerKind::HashMap ); /// ``` - /// # Panics + /// # Panics /// qqq: doc #[ must_use ] pub fn of_type( ty : &syn::Type ) -> ContainerKind @@ -86,7 +86,7 @@ mod private if typ::type_rightmost( ty ) == Some( "Option".to_string() ) { - let ty2 = typ::type_parameters( ty, &( 0 ..= 0 ) ).first().copied(); + let ty2 = typ::type_parameters( ty, 0 ..= 0 ).first().copied(); // inspect_type::inspect_type_of!( ty2 ); if ty2.is_none() { diff --git a/module/core/macro_tools/src/diag.rs b/module/core/macro_tools/src/diag.rs index 1a183908ea..0a9f0e8608 100644 --- a/module/core/macro_tools/src/diag.rs +++ b/module/core/macro_tools/src/diag.rs @@ -2,6 +2,7 @@ //! Macro helpers. //! + /// Define a private namespace for all its items. mod private { diff --git a/module/core/macro_tools/src/ident.rs b/module/core/macro_tools/src/ident.rs new file mode 100644 index 0000000000..f821d0097d --- /dev/null +++ b/module/core/macro_tools/src/ident.rs @@ -0,0 +1,92 @@ +//! +//! Utilities for manipulating identifiers, including keyword handling. +//! + +/// Define a private namespace for all its items. +mod private +{ + #[ allow( clippy::wildcard_imports ) ] + use crate::*; // Use crate's prelude/exposed items + use proc_macro2::Ident; + // use syn::spanned::Spanned; // Needed for span + + /// Creates a new identifier, adding the `r#` prefix if the input identifier's + /// string representation is a Rust keyword. + /// + /// Preserves the span of the original identifier. + /// Requires the `kw` feature. + /// + /// # Example + /// ```rust + /// use macro_tools::{ syn, format_ident, ident }; + /// + /// let ident_normal = format_ident!( "my_var" ); + /// let ident_keyword = format_ident!( "fn" ); + /// + /// let got_normal = ident::ident_maybe_raw( &ident_normal ); + /// let got_keyword = ident::ident_maybe_raw( &ident_keyword ); + /// + /// assert_eq!( got_normal.to_string(), "my_var" ); + /// assert_eq!( got_keyword.to_string(), "r#fn" ); + /// ``` + pub fn ident_maybe_raw( ident : &syn::Ident ) -> Ident + { + let name = ident.to_string(); + if kw::is( &name ) + { + // Use r# prefix if the name is a keyword + format_ident!( "r#{}", name, span = ident.span() ) + } + else + { + // Otherwise, use the name directly (cloned) + ident.clone() + } + } +} + +#[ doc( inline ) ] +#[ allow( unused_imports ) ] +pub use own::*; + +/// Own namespace of the module. +#[ allow( unused_imports ) ] +pub mod own +{ + #[ allow( clippy::wildcard_imports ) ] + use super::*; + #[ doc( inline ) ] + pub use orphan::*; + #[ doc( inline ) ] + pub use private::ident_maybe_raw; // Export the renamed function +} + +/// Orphan namespace of the module. +#[ allow( unused_imports ) ] +pub mod orphan +{ + #[ allow( clippy::wildcard_imports ) ] + use super::*; + #[ doc( inline ) ] + pub use exposed::*; +} + +/// Exposed namespace of the module. +#[ allow( unused_imports ) ] +pub mod exposed +{ + #[ allow( clippy::wildcard_imports ) ] + use super::*; + pub use super::super::ident; // Use the new module name + + #[ doc( inline ) ] + pub use prelude::*; +} + +/// Prelude to use essentials: `use my_module::prelude::*`. +#[ allow( unused_imports ) ] +pub mod prelude +{ + #[ allow( clippy::wildcard_imports ) ] + use super::*; +} diff --git a/module/core/macro_tools/src/lib.rs b/module/core/macro_tools/src/lib.rs index eed453737b..1176755c8c 100644 --- a/module/core/macro_tools/src/lib.rs +++ b/module/core/macro_tools/src/lib.rs @@ -40,6 +40,8 @@ pub mod equation; pub mod generic_args; #[ cfg( all( feature = "enabled", feature = "generic_params" ) ) ] pub mod generic_params; +#[ cfg( all( feature = "enabled", feature = "ident" ) ) ] // Use new feature name +pub mod ident; // Use new module name #[ cfg( all( feature = "enabled", feature = "item" ) ) ] pub mod item; #[ cfg( all( feature = "enabled", feature = "item_struct" ) ) ] @@ -126,6 +128,8 @@ pub mod own pub use generic_args::orphan::*; #[ cfg( feature = "generic_params" ) ] pub use generic_params::orphan::*; + #[ cfg( feature = "ident" ) ] // Use new feature name + pub use ident::orphan::*; // Use new module name #[ cfg( feature = "item" ) ] pub use item::orphan::*; #[ cfg( feature = "item_struct" ) ] @@ -160,7 +164,7 @@ pub mod own /// Parented namespace of the module. #[ cfg( feature = "enabled" ) ] - #[ allow( unused_imports ) ] +#[ allow( unused_imports ) ] pub mod orphan { use super::*; @@ -210,6 +214,8 @@ pub mod exposed pub use generic_args::exposed::*; #[ cfg( feature = "generic_params" ) ] pub use generic_params::exposed::*; + #[ cfg( feature = "ident" ) ] // Use new feature name + pub use ident::exposed::*; // Use new module name #[ cfg( feature = "item" ) ] pub use item::exposed::*; #[ cfg( feature = "item_struct" ) ] @@ -275,6 +281,8 @@ pub mod prelude pub use generic_args::prelude::*; #[ cfg( feature = "generic_params" ) ] pub use generic_params::prelude::*; + #[ cfg( feature = "ident" ) ] // Use new feature name + pub use ident::prelude::*; // Use new module name #[ cfg( feature = "item" ) ] pub use item::prelude::*; #[ cfg( feature = "item_struct" ) ] @@ -352,6 +360,4 @@ pub mod prelude parse_quote_spanned as parse_qt_spanned, }; -} - -// qqq : introduce features. make it smart. discuss list of features before implementing +} \ No newline at end of file diff --git a/module/core/macro_tools/src/typ.rs b/module/core/macro_tools/src/typ.rs index 3a2e710808..7cdd7ebc6e 100644 --- a/module/core/macro_tools/src/typ.rs +++ b/module/core/macro_tools/src/typ.rs @@ -59,7 +59,7 @@ mod private /// # Panics /// qqq: doc #[ allow( clippy::cast_possible_wrap ) ] - pub fn type_parameters< 'a >( ty : &'a syn::Type, range : &'a impl NonIterableInterval ) -> Vec< &'a syn::Type > + pub fn type_parameters< 'a >( ty : &'a syn::Type, range : impl NonIterableInterval ) -> Vec< &'a syn::Type > { if let syn::Type::Path( syn::TypePath{ path : syn::Path { ref segments, .. }, .. } ) = ty { @@ -133,7 +133,7 @@ mod private /// qqq: docs pub fn parameter_first( ty : &syn::Type ) -> Result< &syn::Type > { - typ::type_parameters( ty, &( 0 ..= 0 ) ) + typ::type_parameters( ty, 0 ..= 0 ) .first() .copied() .ok_or_else( || syn_err!( ty, "Expects at least one parameter here:\n {}", qt!{ #ty } ) ) diff --git a/module/core/macro_tools/tests/inc/derive_test.rs b/module/core/macro_tools/tests/inc/derive_test.rs index 9142c0cadd..c553e61b47 100644 --- a/module/core/macro_tools/tests/inc/derive_test.rs +++ b/module/core/macro_tools/tests/inc/derive_test.rs @@ -3,7 +3,7 @@ use super::*; // -#[test] +#[ test ] fn named_fields_with_named_fields() { use syn::{ parse_quote, punctuated::Punctuated, Field, token::Comma }; diff --git a/module/core/macro_tools/tests/inc/ident_test.rs b/module/core/macro_tools/tests/inc/ident_test.rs new file mode 100644 index 0000000000..58b7f33ed0 --- /dev/null +++ b/module/core/macro_tools/tests/inc/ident_test.rs @@ -0,0 +1,53 @@ +use super::*; +use the_module::{ format_ident, ident }; + +#[ test ] +fn ident_maybe_raw_non_keyword() +{ + let input = format_ident!( "my_variable" ); + let expected = format_ident!( "my_variable" ); + let got = ident::ident_maybe_raw( &input ); + assert_eq!( got, expected ); + assert_eq!( got.to_string(), "my_variable" ); +} + +#[ test ] +fn ident_maybe_raw_keyword_fn() +{ + let input = format_ident!( "fn" ); + let expected = format_ident!( "r#fn" ); + let got = ident::ident_maybe_raw( &input ); + assert_eq!( got, expected ); + assert_eq!( got.to_string(), "r#fn" ); +} + +#[ test ] +fn ident_maybe_raw_keyword_struct() +{ + let input = format_ident!( "struct" ); + let expected = format_ident!( "r#struct" ); + let got = ident::ident_maybe_raw( &input ); + assert_eq!( got, expected ); + assert_eq!( got.to_string(), "r#struct" ); +} + +#[ test ] +fn ident_maybe_raw_keyword_break() +{ + let input = format_ident!( "break" ); + let expected = format_ident!( "r#break" ); + let got = ident::ident_maybe_raw( &input ); + assert_eq!( got, expected ); + assert_eq!( got.to_string(), "r#break" ); +} + +#[ test ] +fn ident_maybe_raw_non_keyword_but_looks_like() +{ + // Ensure it only checks the exact string, not variations + let input = format_ident!( "break_point" ); + let expected = format_ident!( "break_point" ); + let got = ident::ident_maybe_raw( &input ); + assert_eq!( got, expected ); + assert_eq!( got.to_string(), "break_point" ); +} \ No newline at end of file diff --git a/module/core/macro_tools/tests/inc/mod.rs b/module/core/macro_tools/tests/inc/mod.rs index c28692fdf0..a7e982e2c8 100644 --- a/module/core/macro_tools/tests/inc/mod.rs +++ b/module/core/macro_tools/tests/inc/mod.rs @@ -1,7 +1,4 @@ - -#[ allow( unused_imports ) ] use super::*; -#[ allow( unused_imports ) ] use test_tools::exposed::*; #[ allow( unused_imports ) ] @@ -32,6 +29,8 @@ mod if_enabled mod generic_args_test; #[ cfg( feature = "generic_params" ) ] mod generic_params_test; + #[ cfg( feature = "ident" ) ] // Use new feature name + mod ident_test; // Add the new test file #[ cfg( feature = "item" ) ] mod item_test; #[ cfg( feature = "item_struct" ) ] @@ -47,4 +46,4 @@ mod if_enabled #[ cfg( feature = "typ" ) ] mod typ_test; -} +} \ No newline at end of file diff --git a/module/core/macro_tools/tests/tests.rs b/module/core/macro_tools/tests/tests.rs index dc27d22258..897b843de3 100644 --- a/module/core/macro_tools/tests/tests.rs +++ b/module/core/macro_tools/tests/tests.rs @@ -1,6 +1,7 @@ -#[ allow( unused_imports ) ] +//! All tests +#![ allow( unused_imports ) ] + use macro_tools as the_module; -#[ allow( unused_imports ) ] -use test_tools::exposed::*; +// use test_tools::exposed::*; mod inc; diff --git a/module/core/mem_tools/License b/module/core/mem_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/mem_tools/License +++ b/module/core/mem_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/meta_tools/License b/module/core/meta_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/meta_tools/License +++ b/module/core/meta_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/mod_interface/Cargo.toml b/module/core/mod_interface/Cargo.toml index 5853e418e2..531ecd4a19 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.31.0" +version = "0.33.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/mod_interface/License b/module/core/mod_interface/License index c32986cee3..a23529f45b 100644 --- a/module/core/mod_interface/License +++ b/module/core/mod_interface/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/mod_interface/Readme.md b/module/core/mod_interface/Readme.md index 53e887fdd3..32f5dc7612 100644 --- a/module/core/mod_interface/Readme.md +++ b/module/core/mod_interface/Readme.md @@ -5,51 +5,54 @@ [![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. +Provides the `mod_interface!` macro to define structured module interfaces with controlled visibility and propagation, simplifying the creation of layered architectures in Rust. -### Problem Solved +### Overview -The `mod_interface` crate provides a structured approach to modularity, addressing two key challenges in software development: +The `mod_interface` crate introduces a procedural macro (`mod_interface!`) designed to streamline module organization in Rust projects. It helps address common challenges in maintaining complex codebases: -1. **Meaningful Namespace Structuring**: The crate enables developers to organize program entities into meaningful namespaces ( read modules ) without additional development overhead. This is achieved through a set of auto-importing rules and a flexible inversion of control mechanism, allowing parent layers ( namespaces or modules ) to delegate control over its items to child layers. This approach ensures that each namespace is self-contained and meaningful, promoting better organization and modularity. +1. **Structured Interfaces**: Define clear boundaries and relationships between modules (layers) using predefined exposure levels. This promotes a layered architecture where visibility and propagation of items are explicitly controlled. +2. **Reduced Boilerplate**: The macro automatically generates the necessary `use` statements and module structures based on simple directives, reducing manual effort and potential errors. +3. **Improved Readability**: By encouraging the explicit definition of a module's interface and how its items are exposed, the crate helps make the codebase easier to understand, navigate, and refactor, reducing cognitive load. -2. **Enhanced Readability and Tooling Independence**: By requiring a `mod private` section that lists all items ( read functions, structures, traits, types ) the `mod_interface` macro encourages developers to create a concise list of items at the beginning or end of a file. This improves readability, encourages refactoring, and reduces cognitive load by providing a clear, high-level grouping of items. Code tooling is not always reliable and can sometimes be counterproductive by automating tasks that should be done manually to achieve more concise code. While code tooling like `rust_analyzer` are useful, this approach minimizes reliance on them, making the program's structure easier to understand and manage. - -While some may argue that inversion of control over namespaces may not always achieve the desired outcome, and code tooling can be sufficient, the `mod_interface` crate offers a cathartic solution for designing complex systems where tooling and triditional structuring often fall short. By promoting a clear and organized structure, it helps developers grasp the semantics of their programs more holistically. +It offers a convention-based approach to modularity, particularly useful for designing complex systems where clear structure and controlled visibility are paramount. ### Basic Concepts In the `mod_interface` crate, the concepts of layers and namespaces are central to its modularity approach. Here's a refined explanation: - **Namespaces**: These are standard Rust modules that help organize code into logical groups. -- **Layers**: A layer is a specialized module that contains a set of predefined submodules, referred to as chapters. These chapters dictate how the contents of the module are propagated to parent layers. +- **Layers**: A layer is a specialized module structured using `mod_interface!`. It contains a set of predefined submodules, referred to as **Exposure Levels**, which dictate how the contents of the module are propagated to parent layers. -The chapters within a layer are: +The Exposure Levels within a layer determine the visibility and propagation scope: -- **Private Chapter (`private`)**: This is where all the code and entities are initially defined. It is not accessible outside the module. -- **Public Chapter (`own`)**: Contains items that are not propagated to any parent layers. They remain within the module. -- **Public Chapter (`orphan`)**: Shares its contents with the immediate parent layer only. -- **Public Chapter (`exposed`)**: Propagates its contents to all parent layers, making them accessible throughout the hierarchy. -- **Public Chapter (`prelude`)**: Similar to `exposed`, but also serves as a recommendation for other crates to implicitly import its contents, akin to the prelude in the [Rust standard library](https://doc.rust-lang.org/std/prelude/index.html). +| Level | Propagation Scope | Purpose | +| :-------- | :---------------------------- | :----------------------------------- | +| `private` | Internal only | Original definitions | +| `own` | Layer only (no propagation) | Layer-specific public items | +| `orphan` | Immediate parent | Items for direct parent | +| `exposed` | All ancestors | Items for hierarchy use | +| `prelude` | All ancestors + intended glob | Core interface essentials (glob use) | -Developers should define all entities within the `private` chapter and then re-export them through the other four chapters based on the desired propagation strategy. +Developers should define all entities within the `private` submodule and then re-export them through the other four exposure levels (`own`, `orphan`, `exposed`, `prelude`) based on the desired propagation strategy. ### Syntax of `mod_interface` Macro The `mod_interface` macro provides several directives to manage the relationships between layers and entities: -- **`layer `**: Establishes a relationship where the current layer uses a child layer. -- **`use `**: Allows the current layer to use another layer defined elsewhere. -- **`reuse `**: Enables the current layer to reuse a layer defined anywhere, promoting code reuse. -- **` use `**: Allows the current layer to use an entity defined anywhere, with the specified promotion stategy (``). +- **`layer `**: Define and include `.rs` (or `/mod.rs`) as a child layer within the current module. +- **`use `**: Integrate an existing module at `` as a layer into the current module's interface. +- **`reuse `**: Similar to `use`, integrates an existing module layer, potentially with slightly different propagation rules intended for reusing common interfaces. +- **` use `**: Re-export `` (from `private` or elsewhere) into the specified exposure level (`own`, `orphan`, `exposed`, or `prelude`). +- **` mod `**: Define `.rs` (or `/mod.rs`) as a "micro module" and include its contents directly into the specified exposure level. These directives provide flexibility in organizing and managing the modular structure of a Rust program, enhancing both readability and maintainability. ### Example: Using Layers and Entities -In this example, we demonstrate the basic use case of one layer utilizing another layer. For a module to be used as a layer, it must contain all the necessary chapters: `orphan`, `exposed`, and `prelude`. Generally, a layer should also have the `own` and `private` chapters, but these are typically not modified directly by the user unless explicitly defined, with the `private` chapter remaining inaccessible from outside the module. +This example shows a parent module using a `child` layer, demonstrating how items propagate based on their assigned exposure level. -Below is a simple example where a parent layer imports a `child` layer. The `child` layer defines several functions, each with a different propagation strategy, resulting in each function being placed in a different chapter of the parent layer, while some functions do not reach the parent layer at all. +For a module to be used as a layer, it must contain the necessary exposure levels (`private`, `own`, `orphan`, `exposed`, `prelude`). The `mod_interface!` macro helps generate these. ```rust use mod_interface::mod_interface; @@ -61,18 +64,29 @@ pub mod child // Define a private namespace for all its items. mod private { - /// Only my thing. - pub fn my_thing() -> bool { true } - /// Parent module should also has this thing. - pub fn orphan_thing() -> bool { true } - /// This thing should be exposed. - pub fn exposed_thing() -> bool { true } - /// This thing should be in prelude. - pub fn prelude_thing() -> bool { true } + /// Only my thing. (Will be in `own`) + pub fn my_thing() -> bool + { + true + } + /// Parent module should also has this thing. (Will be in `orphan`) + pub fn orphan_thing() -> bool + { + true + } + /// This thing should be exposed. (Will be in `exposed`) + pub fn exposed_thing() -> bool + { + true + } + /// This thing should be in prelude. (Will be in `prelude`) + pub fn prelude_thing() -> bool + { + true + } } - // - + // Use mod_interface to define the exposure levels for child's items crate::mod_interface! { own use my_thing; @@ -83,53 +97,58 @@ pub mod child } -// Priave namespaces is necessary. +// Parent module also needs a private namespace. mod private {} +// Parent module uses the `child` layer. crate::mod_interface! { - /// Inner. + /// Use the child layer. use super::child; } -// fn main() +// fn main() // Example usage demonstrating visibility: { + // `prelude_thing` is in `prelude`, so it propagates everywhere. assert!( child::prelude_thing(), "prelude thing of child is there" ); - assert!( prelude_thing(), "and here" ); - assert!( own::prelude_thing(), "and here" ); - assert!( orphan::prelude_thing(), "and here" ); - assert!( exposed::prelude_thing(), "and here" ); - assert!( prelude::prelude_thing(), "and here" ); + assert!( prelude_thing(), "Accessible in parent's root via prelude propagation" ); + assert!( own::prelude_thing(), "Accessible in parent's own via prelude propagation" ); + assert!( orphan::prelude_thing(), "Accessible in parent's orphan via prelude propagation" ); + assert!( exposed::prelude_thing(), "Accessible in parent's exposed via prelude propagation" ); + assert!( prelude::prelude_thing(), "Accessible in parent's prelude via prelude propagation" ); + // `exposed_thing` is in `exposed`, propagates to all ancestors except their prelude. assert!( child::exposed_thing(), "exposed thing of child is there" ); - assert!( exposed_thing(), "and here" ); - assert!( own::exposed_thing(), "and here" ); - assert!( orphan::exposed_thing(), "and here" ); - assert!( exposed::exposed_thing(), "and here" ); - // assert!( prelude::exposed_thing(), "but not here" ); + assert!( exposed_thing(), "Accessible in parent's root via exposed propagation" ); + assert!( own::exposed_thing(), "Accessible in parent's own via exposed propagation" ); + assert!( orphan::exposed_thing(), "Accessible in parent's orphan via exposed propagation" ); + assert!( exposed::exposed_thing(), "Accessible in parent's exposed via exposed propagation" ); + // assert!( prelude::exposed_thing(), "but not in parent's prelude" ); // Fails + // `orphan_thing` is in `orphan`, propagates only to the immediate parent's root and `own`. assert!( child::orphan_thing(), "orphan thing of child is there" ); - assert!( orphan_thing(), "orphan thing of child is here" ); - assert!( own::orphan_thing(), "and here" ); - // assert!( orphan::orphan_thing(), "but not here" ); - // assert!( exposed::orphan_thing(), "and not here" ); - // assert!( prelude::orphan_thing(), "and not here" ); + assert!( orphan_thing(), "Accessible in parent's root via orphan propagation" ); + assert!( own::orphan_thing(), "Accessible in parent's own via orphan propagation" ); + // assert!( orphan::orphan_thing(), "but not in parent's orphan" ); // Fails + // assert!( exposed::orphan_thing(), "and not in parent's exposed" ); // Fails + // assert!( prelude::orphan_thing(), "and not in parent's prelude" ); // Fails + // `my_thing` is in `own`, does not propagate. assert!( child::my_thing(), "own thing of child is only there" ); - // assert!( my_thing(), "and not here" ); - // assert!( own::my_thing(), "and not here" ); - // assert!( orphan::my_thing(), "and not here" ); - // assert!( exposed::my_thing(), "and not here" ); - // assert!( prelude::my_thing(), "and not here" ); + // assert!( my_thing(), "and not here" ); // Fails + // assert!( own::my_thing(), "and not here" ); // Fails + // assert!( orphan::my_thing(), "and not here" ); // Fails + // assert!( exposed::my_thing(), "and not here" ); // Fails + // assert!( prelude::my_thing(), "and not here" ); // Fails } ```
-The code above will be expanded to this +Click to see the code expanded by the macro ```rust use mod_interface::mod_interface; @@ -140,123 +159,149 @@ pub mod child // Define a private namespace for all its items. mod private { - /// Only my thing. - pub fn my_thing() -> bool { true } - /// Parent module should also has this thing. - pub fn orphan_thing() -> bool { true } - /// This thing should be exposed. - pub fn exposed_thing() -> bool { true } - /// This thing should be in prelude. - pub fn prelude_thing() -> bool { true } + /// Only my thing. (Will be in `own`) + pub fn my_thing() -> bool + { + true + } + /// Parent module should also has this thing. (Will be in `orphan`) + pub fn orphan_thing() -> bool + { + true + } + /// This thing should be exposed. (Will be in `exposed`) + pub fn exposed_thing() -> bool + { + true + } + /// This thing should be in prelude. (Will be in `prelude`) + pub fn prelude_thing() -> bool + { + true + } } + // Use mod_interface to define the exposure levels for child's items + /* crate::mod_interface! { own use my_thing; orphan use orphan_thing; exposed use exposed_thing; prelude use prelude_thing; } */ + // Expanded code generated by the macro: pub use own::*; - /// Own namespace of the module. pub mod own { - use super::*; - pub use orphan::*; - pub use private::my_thing; + use super::*; + pub use orphan::*; + pub use private::my_thing; } - /// Orphan namespace of the module. pub mod orphan { - use super::*; - pub use exposed::*; - pub use private::orphan_thing; + use super::*; + pub use exposed::*; + pub use private::orphan_thing; } - /// Exposed namespace of the module. pub mod exposed { - use super::*; - pub use prelude::*; - pub use private::exposed_thing; + use super::*; + pub use prelude::*; + pub use private::exposed_thing; } - /// Prelude to use essentials: `use my_module::prelude::*`. pub mod prelude { - use super::*; - pub use private::prelude_thing; + use super::*; + pub use private::prelude_thing; } } -// Priave namespaces is necessary. +// Parent module also needs a private namespace. mod private {} +// Parent module uses the `child` layer. +/* crate::mod_interface! { use super::child; } */ +// Expanded code generated by the macro: pub use own::*; - /// Own namespace of the module. #[ allow( unused_imports ) ] pub mod own { - use super::*; - pub use orphan::*; - pub use super::child::orphan::*; - pub use super::child; + use super::*; + pub use orphan::*; + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + #[ doc = " Use the child layer."] + pub use super::child::orphan::*; // Items from child's orphan are pulled into parent's own + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + #[ doc = " Use the child layer."] + pub use super::child; // The child module itself is available in parent's own } - /// Orphan namespace of the module. #[ allow( unused_imports ) ] pub mod orphan { - use super::*; - pub use exposed::*; + use super::*; + pub use exposed::*; + // Child's orphan items do not propagate to parent's orphan } - /// Exposed namespace of the module. #[ allow( unused_imports ) ] pub mod exposed { - use super::*; - pub use prelude::*; - pub use child::exposed::*; + use super::*; + pub use prelude::*; + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + #[ doc = " Use the child layer."] + pub use super::child::exposed::*; // Items from child's exposed are pulled into parent's exposed } - /// Prelude to use essentials: `use my_module::prelude::*`. #[ allow( unused_imports ) ] pub mod prelude { - use super::*; - pub use child::prelude::*; + use super::*; + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + #[ doc = " Use the child layer."] + pub use super::child::prelude::*; // Items from child's prelude are pulled into parent's prelude } -// -// fn main() +// fn main() // Example usage demonstrating visibility: { + // `prelude_thing` is in `prelude`, so it propagates everywhere. assert!( child::prelude_thing(), "prelude thing of child is there" ); - assert!( prelude_thing(), "and here" ); - assert!( own::prelude_thing(), "and here" ); - assert!( orphan::prelude_thing(), "and here" ); - assert!( exposed::prelude_thing(), "and here" ); - assert!( prelude::prelude_thing(), "and here" ); + assert!( prelude_thing(), "Accessible in parent's root via prelude propagation" ); + assert!( own::prelude_thing(), "Accessible in parent's own via prelude propagation" ); + assert!( orphan::prelude_thing(), "Accessible in parent's orphan via prelude propagation" ); + assert!( exposed::prelude_thing(), "Accessible in parent's exposed via prelude propagation" ); + assert!( prelude::prelude_thing(), "Accessible in parent's prelude via prelude propagation" ); + // `exposed_thing` is in `exposed`, propagates to all ancestors except their prelude. assert!( child::exposed_thing(), "exposed thing of child is there" ); - assert!( exposed_thing(), "and here" ); - assert!( own::exposed_thing(), "and here" ); - assert!( orphan::exposed_thing(), "and here" ); - assert!( exposed::exposed_thing(), "and here" ); - // assert!( prelude::exposed_thing(), "but not here" ); + assert!( exposed_thing(), "Accessible in parent's root via exposed propagation" ); + assert!( own::exposed_thing(), "Accessible in parent's own via exposed propagation" ); + assert!( orphan::exposed_thing(), "Accessible in parent's orphan via exposed propagation" ); + assert!( exposed::exposed_thing(), "Accessible in parent's exposed via exposed propagation" ); + // assert!( prelude::exposed_thing(), "but not in parent's prelude" ); // Fails + // `orphan_thing` is in `orphan`, propagates only to the immediate parent's root and `own`. assert!( child::orphan_thing(), "orphan thing of child is there" ); - assert!( orphan_thing(), "orphan thing of child is here" ); - assert!( own::orphan_thing(), "and here" ); - // assert!( orphan::orphan_thing(), "but not here" ); - // assert!( exposed::orphan_thing(), "and not here" ); - // assert!( prelude::orphan_thing(), "and not here" ); + assert!( orphan_thing(), "Accessible in parent's root via orphan propagation" ); + assert!( own::orphan_thing(), "Accessible in parent's own via orphan propagation" ); + // assert!( orphan::orphan_thing(), "but not in parent's orphan" ); // Fails + // assert!( exposed::orphan_thing(), "and not in parent's exposed" ); // Fails + // assert!( prelude::orphan_thing(), "and not in parent's prelude" ); // Fails + // `my_thing` is in `own`, does not propagate. assert!( child::my_thing(), "own thing of child is only there" ); - // assert!( my_thing(), "and not here" ); - // assert!( own::my_thing(), "and not here" ); - // assert!( orphan::my_thing(), "and not here" ); - // assert!( exposed::my_thing(), "and not here" ); - // assert!( prelude::my_thing(), "and not here" ); + // assert!( my_thing(), "and not here" ); // Fails + // assert!( own::my_thing(), "and not here" ); // Fails + // assert!( orphan::my_thing(), "and not here" ); // Fails + // assert!( exposed::my_thing(), "and not here" ); // Fails + // assert!( prelude::my_thing(), "and not here" ); // Fails } @@ -273,7 +318,7 @@ mod_interface::mod_interface! { #![ debug ] /// Inner. - layer child; + layer child; // Or `use super::child;` if defined separately } ``` @@ -300,4 +345,4 @@ git clone https://github.com/Wandalen/wTools cd wTools cd examples/mod_interface_trivial cargo run -``` +``` \ No newline at end of file diff --git a/module/core/mod_interface/examples/mod_interface_debug/Readme.md b/module/core/mod_interface/examples/mod_interface_debug/Readme.md index d57023c6a5..ccd620e8a2 100644 --- a/module/core/mod_interface/examples/mod_interface_debug/Readme.md +++ b/module/core/mod_interface/examples/mod_interface_debug/Readme.md @@ -1,15 +1,12 @@ -# Sample +# Sample: Debugging `mod_interface` [![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%2Fmod_interface_with_debug,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/mod_interface) -A sample demonstrates basic usage of macro `mod_interface`. +This sample demonstrates basic usage of the `mod_interface!` macro and its debugging capabilities. -In file `child.rs` demonstrated how to generate module interface from namespace `private` and its public routine. +- In `child.rs`, the macro defines a simple module interface, exporting `inner_is` into the `prelude` exposure level from the `private` namespace. +- In `main.rs`, the macro uses the `layer` keyword to integrate the `child` module as a layer. -In file `main.rs` demonstrated how to generate module interface from layer ( file with full module interface ). - -The directive `#![ debug ]` in declaration of macro `mod_interface` allow to show generated module interface as the standard output in compile time. - - \ No newline at end of file +The directive `#![ debug ]` within the `mod_interface!` macro invocation causes the macro to print the generated code (including the module structure with its exposure levels like `own`, `orphan`, `exposed`, `prelude`) to standard output during compilation. This is useful for understanding how the macro expands and verifying the resulting module structure. Uncomment the `//#![ debug ]` line in `main.rs` to see this output. \ No newline at end of file diff --git a/module/core/mod_interface/examples/mod_interface_debug/src/main.rs b/module/core/mod_interface/examples/mod_interface_debug/src/main.rs index 985f4f49f7..585c5a879a 100644 --- a/module/core/mod_interface/examples/mod_interface_debug/src/main.rs +++ b/module/core/mod_interface/examples/mod_interface_debug/src/main.rs @@ -1,14 +1,28 @@ -//! qqq : write proper description +//! This example demonstrates using the `mod_interface!` macro +//! with the `layer` keyword to integrate a child module (`child.rs`) +//! and shows how to use the `#![ debug ]` directive to inspect +//! the code generated by the macro during compilation. + use mod_interface::mod_interface; // +// This module is intentionally left empty in this example, +// as the focus is on integrating the `child` layer. +// A `mod private {}` is often required by `mod_interface!` +// as the default location for item definitions. mod private {} + mod_interface! { - // Uncomment to see expanded code. + // Uncomment the line below to enable debug output during compilation. + // This will print the expanded code generated by `mod_interface!` + // to the standard output, showing the resulting module structure + // with its exposure levels (`own`, `orphan`, `exposed`, `prelude`). // #![ debug ] - /// Child. + + /// Child layer integration. + /// Defines the `child` module in this file and integrates its interface. layer child; } @@ -16,5 +30,10 @@ mod_interface! fn main() { + // Assert that the `inner_is` function from the child's prelude + // is accessible both directly via the child module and + // via the parent's propagated prelude. assert_eq!( prelude::inner_is(), child::prelude::inner_is() ); -} + assert_eq!( child::inner_is(), true ); // Also accessible directly in child's root + assert_eq!( prelude::inner_is(), true ); // Accessible via parent's prelude +} \ No newline at end of file diff --git a/module/core/mod_interface/examples/mod_interface_trivial/Readme.md b/module/core/mod_interface/examples/mod_interface_trivial/Readme.md index e7b67c25d1..b9335ebb8d 100644 --- a/module/core/mod_interface/examples/mod_interface_trivial/Readme.md +++ b/module/core/mod_interface/examples/mod_interface_trivial/Readme.md @@ -4,6 +4,6 @@ [![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%2Fmod_interface_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/mod_interface) -In this example, we demonstrate the basic use case of one layer utilizing another layer. For a module to be used as a layer, it must contain all the necessary chapters: `orphan`, `exposed`, and `prelude`. Generally, a layer should also have the `own` and `private` chapters, but these are typically not modified directly by the user unless explicitly defined, with the `private` chapter remaining inaccessible from outside the module. +In this example, we demonstrate the basic use case of one layer utilizing another layer. For a module to be used as a layer, it must contain all the necessary **exposure levels**: `orphan`, `exposed`, and `prelude`. Generally, a layer should also have the `own` and `private` **exposure levels**, but these are typically not modified directly by the user unless explicitly defined, with the `private` **exposure level** remaining inaccessible from outside the module. -Below is a simple example where a parent layer imports a `child` layer. The `child` layer defines several functions, each with a different propagation strategy, resulting in each function being placed in a different chapter of the parent layer, while some functions do not reach the parent layer at all. +Below is a simple example where a parent layer imports a `child` layer. The `child` layer defines several functions, each with a different propagation strategy, resulting in each function being placed in a different **exposure level** of the parent layer, while some functions do not reach the parent layer at all. \ No newline at end of file diff --git a/module/core/mod_interface/examples/mod_interface_trivial/src/child.rs b/module/core/mod_interface/examples/mod_interface_trivial/src/child.rs index 4ea0121559..1be662fbdc 100644 --- a/module/core/mod_interface/examples/mod_interface_trivial/src/child.rs +++ b/module/core/mod_interface/examples/mod_interface_trivial/src/child.rs @@ -1,23 +1,43 @@ -// Define a private namespace for all its items. +// Define a private namespace where all items are initially defined. mod private { - /// Only my thing. - pub fn my_thing() -> bool { true } - /// Parent module should also has this thing. - pub fn orphan_thing() -> bool { true } - /// This thing should be exposed. - pub fn exposed_thing() -> bool { true } - /// This thing should be in prelude. - pub fn prelude_thing() -> bool { true } + /// This item should only be accessible within the `child` module itself. + /// It will be placed in the `own` exposure level. + pub fn my_thing() -> bool + { + true + } + /// This item should be accessible in the `child` module and its immediate parent. + /// It will be placed in the `orphan` exposure level. + pub fn orphan_thing() -> bool + { + true + } + /// This item should be accessible throughout the module hierarchy (ancestors). + /// It will be placed in the `exposed` exposure level. + pub fn exposed_thing() -> bool + { + true + } + /// This item should be accessible everywhere and intended for glob imports. + /// It will be placed in the `prelude` exposure level. + pub fn prelude_thing() -> bool + { + true + } } -// - +// Use `mod_interface!` to re-export items from `private` +// into the appropriate public exposure levels. crate::mod_interface! { + // `my_thing` goes into the `own` level (not propagated). own use my_thing; + // `orphan_thing` goes into the `orphan` level (propagates to immediate parent). orphan use orphan_thing; + // `exposed_thing` goes into the `exposed` level (propagates to all ancestors). exposed use exposed_thing; + // `prelude_thing` goes into the `prelude` level (propagates like exposed, intended for glob). prelude use prelude_thing; -} +} \ No newline at end of file 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 f834f8b12f..900aab5206 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 @@ -1,16 +1,33 @@ -//! This example demonstrates how to use the `mod_interface` crate to organize a Rust program into structured namespaces. The code is divided into a library file (`child.rs`) and a main function. The library file defines a module with private functions and uses the `mod_interface` macro to specify which functions should be exposed in different namespaces. The main function then tests the visibility and accessibility of these functions. +//! This example demonstrates how to use the `mod_interface` crate +//! to structure a module (`child`) with different exposure levels (`own`, +//! `orphan`, `exposed`, `prelude`) for its items. +//! +//! The `child.rs` file defines several functions within a `private` module +//! and then uses `mod_interface!` to assign each function to a specific +//! exposure level, controlling how they propagate. +//! +//! This `main.rs` file declares `child` as a submodule and then uses +//! `mod_interface!` again with the `use` keyword to integrate the `child` +//! module's interface into its own structure. +//! +//! The `main` function includes assertions that test the visibility and +//! accessibility of the functions from the `child` module according to the +//! propagation rules associated with their exposure levels (`own`, `orphan`, +//! `exposed`, `prelude`). use mod_interface::mod_interface; -/// Children. +/// Child module defined in `child.rs`. pub mod child; -// Priave namespaces is necessary. +// A private namespace is necessary for the `mod_interface!` macro +// in the parent module, even if it remains empty. mod private {} +// Integrate the interface defined in the `child` module. crate::mod_interface! { - /// Inner. + /// Use the child layer. use super::child; } @@ -18,32 +35,36 @@ crate::mod_interface! fn main() { + // `prelude_thing` is in `child::prelude`, propagates everywhere. assert!( child::prelude_thing(), "prelude thing of child is there" ); - assert!( prelude_thing(), "and here" ); - assert!( own::prelude_thing(), "and here" ); - assert!( orphan::prelude_thing(), "and here" ); - assert!( exposed::prelude_thing(), "and here" ); - assert!( prelude::prelude_thing(), "and here" ); + assert!( prelude_thing(), "Accessible in parent's root via prelude propagation" ); + assert!( own::prelude_thing(), "Accessible in parent's own via prelude propagation" ); + assert!( orphan::prelude_thing(), "Accessible in parent's orphan via prelude propagation" ); + assert!( exposed::prelude_thing(), "Accessible in parent's exposed via prelude propagation" ); + assert!( prelude::prelude_thing(), "Accessible in parent's prelude via prelude propagation" ); + // `exposed_thing` is in `child::exposed`, propagates to all ancestors except their prelude. assert!( child::exposed_thing(), "exposed thing of child is there" ); - assert!( exposed_thing(), "and here" ); - assert!( own::exposed_thing(), "and here" ); - assert!( orphan::exposed_thing(), "and here" ); - assert!( exposed::exposed_thing(), "and here" ); - // assert!( prelude::exposed_thing(), "but not here" ); + assert!( exposed_thing(), "Accessible in parent's root via exposed propagation" ); + assert!( own::exposed_thing(), "Accessible in parent's own via exposed propagation" ); + assert!( orphan::exposed_thing(), "Accessible in parent's orphan via exposed propagation" ); + assert!( exposed::exposed_thing(), "Accessible in parent's exposed via exposed propagation" ); + // assert!( prelude::exposed_thing(), "but not in parent's prelude" ); // Fails: Exposed items don't reach parent's prelude + // `orphan_thing` is in `child::orphan`, propagates only to the immediate parent's root and `own`. assert!( child::orphan_thing(), "orphan thing of child is there" ); - assert!( orphan_thing(), "orphan thing of child is here" ); - assert!( own::orphan_thing(), "and here" ); - // assert!( orphan::orphan_thing(), "but not here" ); - // assert!( exposed::orphan_thing(), "and not here" ); - // assert!( prelude::orphan_thing(), "and not here" ); + assert!( orphan_thing(), "Accessible in parent's root via orphan propagation" ); + assert!( own::orphan_thing(), "Accessible in parent's own via orphan propagation" ); + // assert!( orphan::orphan_thing(), "but not in parent's orphan" ); // Fails: Orphan items don't reach parent's orphan + // assert!( exposed::orphan_thing(), "and not in parent's exposed" ); // Fails: Orphan items don't reach parent's exposed + // assert!( prelude::orphan_thing(), "and not in parent's prelude" ); // Fails: Orphan items don't reach parent's prelude + // `my_thing` is in `child::own`, does not propagate. assert!( child::my_thing(), "own thing of child is only there" ); - // assert!( my_thing(), "and not here" ); - // assert!( own::my_thing(), "and not here" ); - // assert!( orphan::my_thing(), "and not here" ); - // assert!( exposed::my_thing(), "and not here" ); - // assert!( prelude::my_thing(), "and not here" ); + // assert!( my_thing(), "and not here" ); // Fails: Own items don't propagate to parent's root + // assert!( own::my_thing(), "and not here" ); // Fails: Own items don't propagate to parent's own + // assert!( orphan::my_thing(), "and not here" ); // Fails: Own items don't propagate to parent's orphan + // assert!( exposed::my_thing(), "and not here" ); // Fails: Own items don't propagate to parent's exposed + // assert!( prelude::my_thing(), "and not here" ); // Fails: Own items don't propagate to parent's prelude -} +} \ No newline at end of file diff --git a/module/core/mod_interface_meta/Cargo.toml b/module/core/mod_interface_meta/Cargo.toml index 69ba680466..193ec24543 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.30.0" +version = "0.31.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/mod_interface_meta/License b/module/core/mod_interface_meta/License index c32986cee3..a23529f45b 100644 --- a/module/core/mod_interface_meta/License +++ b/module/core/mod_interface_meta/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/process_tools/License b/module/core/process_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/process_tools/License +++ b/module/core/process_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/program_tools/License b/module/core/program_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/program_tools/License +++ b/module/core/program_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/pth/Cargo.toml b/module/core/pth/Cargo.toml index 0d06f333ef..fce38c2429 100644 --- a/module/core/pth/Cargo.toml +++ b/module/core/pth/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pth" -version = "0.22.0" +version = "0.23.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -45,6 +45,7 @@ derive_serde = [ "serde" ] path_utf8 = [ "camino" ] [dependencies] +# qqq : xxx : make sure all dependencies are in workspace regex = { version = "1.10.3" } mod_interface = { workspace = true } serde = { version = "1.0.197", optional = true, features = [ "derive" ] } diff --git a/module/core/pth/License b/module/core/pth/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/pth/License +++ b/module/core/pth/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/pth/src/lib.rs b/module/core/pth/src/lib.rs index fb9557d7fa..56a6d64e05 100644 --- a/module/core/pth/src/lib.rs +++ b/module/core/pth/src/lib.rs @@ -6,12 +6,13 @@ #![ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] #[ cfg( feature = "enabled" ) ] -use mod_interface::mod_interface; +use ::mod_interface::mod_interface; #[ cfg( feature="no_std" ) ] #[ macro_use ] extern crate alloc; +/// Own namespace of the module. Contains items public within this layer, but not propagated. mod private {} #[ cfg( feature = "enabled" ) ] diff --git a/module/core/pth/tests/experiment.rs b/module/core/pth/tests/experiment.rs index 60e36f8879..3116a9c61b 100644 --- a/module/core/pth/tests/experiment.rs +++ b/module/core/pth/tests/experiment.rs @@ -1,3 +1,4 @@ +//! Experiment include!( "../../../../module/step/meta/src/module/terminal.rs" ); diff --git a/module/core/pth/tests/inc/absolute_path_test/basic_test.rs b/module/core/pth/tests/inc/absolute_path_test/basic_test.rs index 3f8a254ba0..c4563ab9f8 100644 --- a/module/core/pth/tests/inc/absolute_path_test/basic_test.rs +++ b/module/core/pth/tests/inc/absolute_path_test/basic_test.rs @@ -24,7 +24,7 @@ fn test_to_string_lossy() let result = path.to_string_lossy(); assert_eq!( result, "/path/to/file.txt" ); } -#[test] +#[ test ] fn test_to_string_lossy_hard() { let abs_path : AbsolutePath = "/path/with/😀/unicode.txt".try_into().unwrap(); @@ -32,7 +32,7 @@ fn test_to_string_lossy_hard() assert_eq!( string_lossy, "/path/with/\u{1F600}/unicode.txt" ); } -#[test] +#[ test ] #[ cfg( not( feature="no_std" ) ) ] fn test_try_from_pathbuf() { @@ -42,7 +42,7 @@ fn test_try_from_pathbuf() assert_eq!( abs_path.to_string_lossy(), "/path/to/some/file.txt" ); } -#[test] +#[ test ] #[ cfg( not( feature="no_std" ) ) ] fn test_try_from_path() { @@ -51,7 +51,7 @@ fn test_try_from_path() assert_eq!( abs_path.to_string_lossy(), "/path/to/some/file.txt" ); } -#[test] +#[ test ] fn test_parent() { let abs_path : AbsolutePath = "/path/to/some/file.txt".try_into().unwrap(); @@ -59,7 +59,7 @@ fn test_parent() assert_eq!( parent_path.to_string_lossy(), "/path/to/some" ); } -#[test] +#[ test ] fn test_join() { let abs_path : AbsolutePath = "/path/to/some".try_into().unwrap(); @@ -67,7 +67,7 @@ fn test_join() assert_eq!( joined_path.to_string_lossy(), "/path/to/some/file.txt" ); } -#[test] +#[ test ] fn test_relative_path_try_from_str() { let rel_path_str = "src/main.rs"; @@ -75,7 +75,7 @@ fn test_relative_path_try_from_str() assert_eq!( rel_path.to_string_lossy(), "src/main.rs" ); } -#[test] +#[ test ] #[ cfg( not( feature="no_std" ) ) ] fn test_relative_path_try_from_pathbuf() { @@ -84,7 +84,7 @@ fn test_relative_path_try_from_pathbuf() assert_eq!( rel_path.to_string_lossy(), "src/main.rs" ); } -#[test] +#[ test ] #[ cfg( not( feature="no_std" ) ) ] fn test_relative_path_try_from_path() { @@ -94,7 +94,7 @@ fn test_relative_path_try_from_path() assert_eq!( rel_path_result.unwrap().to_string_lossy(), "src/main.rs" ); } -#[test] +#[ test ] fn test_relative_path_parent() { let rel_path = AbsolutePath::try_from( "src/main.rs" ).unwrap(); @@ -102,7 +102,7 @@ fn test_relative_path_parent() assert_eq!( parent_path.to_string_lossy(), "src" ); } -#[test] +#[ test ] fn test_relative_path_join() { let rel_path = AbsolutePath::try_from( "src" ).unwrap(); diff --git a/module/core/pth/tests/inc/mod.rs b/module/core/pth/tests/inc/mod.rs index 8026b293ba..2c2b0d726d 100644 --- a/module/core/pth/tests/inc/mod.rs +++ b/module/core/pth/tests/inc/mod.rs @@ -1,5 +1,6 @@ use super::*; +use test_tools::exposed::*; mod as_path_test; mod try_into_path_test; diff --git a/module/core/pth/tests/tests.rs b/module/core/pth/tests/tests.rs index 49fa343161..7ec129f839 100644 --- a/module/core/pth/tests/tests.rs +++ b/module/core/pth/tests/tests.rs @@ -1,9 +1,9 @@ +//! All tests. #![ allow( unused_imports ) ] include!( "../../../../module/step/meta/src/module/terminal.rs" ); use pth as the_module; -use test_tools::exposed::*; #[ cfg( feature = "enabled" ) ] mod inc; diff --git a/module/core/reflect_tools/Cargo.toml b/module/core/reflect_tools/Cargo.toml index 6d4715039d..b4af9b6b5d 100644 --- a/module/core/reflect_tools/Cargo.toml +++ b/module/core/reflect_tools/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reflect_tools" -version = "0.4.0" +version = "0.5.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/reflect_tools/License b/module/core/reflect_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/reflect_tools/License +++ b/module/core/reflect_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/reflect_tools_meta/Cargo.toml b/module/core/reflect_tools_meta/Cargo.toml index 3225c58663..8538a62817 100644 --- a/module/core/reflect_tools_meta/Cargo.toml +++ b/module/core/reflect_tools_meta/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reflect_tools_meta" -version = "0.4.0" +version = "0.5.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/reflect_tools_meta/License b/module/core/reflect_tools_meta/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/reflect_tools_meta/License +++ b/module/core/reflect_tools_meta/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/strs_tools/License b/module/core/strs_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/strs_tools/License +++ b/module/core/strs_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/test_tools/Cargo.toml b/module/core/test_tools/Cargo.toml index 19d9c3e617..d8d8acc784 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.12.0" +version = "0.14.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -120,6 +120,7 @@ rand = { workspace = true } # diagnostics_tools = { workspace = true, features = [ "full" ], optional = true } # # process_tools = { workspace = true, features = [ "full" ], optional = true } + ## transient # error_tools diff --git a/module/core/test_tools/License b/module/core/test_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/test_tools/License +++ b/module/core/test_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/test_tools/src/lib.rs b/module/core/test_tools/src/lib.rs index 31a90abcf7..eaf065f365 100644 --- a/module/core/test_tools/src/lib.rs +++ b/module/core/test_tools/src/lib.rs @@ -272,6 +272,8 @@ pub mod prelude #[ doc( inline ) ] pub use test::prelude::*; + pub use ::rustversion::{ nightly, stable }; + #[ doc( inline ) ] pub use { diff --git a/module/core/time_tools/License b/module/core/time_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/time_tools/License +++ b/module/core/time_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/typing_tools/License b/module/core/typing_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/typing_tools/License +++ b/module/core/typing_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/variadic_from/Cargo.toml b/module/core/variadic_from/Cargo.toml index 7cecdbc449..e1746d2494 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.28.0" +version = "0.29.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/variadic_from/License b/module/core/variadic_from/License index 0804aed8e3..72c80c1308 100644 --- a/module/core/variadic_from/License +++ b/module/core/variadic_from/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/core/wtools/License b/module/core/wtools/License index c32986cee3..a23529f45b 100644 --- a/module/core/wtools/License +++ b/module/core/wtools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/move/assistant/.key/readme.md b/module/move/assistant/.key/readme.md deleted file mode 100644 index 4209c24678..0000000000 --- a/module/move/assistant/.key/readme.md +++ /dev/null @@ -1,20 +0,0 @@ -# Keys - -This document provides a concise example of an environment configuration script, used to set up environment variables for a project. These variables configure application behavior without altering the code. - -## Example of `.key/-env.sh` - -```bash -# OpenAI API key. -OPENAI_API_KEY=sk-proj-ABCDEFG -``` - -## How to Use in Shell - -To apply these variables to your current shell session, use: - -```bash -. ./key/-env.sh -``` - -This command sources the script, making the variables available in your current session. Ensure `-env.sh` is in the `key` directory relative to your current location. \ No newline at end of file diff --git a/module/move/assistant/Cargo.toml b/module/move/assistant/Cargo.toml deleted file mode 100644 index 50700f8c3c..0000000000 --- a/module/move/assistant/Cargo.toml +++ /dev/null @@ -1,63 +0,0 @@ -[package] -name = "assistant" -version = "0.1.0" -edition = "2021" -authors = [ - "Kostiantyn Wandalen ", -] -license = "MIT" -readme = "Readme.md" -documentation = "https://docs.rs/assistant" -repository = "https://github.com/Wandalen/wTools/tree/master/module/core/assistant" -homepage = "https://github.com/Wandalen/wTools/tree/master/module/core/assistant" -description = """ -Assist AI in writing code. -""" -categories = [ "algorithms", "development-tools" ] -keywords = [ "fundamental", "general-purpose" ] -default-run = "main" - -[lints] -workspace = true - -[package.metadata.docs.rs] -features = [ "full" ] -all-features = false - -[features] -default = [ "enabled" ] -full = [ "enabled" ] -enabled = [ - "former/enabled", - "format_tools/enabled", - "reflect_tools/enabled", -] - -[[bin]] -name = "main" -path = "src/bin/main.rs" - -[[bin]] -name = "list_resources" -path = "src/bin/list_resources.rs" - -[dependencies] -# xxx : qqq : optimze features -mod_interface = { workspace = true, features = [ "full" ] } -former = { workspace = true, features = [ "full" ] } -format_tools = { workspace = true, features = [ "full" ] } -reflect_tools = { workspace = true, features = [ "full" ] } -openai-api-rs = { version = "=5.0.14" } -tokio = { version = "1", features = ["full"] } -dotenv = "0.15" -clap = { version = "4.5.20", features = ["derive"] } -pth = "0.21.0" -serde = { version = "1.0.213", features = ["derive"] } -serde_with = "3.11.0" -error_tools = "0.17.0" -derive_tools = { version = "0.32.0", features = ["full"] } -regex = { version = "1.10.3" } -serde_yaml = "0.9" - -[dev-dependencies] -test_tools = { workspace = true } diff --git a/module/move/assistant/License b/module/move/assistant/License deleted file mode 100644 index 0804aed8e3..0000000000 --- a/module/move/assistant/License +++ /dev/null @@ -1,22 +0,0 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-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/move/assistant/Readme.md b/module/move/assistant/Readme.md deleted file mode 100644 index 9296447b86..0000000000 --- a/module/move/assistant/Readme.md +++ /dev/null @@ -1,35 +0,0 @@ - - -# Module :: assistant -[![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/ModuleassistantPush.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/ModuleassistantPush.yml) [![docs.rs](https://img.shields.io/docsrs/assistant?color=e3e8f0&logo=docs.rs)](https://docs.rs/assistant) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) - -**NOT ready for production** - - - - diff --git a/module/move/assistant/api/list.http b/module/move/assistant/api/list.http deleted file mode 100644 index 89424758ff..0000000000 --- a/module/move/assistant/api/list.http +++ /dev/null @@ -1,11 +0,0 @@ -# use REST Client VSCode plugin -# ctrl+l - -get https://api.openai.com/v1/models -Authorization: Bearer {{openai_token}} -# Content-Type: application/json - -get https://api.openai.com/v1/assistants -Authorization: Bearer {{openai_token}} -OpenAI-Beta: assistants=v2 -Content-Type: application/json diff --git a/module/move/assistant/design/agents_design.md b/module/move/assistant/design/agents_design.md deleted file mode 100644 index a4f9901294..0000000000 --- a/module/move/assistant/design/agents_design.md +++ /dev/null @@ -1,37 +0,0 @@ -# Agents - -## YAML description structure - -Please refer to `examples/` directory. - -## Paths - -- Used in node types, templates. -- Parts are delimited with `::`. -- Absolute path has a leading `::`. -- All paths (expect absolute) **are subject to absolutization**. Absolutization also depends on the context: in `next` fields paths are absolutized to `::nodes` dir, in templates - to `::output` and so on. - -## Execution - -- YAML file contains section about `nodes:`. -- Next node is encoded in `next:` field. -- Output of the nodes are stored in `::output` dir. - -## Builtin scenarios - -- `::scenario::entry` -- `::scenario::termination` - -## Node types - -- Input nodes: - - `trigger::stdin` - - `trigger::file` -- Processing nodes: - - `script` - - `agent::completion` -- Output nodes: - - `event::stdout` - - `event::file` - -Refer to examples in `examples/` to see fields of nodes. \ No newline at end of file diff --git a/module/move/assistant/design/agents_examples/sql.yaml b/module/move/assistant/design/agents_examples/sql.yaml deleted file mode 100644 index 5149f07a29..0000000000 --- a/module/move/assistant/design/agents_examples/sql.yaml +++ /dev/null @@ -1,23 +0,0 @@ -nodes: - - id: input - type: trigger::stdin - prompt: 'Your query: ' - next: node::translator - - - id: sql_generator_stage1 - type: agent::completion - system_message: 'Your task is to think about how to translate the user query in natural langauge in SQL langauge. Think step by steps.' - user_message: '{{input}}' - next: node::sql_generator_stage2 - - - id: sql_generator_stage2 - type: agent::completion - system_message: 'Your task to make an SQL code based on user query in natural language and the results of thinking on that query'. - user_message: '{{sql_generator_stage1}}' - agent_reuse: node::sql_generator_stage2 - next: node::output - - - id: output - type: event::stdout - output: '{{sql_generator_stage2}}' - next: scenario::termination \ No newline at end of file diff --git a/module/move/assistant/design/commands.md b/module/move/assistant/design/commands.md deleted file mode 100644 index d1c0410dba..0000000000 --- a/module/move/assistant/design/commands.md +++ /dev/null @@ -1,73 +0,0 @@ -# Commands - -## Legend - -- `<...>` - argument. -- `<..?>` - optional argument. -- `<...=...>` - argument with default value. -- `(...)+` - one or more times. - -## OpenAI - -### Files - -```shell -assistant openai files upload -assistant openai files list -assistant openai files retrieve -assistant openai files delete -assistant openai files retrieve-content -``` - -### Assistants - -```shell -assistant openai assistants create -assistant openai assistants list -assistant openai assistants retrieve -assistant openai assistants modify -assistant openai assistants delete -``` - -### Threads - -```shell -assistant openai threads create -assistant openai threads retrieve -assistant openai threads delete -``` - -### Messages - -```shell -assistant openai messages create -assistant openai messages list -assistant openai messages retrieve -assistant openai messages modify -assistant openai messages delete -``` - -### Chat - -```shell -assistant openai chat create-completion ( )+ -``` - -### Runs - -```shell -assistant openai runs create -assistant openai runs create-with-thread -assistant openai runs list -assistant openai runs retrieve -assistant openai runs cancel -``` - -## Anthropic - -### Messages - -```shell -assistant anthropic messages create ( )+ -``` - diff --git a/module/move/assistant/design/entities.mmd b/module/move/assistant/design/entities.mmd deleted file mode 100644 index 53e9e6baa5..0000000000 --- a/module/move/assistant/design/entities.mmd +++ /dev/null @@ -1,78 +0,0 @@ ---- -title: OpenAI API ---- -erDiagram - File { - string id PK - string object - integer bytes - integer created_at - string file_name - string purpose - } - - Assistant { - string id PK - string object - string model - integer created_at - string name - string description - string instructions - tool[] tools - metadata metadata - headers headers - } - - Thread { - string id PK - string object - integer created_at - object tool_resources - metadata metadata - } - - Message { - string id PK - string object - integer created_at - string thread_id FK - string status - object incomplete_details - integer completed_at - integer incomplete_at - string role - array content - string assistant_id FK - string run_id FK - array attachments - metadata metadata - } - - Run { - string id PK - string object - integer created_at - string thread_id FK - string assistant_id FK - string status - object required_action - object last_error - integer expires_at - integer started_at - integer cancelled_at - integer failed_at - integer completed_at - object incomplete_details - string model - string instructions - tool[] tools - metadata metadata - headers headers - } - - Assistant |o--o{ Run : "" - - Thread ||--o{ Message : "" - Thread ||--o{ Run: "" - diff --git a/module/move/assistant/design/entities.png b/module/move/assistant/design/entities.png deleted file mode 100644 index 69906c0fea..0000000000 Binary files a/module/move/assistant/design/entities.png and /dev/null differ diff --git a/module/move/assistant/design/scenarios.md b/module/move/assistant/design/scenarios.md deleted file mode 100644 index a32543fbc9..0000000000 --- a/module/move/assistant/design/scenarios.md +++ /dev/null @@ -1,32 +0,0 @@ -# Scenarios - -## OpenAI - -### Assistants - -#### Make new assistant - -```shell -assistant openai assistants create gpt-4o-mini CoolBot 'CoolBot is a helpful assistant.' 'You are a helpful assistant.' -``` - -This command will return assistant ID. - -#### Chat with the assistant - -To chat with OpenAI assistant, one should do this: - -1. Create a thread. Thread is like a chat. -2. Write a message in thread (e.g. a question). -3. Run the assistant in the thread. - -```shell -assistant openai threads create -``` - -This command will return the new thread ID (referred as `thread_id`). To call an assistant, you need to know its ID. - -```shell -assistant openai messages create user '2 + 2 = ?' -assistant openai runs create -``` diff --git a/module/move/assistant/src/actions.rs b/module/move/assistant/src/actions.rs deleted file mode 100644 index 826bf1603e..0000000000 --- a/module/move/assistant/src/actions.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! -//! CLI actions of the tool. -//! - -mod private {} - -crate::mod_interface! -{ - layer openai; - layer openai_assistants_list; - layer openai_files_list; - layer openai_runs_list; -} diff --git a/module/move/assistant/src/actions/openai.rs b/module/move/assistant/src/actions/openai.rs deleted file mode 100644 index da8be32030..0000000000 --- a/module/move/assistant/src/actions/openai.rs +++ /dev/null @@ -1,66 +0,0 @@ -//! -//! OpenAI API actions. -//! -//! This module also contains the definition of OpenAI Error. -//! - -mod private -{ - - use error_tools::typed::Error; - use derive_tools::{ AsRefStr }; - - use crate::*; - use ser::DisplayFromStr; - - use commands::TableConfig; - - /// Collective enum for errors in OpenAI actions. - #[ ser::serde_as ] - #[ derive( Debug, Error, AsRefStr, ser::Serialize ) ] - #[ serde( tag = "type", content = "data" ) ] - pub enum Error - { - /// API error from the underlying implementation crate. - #[ error( "OpenAI API returned error:\n{0}" ) ] - ApiError - ( - #[ from ] - #[ serde_as( as = "DisplayFromStr" ) ] - openai_api_rs::v1::error::APIError - ), - - /// User chosen a mix of table styles instead of a single one. - /// E.g.: both `--as-table` and `--as-records` were set, however only one style must be chosen - #[ error( "Select only one table style: either `--as-table`, `--as-records`, or `--columns`" ) ] - WrongTableStyle, - } - - /// Shorthand for `Result` in OpenAI actions. - pub type Result< T > = core::result::Result< T, Error >; - - /// Check the CLI arguments for table style. - /// There are 3 arguments: `--as-table`, `--as-records`, `--columns`. Only one argument - /// should be active a time. - pub fn check_table_style( table_config: &TableConfig ) -> Result< () > - { - if table_config.as_table && ( table_config.as_records || table_config.columns ) - || table_config.as_records && ( table_config.as_table || table_config.columns ) - || table_config.columns && ( table_config.as_records || table_config.as_table ) - { - return Err( Error::WrongTableStyle ) - } - - Ok( () ) - } -} - -crate::mod_interface! -{ - own use - { - Error, - Result, - check_table_style, - }; -} \ No newline at end of file diff --git a/module/move/assistant/src/actions/openai_assistants_list.rs b/module/move/assistant/src/actions/openai_assistants_list.rs deleted file mode 100644 index 2326ac2a6a..0000000000 --- a/module/move/assistant/src/actions/openai_assistants_list.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! -//! List assistants in OpenAI API (action part). -//! - -mod private -{ - - use std::fmt; - - use format_tools::AsTable; - - use crate::*; - use client::Client; - - use debug::AssistantObjectWrap; - - use actions::openai::{ Result, check_table_style }; - - use commands::TableConfig; - use util::display_table::display_tabular_data; - - /// Report for `openai assistants list`. - #[ derive( Debug ) ] - pub struct ListReport - { - /// Configure table formatting. - pub table_config : TableConfig, - - /// OpenAI assistants. - pub assistants: Vec< AssistantObjectWrap > - } - - impl fmt::Display for ListReport - { - fn fmt - ( - &self, - f : &mut fmt::Formatter< '_ > - ) -> fmt::Result - { - display_tabular_data( &AsTable::new( &self.assistants ), f, &self.table_config ) - } - } - - /// List OpenAI assistants action. - pub async fn action - ( - client : &Client, - table_config : TableConfig, - ) -> Result < ListReport > - { - check_table_style( &table_config )?; - - let response = client.list_assistant( None, None, None, None ).await?; - let assistants = response.data.into_iter().map( AssistantObjectWrap ).collect(); - Ok( ListReport { table_config, assistants } ) - } -} - -crate::mod_interface! -{ - own use action; -} \ No newline at end of file diff --git a/module/move/assistant/src/actions/openai_files_list.rs b/module/move/assistant/src/actions/openai_files_list.rs deleted file mode 100644 index 12f6ab7bd0..0000000000 --- a/module/move/assistant/src/actions/openai_files_list.rs +++ /dev/null @@ -1,64 +0,0 @@ -//! -//! List files in OpenAI API (action part). -//! - -mod private -{ - - use std::fmt; - - use format_tools::AsTable; - - use crate::*; - use client::Client; - - use debug::FileDataWrap; - - use actions::openai::{ Result, check_table_style }; - - use commands::TableConfig; - use util::display_table::display_tabular_data; - - /// Report for `openai files list`. - #[ derive( Debug ) ] - pub struct ListReport - { - /// Configure table formatting. - pub table_config : TableConfig, - - /// Files in OpenAI. - pub files : Vec< FileDataWrap > - } - - impl fmt::Display for ListReport - { - fn fmt - ( - &self, - f : &mut fmt::Formatter< '_ > - ) -> fmt::Result - { - display_tabular_data( &AsTable::new( &self.files ), f, &self.table_config ) - } - } - - /// List OpenAI files action. - pub async fn action - ( - client : &Client, - table_config : TableConfig, - ) -> Result < ListReport > - { - check_table_style( &table_config )?; - - let response = client.file_list().await?; - let files = response.data.into_iter().map( FileDataWrap ).collect(); - Ok( ListReport { table_config, files } ) - } - -} - -crate::mod_interface! -{ - own use action; -} \ No newline at end of file diff --git a/module/move/assistant/src/actions/openai_runs_list.rs b/module/move/assistant/src/actions/openai_runs_list.rs deleted file mode 100644 index d5bf8098c6..0000000000 --- a/module/move/assistant/src/actions/openai_runs_list.rs +++ /dev/null @@ -1,65 +0,0 @@ -//! -//! List runs in OpenAI API (action part). -//! - -mod private -{ - - use std::fmt; - - use format_tools::AsTable; - - use crate::*; - use client::Client; - - use debug::RunObjectWrap; - - use actions::openai::{ Result, check_table_style }; - - use commands::TableConfig; - use util::display_table::display_tabular_data; - - /// Report for `openai runs list`. - #[ derive( Debug ) ] - pub struct ListReport - { - /// Configure table formatting. - pub table_config : TableConfig, - - /// Current OpenAI runs. - pub runs : Vec< RunObjectWrap >, - } - - impl fmt::Display for ListReport - { - fn fmt - ( - &self, - f : &mut fmt::Formatter< '_ > - ) -> fmt::Result - { - display_tabular_data( &AsTable::new( &self.runs ), f, &self.table_config ) - } - } - - /// List OpenAI runs action. - pub async fn action - ( - client : &Client, - thread_id : String, - table_config : TableConfig, - ) -> Result < ListReport > - { - check_table_style( &table_config )?; - - let response = client.list_run( thread_id, None, None, None, None ).await?; - let runs = response.data.into_iter().map( RunObjectWrap ).collect(); - Ok( ListReport { table_config, runs } ) - } - -} - -crate::mod_interface! -{ - own use action; -} \ No newline at end of file diff --git a/module/move/assistant/src/bin/list_resources.rs b/module/move/assistant/src/bin/list_resources.rs deleted file mode 100644 index d85d524ceb..0000000000 --- a/module/move/assistant/src/bin/list_resources.rs +++ /dev/null @@ -1,55 +0,0 @@ -#![ 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/assistant/latest/assistant/" ) ] -#![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] - -use std:: -{ - env, - error::Error, -}; - -use format_tools:: -{ - AsTable, - TableFormatter, - output_format, -}; -use dotenv::dotenv; - -use assistant:: -{ - client::client, - Secret -}; - -#[ tokio::main ] -async fn main() -> Result< (), Box< dyn Error > > -{ - dotenv().ok(); - - let secret = Secret::load()?; - - let client = client( &secret )?; - - let response = client.file_list().await?; - // println!( "Files: {:?}", response.data ); - let files : Vec< _ > = response.data.into_iter().map( | e | assistant::FileDataWrap( e ) ).collect(); - println! - ( - "Files:\n{}", - AsTable::new( &files ).table_to_string_with_format( &output_format::Table::default() ), - ); - - let response = client.list_assistant( None, None, None, None ).await?; - - // println!( "Assistants: {:?}", assistants.data ); - let assistants : Vec< _ > = response.data.into_iter().map( | e | assistant::AssistantObjectWrap( e ) ).collect(); - println! - ( - "Assistants:\n{}", - AsTable::new( &assistants ).table_to_string_with_format( &output_format::Records::default() ), - ); - - Ok( () ) -} \ No newline at end of file diff --git a/module/move/assistant/src/bin/main.rs b/module/move/assistant/src/bin/main.rs deleted file mode 100644 index 419030d03b..0000000000 --- a/module/move/assistant/src/bin/main.rs +++ /dev/null @@ -1,42 +0,0 @@ -#![ 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/assistant/latest/assistant/" ) ] -#![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] - -use std:: -{ - env, - error::Error, -}; - -use dotenv::dotenv; -use clap::Parser; - -use assistant:: -{ - client::client, - commands::{ Cli, CliCommand, self }, - Secret -}; - -#[ tokio::main ] -async fn main() -> Result< (), Box< dyn Error > > -{ - dotenv().ok(); - - let secret = Secret::load()?; - - let client = client( &secret )?; - - let cli = Cli::parse(); - - match cli.command - { - CliCommand::OpenAi( openai_command ) => - { - commands::openai::command( &client, openai_command ).await; - } - } - - Ok( () ) -} diff --git a/module/move/assistant/src/client.rs b/module/move/assistant/src/client.rs deleted file mode 100644 index 1c9fd0bbee..0000000000 --- a/module/move/assistant/src/client.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! -//! Client of API. -//! - -/// Define a private namespace for all its items. -mod private -{ - - pub use openai_api_rs::v1:: - { - api::OpenAIClient as Client, - assistant::AssistantObject, - }; - - use std:: - { - error::Error, - }; - - use crate::*; - use secret::Secret; - - /// Creates a new OpenAI API client using the secrets. - pub fn client( secrets : &Secret ) -> Result< Client, Box< dyn Error > > - { - Ok( Client::new( secrets.OPENAI_API_KEY.clone() ) ) - } - -} - -crate::mod_interface! -{ - exposed use - { - Client, - AssistantObject, - client - }; -} diff --git a/module/move/assistant/src/commands.rs b/module/move/assistant/src/commands.rs deleted file mode 100644 index 480b13d8d5..0000000000 --- a/module/move/assistant/src/commands.rs +++ /dev/null @@ -1,77 +0,0 @@ -//! -//! CLI commands of the tool. -//! - -/// Internal namespace. -mod private -{ - - use clap::{ Parser, Subcommand }; - - use crate::*; - use commands::openai; - - /// CLI commands of the tool. - #[ derive ( Debug, Parser ) ] - pub struct Cli - { - /// Root of the CLI commands. - #[ command ( subcommand ) ] - pub command : CliCommand, - } - - /// Root of the CLI commands. - #[ derive ( Debug, Subcommand ) ] - pub enum CliCommand - { - /// OpenAI API commands. - #[ command ( subcommand, name = "openai" ) ] - OpenAi( openai::Command ), - } - - const DEFAULT_MAX_TABLE_WIDTH : usize = 130; - - /// Common collection of arguments for formatting tabular data. - #[ derive( Debug, Parser ) ] - pub struct TableConfig - { - /// Limit table widht. - #[ arg( long, default_value_t = DEFAULT_MAX_TABLE_WIDTH ) ] - pub max_table_width : usize, - - /// Show tabular data as an ordinary table. - #[ arg( long ) ] - pub as_table : bool, - - /// Show each record of a tabular data as a separate table. - #[ arg( long ) ] - pub as_records : bool, - - /// Show only keys (columns) of tabular data. - #[ arg( long ) ] - pub columns : bool, - - /// Filter columns of tabular data. - #[ arg( long, value_delimiter( ',' ) ) ] - pub filter_columns : Vec< String >, - } - -} - -crate::mod_interface! -{ - layer openai; - layer openai_assistants; - layer openai_assistants_list; - layer openai_runs; - layer openai_runs_list; - layer openai_files; - layer openai_files_list; - - own use - { - Cli, - CliCommand, - TableConfig, - }; -} diff --git a/module/move/assistant/src/commands/openai.rs b/module/move/assistant/src/commands/openai.rs deleted file mode 100644 index 42c7ea5595..0000000000 --- a/module/move/assistant/src/commands/openai.rs +++ /dev/null @@ -1,75 +0,0 @@ -//! -//! Collection of OpenAI API commands. -//! - -mod private -{ - - use clap::Subcommand; - - use crate::*; - use client::Client; - use commands::{ openai_assistants, openai_files, openai_runs }; - - /// OpenAI API commands. - #[ derive ( Debug, Subcommand ) ] - pub enum Command - { - /// OpenAI assistants. - #[ command ( subcommand ) ] - Assistants - ( - openai_assistants::Command - ), - - /// OpenAI files. - #[ command ( subcommand ) ] - Files - ( - openai_files::Command - ), - - /// OpenAI runs. - #[ command ( subcommand ) ] - Runs - ( - openai_runs::Command - ), - } - - /// Execute OpenAI command. - pub async fn command - ( - client : &Client, - command : Command, - ) - { - match command - { - Command::Assistants( assistants_command ) => - { - openai_assistants::command( client, assistants_command ).await; - } - - Command::Files( files_command ) => - { - openai_files::command( client, files_command ).await; - } - - Command::Runs( runs_command ) => - { - openai_runs::command( client, runs_command ).await; - } - } - } - -} - -crate::mod_interface! -{ - own use - { - Command, - command, - }; -} \ No newline at end of file diff --git a/module/move/assistant/src/commands/openai_assistants.rs b/module/move/assistant/src/commands/openai_assistants.rs deleted file mode 100644 index 0d941c94ba..0000000000 --- a/module/move/assistant/src/commands/openai_assistants.rs +++ /dev/null @@ -1,52 +0,0 @@ -//! -//! Collection of assistants commands for OpenAI API. -//! - -mod private -{ - - use clap::Subcommand; - - use crate::*; - use client::Client; - use commands::{ openai_assistants_list, TableConfig }; - - /// OpenAI assistants. - #[ derive ( Debug, Subcommand ) ] - pub enum Command - { - /// List OpenAI assistants. - List - { - /// Configure table formatting. - #[ clap( flatten ) ] - table_config : TableConfig, - }, - } - - /// Execute OpenAI command related to assistants. - pub async fn command - ( - client : &Client, - command : Command, - ) - { - match command - { - Command::List{ table_config } => - { - openai_assistants_list::command( client, table_config ).await; - } - } - } - -} - -crate::mod_interface! -{ - own use - { - Command, - command, - }; -} \ No newline at end of file diff --git a/module/move/assistant/src/commands/openai_assistants_list.rs b/module/move/assistant/src/commands/openai_assistants_list.rs deleted file mode 100644 index 6ce7a80ac4..0000000000 --- a/module/move/assistant/src/commands/openai_assistants_list.rs +++ /dev/null @@ -1,34 +0,0 @@ -//! -//! List assistants in OpenAI API (command part). -//! - -mod private -{ - - use crate::*; - use client::Client; - use actions; - use commands::TableConfig; - - /// List OpenAI assistants command. - pub async fn command - ( - client : &Client, - table_config : TableConfig, - ) - { - let result = actions::openai_assistants_list::action( client, table_config ).await; - - match result - { - Ok ( report ) => println!( "{}", report ), - Err ( error ) => println!( "{}", error ) - } - } - -} - -crate::mod_interface! -{ - own use command; -} \ No newline at end of file diff --git a/module/move/assistant/src/commands/openai_files.rs b/module/move/assistant/src/commands/openai_files.rs deleted file mode 100644 index ea72a42ad1..0000000000 --- a/module/move/assistant/src/commands/openai_files.rs +++ /dev/null @@ -1,52 +0,0 @@ -//! -//! Collection of files commands for OpenAI API. -//! - -mod private -{ - - use clap::Subcommand; - - use crate::*; - use client::Client; - use commands::{ openai_files_list, TableConfig }; - - /// OpenAI files. - #[ derive ( Debug, Subcommand ) ] - pub enum Command - { - /// List OpenAI files. - List - { - /// Configure table formatting. - #[ clap( flatten ) ] - table_config : TableConfig, - }, - } - - /// Execute OpenAI commands related to files. - pub async fn command - ( - client : &Client, - command : Command, - ) - { - match command - { - Command::List{ table_config } => - { - openai_files_list::command( client, table_config ).await; - } - } - } - -} - -crate::mod_interface! -{ - own use - { - Command, - command, - }; -} \ No newline at end of file diff --git a/module/move/assistant/src/commands/openai_files_list.rs b/module/move/assistant/src/commands/openai_files_list.rs deleted file mode 100644 index 6225b9faf2..0000000000 --- a/module/move/assistant/src/commands/openai_files_list.rs +++ /dev/null @@ -1,34 +0,0 @@ -//! -//! List files in OpenAI API (command part). -//! - -mod private -{ - - use crate::*; - use client::Client; - use actions; - use commands::TableConfig; - - /// List files in your OpenAI API. - pub async fn command - ( - client : &Client, - table_config : TableConfig, - ) - { - let result = actions::openai_files_list::action( client, table_config ).await; - - match result - { - Ok ( report ) => println!( "{}", report ), - Err ( error ) => println!( "{}", error ) - } - } - -} - -crate::mod_interface! -{ - own use command; -} \ No newline at end of file diff --git a/module/move/assistant/src/commands/openai_runs.rs b/module/move/assistant/src/commands/openai_runs.rs deleted file mode 100644 index 2cf7812000..0000000000 --- a/module/move/assistant/src/commands/openai_runs.rs +++ /dev/null @@ -1,55 +0,0 @@ -//! -//! Collection of runs commands for OpenAI API. -//! - -mod private -{ - - use clap::Subcommand; - - use crate::*; - use client::Client; - use commands::{ openai_runs_list, TableConfig }; - - /// OpenAI runs. - #[ derive ( Debug, Subcommand ) ] - pub enum Command - { - /// List OpenAI runs in a thread. - List - { - /// Thread ID. - thread_id : String, - - /// Configure table formatting. - #[ clap( flatten ) ] - table_config : TableConfig, - } - } - - /// Execute OpenAI commands related to runs. - pub async fn command - ( - client : &Client, - command : Command, - ) - { - match command - { - Command::List { thread_id, table_config } => - { - openai_runs_list::command( client, thread_id, table_config ).await; - } - } - } - -} - -crate::mod_interface! -{ - own use - { - Command, - command, - }; -} \ No newline at end of file diff --git a/module/move/assistant/src/commands/openai_runs_list.rs b/module/move/assistant/src/commands/openai_runs_list.rs deleted file mode 100644 index 6d08d64ed3..0000000000 --- a/module/move/assistant/src/commands/openai_runs_list.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! -//! List runs in OpenAI API (command part). -//! - -mod private -{ - - use crate::*; - use client::Client; - use actions; - use commands::TableConfig; - - /// List runs in the thread in OpenAI API. - pub async fn command - ( - client : &Client, - thread_id : String, - table_config : TableConfig, - ) - { - let result = actions::openai_runs_list::action( client, thread_id, table_config ).await; - - match result - { - Ok ( report ) => println!( "{}", report ), - Err ( error ) => println!( "{}", error ) - } - } - -} - -crate::mod_interface! -{ - own use command; -} \ No newline at end of file diff --git a/module/move/assistant/src/debug.rs b/module/move/assistant/src/debug.rs deleted file mode 100644 index 294333abf0..0000000000 --- a/module/move/assistant/src/debug.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! -//! Client of API. -//! - -/// Define a private namespace for all its items. -mod private -{ - -} - -use format_tools:: -{ - Fields, - TableWithFields, -}; -use std::borrow::Cow; - -mod assistant_object; -mod file_data; -mod run_object; - -crate::mod_interface! -{ - exposed use - { - assistant_object::AssistantObjectWrap, - file_data::FileDataWrap, - run_object::RunObjectWrap, - }; -} diff --git a/module/move/assistant/src/debug/assistant_object.rs b/module/move/assistant/src/debug/assistant_object.rs deleted file mode 100644 index 9ebcead56e..0000000000 --- a/module/move/assistant/src/debug/assistant_object.rs +++ /dev/null @@ -1,84 +0,0 @@ -use super::*; -use openai_api_rs::v1::assistant; - -/// A wrapper for `AssistantObject` to make pretty print. -#[ derive( Debug ) ] -pub struct AssistantObjectWrap( pub assistant::AssistantObject ); - -/// Manually implemented `Clone`, as `FileData` does not implement it. -impl Clone for AssistantObjectWrap -{ - fn clone( &self ) -> Self - { - // Manually clone each field of the wrapped AssistantObject - AssistantObjectWrap( assistant::AssistantObject - { - id : self.0.id.clone(), - object : self.0.object.clone(), - created_at : self.0.created_at, - name : self.0.name.clone(), - description : self.0.description.clone(), - model : self.0.model.clone(), - instructions : self.0.instructions.clone(), - tools : self.0.tools.clone(), - tool_resources : self.0.tool_resources.clone(), - metadata : self.0.metadata.clone(), - headers : self.0.headers.clone(), - } ) - } -} - -impl TableWithFields for AssistantObjectWrap {} -impl Fields< &'_ str, Option< Cow< '_, str > > > -for AssistantObjectWrap -{ - type Key< 'k > = &'k str; - type Val< 'v > = Option< Cow< 'v, str > >; - - fn fields( &self ) -> impl format_tools::IteratorTrait< Item = ( &'_ str, Option< Cow< '_, str > > ) > - { - use format_tools::ref_or_display_or_debug_multiline::field; - let mut dst = Vec::new(); - - // Use the field! macro for direct field references - dst.push( field!( &self.0.id ) ); - dst.push( field!( &self.0.object ) ); - dst.push( field!( &self.0.model ) ); - - // Manually handle fields that require function calls - dst.push( ( "created_at", Some( Cow::Owned( self.0.created_at.to_string() ) ) ) ); - dst.push( ( "name", self.0.name.as_deref().map( Cow::Borrowed ) ) ); - dst.push( ( "description", self.0.description.as_deref().map( Cow::Borrowed ) ) ); - dst.push( ( "instructions", self.0.instructions.as_deref().map( Cow::Borrowed ) ) ); - - // Handle complex fields like `tools`, `tool_resources`, `metadata`, and `headers` - if !self.0.tools.is_empty() - { - dst.push( ( "tools", Some( Cow::Borrowed( "tools present" ) ) ) ); - } - else - { - dst.push( ( "tools", Option::None ) ); - } - - if let Some( _metadata ) = &self.0.metadata - { - dst.push( ( "metadata", Some( Cow::Borrowed( "metadata present" ) ) ) ); - } - else - { - dst.push( ( "metadata", Option::None ) ); - } - - if let Some( _headers ) = &self.0.headers - { - dst.push( ( "headers", Some( Cow::Borrowed( "headers present" ) ) ) ); - } - else - { - dst.push( ( "headers", Option::None ) ); - } - - dst.into_iter() - } -} diff --git a/module/move/assistant/src/debug/file_data.rs b/module/move/assistant/src/debug/file_data.rs deleted file mode 100644 index b8029949c7..0000000000 --- a/module/move/assistant/src/debug/file_data.rs +++ /dev/null @@ -1,50 +0,0 @@ - -use super::*; -use openai_api_rs::v1::file::FileData; - -// Assuming the `format_tools` module and `field!` macro are defined elsewhere - -/// A wrapper for `FileData` to make pretty print. -#[ derive( Debug ) ] -pub struct FileDataWrap( pub FileData ); - -/// Manually implemented `Clone`, as `FileData` does not implement it. -impl Clone for FileDataWrap -{ - fn clone( &self ) -> Self - { - FileDataWrap( FileData - { - id : self.0.id.clone(), - object : self.0.object.clone(), - bytes : self.0.bytes, - created_at : self.0.created_at, - filename : self.0.filename.clone(), - purpose : self.0.purpose.clone(), - } ) - } -} - -impl TableWithFields for FileDataWrap {} -impl Fields< &'_ str, Option< Cow< '_, str > > > -for FileDataWrap -{ - type Key<'k> = &'k str; - type Val< 'v > = Option< Cow< 'v, str > >; - - fn fields( &self ) -> impl format_tools::IteratorTrait< Item = ( &'_ str, Option< Cow< '_, str > > ) > - { - use format_tools::ref_or_display_or_debug_multiline::field; - let mut dst = Vec::new(); - - // Use the field! macro for direct field references - dst.push( field!( &self.0.id ) ); - dst.push( field!( &self.0.object ) ); - dst.push( ( "bytes", Some( Cow::Owned( self.0.bytes.to_string() ) ) ) ); - dst.push( ( "created_at", Some( Cow::Owned( self.0.created_at.to_string() ) ) ) ); - dst.push( field!( &self.0.filename ) ); - dst.push( field!( &self.0.purpose ) ); - - dst.into_iter() - } -} diff --git a/module/move/assistant/src/debug/run_object.rs b/module/move/assistant/src/debug/run_object.rs deleted file mode 100644 index cc2fda5357..0000000000 --- a/module/move/assistant/src/debug/run_object.rs +++ /dev/null @@ -1,79 +0,0 @@ - -use super::*; -use openai_api_rs::v1::run::RunObject; - -// Assuming the `format_tools` module and `field!` macro are defined elsewhere - -/// A wrapper for `RunObject` to make pretty print. -#[ derive( Debug ) ] -pub struct RunObjectWrap( pub RunObject ); - -/// Manually implemented `Clone`, as `RunObject` does not implement it. -impl Clone for RunObjectWrap -{ - fn clone(&self) -> Self - { - RunObjectWrap - ( - RunObject - { - id : self.0.id.clone(), - object : self.0.object.clone(), - created_at : self.0.created_at, - thread_id : self.0.thread_id.clone(), - assistant_id : self.0.assistant_id.clone(), - status : self.0.status.clone(), - required_action : self.0.required_action.clone(), - last_error : self.0.last_error.clone(), - expires_at : self.0.expires_at, - started_at : self.0.started_at, - cancelled_at : self.0.cancelled_at, - failed_at : self.0.failed_at, - completed_at : self.0.completed_at, - model : self.0.model.clone(), - instructions : self.0.instructions.clone(), - tools : self.0.tools.clone(), - metadata : self.0.metadata.clone(), - headers : self.0.headers.clone(), - } - ) - } -} - -impl TableWithFields for RunObjectWrap {} -impl Fields< &'_ str, Option< Cow< '_, str > > > -for RunObjectWrap -{ - type Key< 'k > = &'k str; - type Val< 'v > = Option< Cow< 'v, str > >; - - fn fields( &self ) -> impl format_tools::IteratorTrait< Item = ( &'_ str, Option< Cow< '_, str > > ) > - { - use format_tools::ref_or_display_or_debug_multiline::field; - let mut dst = Vec::new(); - - dst.push( field!( &self.0.id ) ); - dst.push( field!( &self.0.object ) ); - dst.push( ( "created_at", Some( Cow::Owned( self.0.created_at.to_string() ) ) ) ); - dst.push( field!( &self.0.thread_id ) ); - dst.push( field!( &self.0.assistant_id ) ); - dst.push( field!( &self.0.status ) ); - - dst.push( ( "required_action", self.0.required_action.as_ref().map( |ra| Cow::Owned( format!( "{:?}", ra ) ) ) ) ); - dst.push( ( "last_error", self.0.last_error.as_ref().map( |le| Cow::Owned( format!( "{:?}", le ) ) ) ) ); - dst.push( ( "expires_at", self.0.expires_at.map( |ea| Cow::Owned( ea.to_string() ) ) ) ); - dst.push( ( "started_at", self.0.started_at.map( |sa| Cow::Owned( sa.to_string() ) ) ) ); - dst.push( ( "cancelled_at", self.0.cancelled_at.map( |ca| Cow::Owned( ca.to_string() ) ) ) ); - dst.push( ( "failed_at", self.0.failed_at.map( |fa| Cow::Owned( fa.to_string() ) ) ) ); - dst.push( ( "completed_at", self.0.completed_at.map( |ca| Cow::Owned( ca.to_string() ) ) ) ); - - dst.push( field!( &self.0.model ) ); - dst.push( ( "instructions", self.0.instructions.as_ref().map( |i| Cow::Owned( i.clone() ) ) ) ); - - dst.push( ( "tools", Some( Cow::Owned( format!( "{:?}", self.0.tools ) ) ) ) ); - dst.push( ( "metadata", Some( Cow::Owned( format!( "{:?}", self.0.metadata ) ) ) ) ); - dst.push( ( "headers", self.0.headers.as_ref().map( |h| Cow::Owned( format!( "{:?}", h ) ) ) ) ); - - dst.into_iter() - } -} diff --git a/module/move/assistant/src/lib.rs b/module/move/assistant/src/lib.rs deleted file mode 100644 index 5a33e41692..0000000000 --- a/module/move/assistant/src/lib.rs +++ /dev/null @@ -1,44 +0,0 @@ -#![ 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/assistant/latest/assistant/" ) ] -#![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] - -use mod_interface::mod_interface; -use error_tools::thiserror; - -/// Define a private namespace for all its items. -mod private -{ -} - -/// Serde-related exports. -pub mod ser -{ - pub use serde:: - { - Serialize, - Deserialize, - }; - pub use serde_with::*; -} - -// pub mod client; - -crate::mod_interface! -{ - - layer client; - layer debug; - layer commands; - layer actions; - layer secret; - layer util; - - exposed use ::reflect_tools:: - { - Fields, - _IteratorTrait, - IteratorTrait, - }; - -} diff --git a/module/move/assistant/src/secret.rs b/module/move/assistant/src/secret.rs deleted file mode 100644 index aa90da77bc..0000000000 --- a/module/move/assistant/src/secret.rs +++ /dev/null @@ -1,219 +0,0 @@ -//! -//! Tool's secrets. -//! - -/// Internal namespace. -mod private -{ - use crate::*; - use std:: - { - env, - sync::OnceLock, - }; - - use error_tools::typed::Error; - use ser::DisplayFromStr; - - /// Typed secret error. - #[ ser::serde_as ] - #[ derive( Debug, Error, ser::Serialize ) ] - #[ serde( tag = "type", content = "data" ) ] - pub enum Error - { - - /// Secret file is illformed. - #[ error( "Secret file is illformed\n{0}" ) ] - SecretFileIllformed - ( - #[ from ] - #[ serde_as( as = "DisplayFromStr" ) ] - dotenv::Error - ), - - /// Some variable in the secrets is missing. - #[ error( "Secret misssing the variable {0}" ) ] - VariableMissing( &'static str ), - - /// Some variable in the secrets is illformed. - #[ error( "Secret error processing the variable {0}\n{1}" ) ] - VariableIllformed( &'static str, String ), - - } - - /// Result type for `Secret` methods. - pub type Result< R > = core::result::Result< R, Error >; - - /// Represents the application secrets loaded from environment variables. - #[ derive( Debug ) ] - #[ allow( non_snake_case ) ] - pub struct Secret - { - /// OpenAI API key. - pub OPENAI_API_KEY : String, - } - - impl Secret - { - - /// Loads secrets from environment variables. - /// - /// # Returns - /// - /// * `Result< Self >` - On success, returns a `Secret` instance with values from environment variables. - /// * On failure, returns an error indicating which environment variable is missing or invalid. - #[ allow( non_snake_case ) ] - pub fn load() -> Result< Self > - { - let path = "./.key/-env.sh"; - - // Attempt to load environment variables from the specified file - let r = dotenv::from_filename( path ); - if let Err( ref err ) = r - { - // Only return an error if it's not an Io error, and include the file path in the error message - if !matches!( err, dotenv::Error::Io( _ ) ) - { - return Err( r.expect_err( &format!( "Failed to load {path}" ) ).into() ); - } - } - - let config = Self - { - OPENAI_API_KEY : var( "OPENAI_API_KEY", None )?, - }; - Ok( config ) - } - - /// Reads the secrets, panicking with an explanation if loading fails. - /// - /// # Returns - /// - /// * `Secret` - The loaded secrets. - /// - /// # Panics - /// - /// * Panics with a detailed explanation if the secrets cannot be loaded. - - pub fn read() -> Secret - { - Self::load().unwrap_or_else( | err | - { - let example = include_str!( "../.key/readme.md" ); - let explanation = format! - ( -r#" = Lack of secrets - -Failed to load secret or some its parameters. -{err} - - = Fix - -Either define missing environment variable or make sure `./.key/-env.toml` file has it defined. - - = More information - -{example} -"# - ); - panic!( "{}", explanation ); - }) - } - - /// Retrieves a static reference to the secrets, initializing it if necessary. - /// - /// # Returns - /// - /// * `&'static Secret` - A static reference to the secrets. - /// - /// # Warning - /// - /// * Do not use this function unless absolutely necessary. - /// * Avoid using it in `lib.rs`. - pub fn get() -> &'static Secret - { - static INSTANCE : OnceLock< Secret > = OnceLock::new(); - INSTANCE.get_or_init( || Self::read() ) - } - - } - - /// Retrieves the value of an environment variable as a `String`. - /// - /// This function attempts to fetch the value of the specified environment variable. - /// If the variable is not set, it returns a provided default value if available, or an error if not. - /// - /// # Arguments - /// - /// * `name` - The name of the environment variable to retrieve. - /// * `default` - An optional default value to return if the environment variable is not set. - /// - /// # Returns - /// - /// * `Result` - On success, returns the value of the environment variable or the default value. - /// * On failure, returns an error indicating the missing environment variable. - fn var - ( - name : &'static str, - default : Option< &'static str >, - ) -> Result< String > - { - match env::var( name ) - { - Ok( value ) => Ok( value ), - Err( _ ) => - { - if let Some( default_value ) = default - { - Ok( default_value.to_string() ) - } - else - { - Err( Error::VariableMissing( name ) ) - } - } - } - } - - /// Retrieves the value of an environment variable as an `AbsolutePath`. - /// - /// This function attempts to fetch the value of the specified environment variable and convert it into an `AbsolutePath`. - /// If the variable is not set, it returns a provided default value if available, or an error if not. - /// - /// # Arguments - /// - /// * `name` - The name of the environment variable to retrieve. - /// * `default` - An optional default value to return if the environment variable is not set. - /// - /// # Returns - /// - /// * `Result` - On success, returns the parsed `AbsolutePath`. - /// * On failure, returns an error indicating the missing or ill-formed environment variable. - fn _var_path - ( - name : &'static str, - default : Option< &'static str >, - ) -> Result< pth::AbsolutePath > - { - let p = var( name, default )?; - pth::AbsolutePath::from_paths( ( pth::CurrentPath, p ) ) - .map_err( |e| Error::VariableIllformed( name, e.to_string() ) ) - } - -} - -crate::mod_interface! -{ - - own use - { - Error, - Result, - }; - - orphan use - { - Secret, - }; - -} \ No newline at end of file diff --git a/module/move/assistant/src/util.rs b/module/move/assistant/src/util.rs deleted file mode 100644 index 7e34c0fd16..0000000000 --- a/module/move/assistant/src/util.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! -//! Collection of utility functions for this crate. -//! - -mod private {} - -crate::mod_interface! -{ - layer display_table; -} \ No newline at end of file diff --git a/module/move/assistant/src/util/display_table.rs b/module/move/assistant/src/util/display_table.rs deleted file mode 100644 index c4e7ddcd28..0000000000 --- a/module/move/assistant/src/util/display_table.rs +++ /dev/null @@ -1,128 +0,0 @@ -//! -//! Function for displaying tabular data according to `TableConfig`. -//! - -mod private -{ - - use std::fmt; - - use format_tools:: - { - TableFormatter, - output_format, - print, - TableOutputFormat, - }; - - use crate::*; - use commands::{ TableConfig }; - - /// Function for displaying tabular data according to `TableConfig`. - pub fn display_tabular_data<'a> - ( - data : &'a impl TableFormatter< 'a >, - f : &mut fmt::Formatter< '_ >, - table_config : &'a TableConfig, - ) -> fmt::Result - { - if table_config.as_table - { - display_table( data, f, table_config ) - } - else if table_config.as_records - { - display_records( data, f, table_config ) - } - else if table_config.columns - { - display_columns( data, f, table_config ) - } - else - { - display_table( data, f, table_config ) - } - } - - fn display_table<'a> - ( - data : &'a impl TableFormatter< 'a >, - f : &mut fmt::Formatter< '_ >, - table_config : &'a TableConfig, - ) -> fmt::Result - { - let mut format = output_format::Table::default(); - format.max_width = table_config.max_table_width; - - display_data - ( - data, - f, - format, - &table_config.filter_columns, - ) - } - - fn display_records<'a> - ( - data : &'a impl TableFormatter< 'a >, - f : &mut fmt::Formatter< '_ >, - table_config : &'a TableConfig, - ) -> fmt::Result - { - let mut format = output_format::Records::default(); - format.max_width = table_config.max_table_width; - - display_data - ( - data, - f, - format, - &table_config.filter_columns, - ) - } - - fn display_columns<'a> - ( - data : &'a impl TableFormatter< 'a >, - f : &mut fmt::Formatter< '_ >, - table_config : &'a TableConfig, - ) -> fmt::Result - { - let mut format = output_format::Records::default(); - format.max_width = table_config.max_table_width; - - display_data - ( - data, - f, - format, - &table_config.filter_columns, - ) - } - - fn display_data<'a> - ( - data : &'a impl TableFormatter< 'a >, - f : &mut fmt::Formatter< '_ >, - format : impl TableOutputFormat, - filter_columns : &'a Vec< String >, - ) -> fmt::Result - { - let mut printer = print::Printer::with_format( &format ); - let binding = | title : &str | - { - filter_columns.is_empty() || filter_columns.iter().any( |c| c.as_str() == title ) - }; - printer.filter_col = &binding; - - let mut context = print::Context::new( f, printer ); - TableFormatter::fmt( data, &mut context ) - } - -} - -crate::mod_interface! -{ - own use display_tabular_data; -} \ No newline at end of file diff --git a/module/move/assistant/tests/inc/basic_test.rs b/module/move/assistant/tests/inc/basic_test.rs deleted file mode 100644 index 60c9a81cfb..0000000000 --- a/module/move/assistant/tests/inc/basic_test.rs +++ /dev/null @@ -1,7 +0,0 @@ -#[ allow( unused_imports ) ] -use super::*; - -#[ test ] -fn basic() -{ -} diff --git a/module/move/assistant/tests/inc/experiment.rs b/module/move/assistant/tests/inc/experiment.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/module/move/assistant/tests/inc/mod.rs b/module/move/assistant/tests/inc/mod.rs deleted file mode 100644 index fd3344b089..0000000000 --- a/module/move/assistant/tests/inc/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[ allow( unused_imports ) ] -use super::*; - -mod basic_test; -mod experiment; diff --git a/module/move/assistant/tests/smoke_test.rs b/module/move/assistant/tests/smoke_test.rs deleted file mode 100644 index 2f3e22aa74..0000000000 --- a/module/move/assistant/tests/smoke_test.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! Smoke testing of the crate. - -#[ test ] -fn local_smoke_test() -{ - ::test_tools::smoke_test_for_local_run(); -} - -#[ test ] -fn published_smoke_test() -{ - ::test_tools::smoke_test_for_published_run(); -} diff --git a/module/move/assistant/tests/tests.rs b/module/move/assistant/tests/tests.rs deleted file mode 100644 index 9c966dc926..0000000000 --- a/module/move/assistant/tests/tests.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! All test. - -include!( "../../../../module/step/meta/src/module/terminal.rs" ); - -#[ allow( unused_imports ) ] -use assistant as the_module; -#[ allow( unused_imports ) ] -use test_tools::exposed::*; - -#[ cfg( feature = "enabled" ) ] -mod inc; diff --git a/module/move/crates_tools/License b/module/move/crates_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/move/crates_tools/License +++ b/module/move/crates_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/move/deterministic_rand/License b/module/move/deterministic_rand/License index 0804aed8e3..72c80c1308 100644 --- a/module/move/deterministic_rand/License +++ b/module/move/deterministic_rand/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/move/deterministic_rand/tests/basic_test.rs b/module/move/deterministic_rand/tests/basic_test.rs index 5ebfffd9f6..0f88aca89b 100644 --- a/module/move/deterministic_rand/tests/basic_test.rs +++ b/module/move/deterministic_rand/tests/basic_test.rs @@ -2,7 +2,7 @@ use rand::distributions::Uniform; use rayon::prelude::*; -#[test] +#[ test ] fn test_rng_manager() { use deterministic_rand::{ Hrng, Rng }; @@ -37,7 +37,7 @@ fn test_rng_manager() #[ cfg( not( feature = "no_std" ) ) ] #[ cfg( feature = "determinism" ) ] -#[test] +#[ test ] fn test_reusability() { use deterministic_rand::{ Hrng, Rng }; @@ -95,7 +95,7 @@ fn test_reusability() #[ cfg( not( feature = "no_std" ) ) ] #[ cfg( feature = "determinism" ) ] -#[test] +#[ test ] fn test_par() { use std::sync::{ Arc, Mutex }; @@ -137,7 +137,7 @@ fn test_par() #[ cfg( not( feature = "no_std" ) ) ] #[ cfg( feature = "determinism" ) ] -#[test] +#[ test ] fn seed() { use deterministic_rand::Seed; diff --git a/module/move/graphs_tools/License b/module/move/graphs_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/move/graphs_tools/License +++ b/module/move/graphs_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/move/graphs_tools_deprecated/License b/module/move/graphs_tools_deprecated/License index 0804aed8e3..72c80c1308 100644 --- a/module/move/graphs_tools_deprecated/License +++ b/module/move/graphs_tools_deprecated/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/move/llm_tools/.key/readme.md b/module/move/llm_tools/.key/readme.md deleted file mode 100644 index 4209c24678..0000000000 --- a/module/move/llm_tools/.key/readme.md +++ /dev/null @@ -1,20 +0,0 @@ -# Keys - -This document provides a concise example of an environment configuration script, used to set up environment variables for a project. These variables configure application behavior without altering the code. - -## Example of `.key/-env.sh` - -```bash -# OpenAI API key. -OPENAI_API_KEY=sk-proj-ABCDEFG -``` - -## How to Use in Shell - -To apply these variables to your current shell session, use: - -```bash -. ./key/-env.sh -``` - -This command sources the script, making the variables available in your current session. Ensure `-env.sh` is in the `key` directory relative to your current location. \ No newline at end of file diff --git a/module/move/llm_tools/Cargo.toml b/module/move/llm_tools/Cargo.toml deleted file mode 100644 index 68be7a479c..0000000000 --- a/module/move/llm_tools/Cargo.toml +++ /dev/null @@ -1,64 +0,0 @@ -[package] -name = "llm_tools" -version = "0.2.0" -edition = "2021" -authors = [ - "Kostiantyn Wandalen ", -] -license = "MIT" -readme = "Readme.md" -documentation = "https://docs.rs/llm_tools" -repository = "https://github.com/Wandalen/wTools/tree/master/module/core/llm_tools" -homepage = "https://github.com/Wandalen/wTools/tree/master/module/core/llm_tools" -description = """ -Unified API for AI. -""" -categories = [ "algorithms", "development-tools" ] -keywords = [ "fundamental", "general-purpose" ] -default-run = "main" - -[lints] -workspace = true - -[package.metadata.docs.rs] -features = [ "full" ] -all-features = false - -[features] -default = [ "enabled" ] -full = [ "enabled" ] -enabled = [ - "former/enabled", - "format_tools/enabled", - "reflect_tools/enabled", -] - -[[bin]] -name = "main" -path = "src/bin/main.rs" - -[dependencies] - -# xxx : qqq : optimze features -mod_interface = { workspace = true, features = [ "full" ] } -former = { workspace = true, features = [ "full" ] } -format_tools = { workspace = true, features = [ "full" ] } -reflect_tools = { workspace = true, features = [ "full" ] } -openai-api-rs = { version = "=5.0.14" } -tokio = { version = "1", features = ["full"] } -dotenv = "0.15" -pth = { workspace = true, features = [ "full" ] } -error_tools = { workspace = true, features = [ "full" ] } -derive_tools = { workspace = true, features = ["full"] } - -regex = { workspace = true } -serde = { workspace = true, features = ["derive"] } -serde_with = { workspace = true } -serde_json = { workspace = true } -serde_yaml = { workspace = true } - -futures-core = { workspace = true } -futures-util = { workspace = true } - -[dev-dependencies] -test_tools = { workspace = true } diff --git a/module/move/llm_tools/License b/module/move/llm_tools/License deleted file mode 100644 index 0804aed8e3..0000000000 --- a/module/move/llm_tools/License +++ /dev/null @@ -1,22 +0,0 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-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/move/llm_tools/Readme.md b/module/move/llm_tools/Readme.md deleted file mode 100644 index 3982fe50dc..0000000000 --- a/module/move/llm_tools/Readme.md +++ /dev/null @@ -1,8 +0,0 @@ - - -# Module :: llm_tools -[![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/ModuleassistantPush.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/ModuleassistantPush.yml) [![docs.rs](https://img.shields.io/docsrs/llm_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/llm_tools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) - -**NOT ready for production** - -Unified API for AI. diff --git a/module/move/llm_tools/api/list.http b/module/move/llm_tools/api/list.http deleted file mode 100644 index 89424758ff..0000000000 --- a/module/move/llm_tools/api/list.http +++ /dev/null @@ -1,11 +0,0 @@ -# use REST Client VSCode plugin -# ctrl+l - -get https://api.openai.com/v1/models -Authorization: Bearer {{openai_token}} -# Content-Type: application/json - -get https://api.openai.com/v1/assistants -Authorization: Bearer {{openai_token}} -OpenAI-Beta: assistants=v2 -Content-Type: application/json diff --git a/module/move/llm_tools/src/bin/main.rs b/module/move/llm_tools/src/bin/main.rs deleted file mode 100644 index fc284b794a..0000000000 --- a/module/move/llm_tools/src/bin/main.rs +++ /dev/null @@ -1,53 +0,0 @@ -#![ 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/assistant/latest/assistant/" ) ] -#![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] - -use std:: -{ - env, - error::Error, -}; - -use format_tools:: -{ - AsTable, - TableFormatter, - output_format, -}; -use dotenv::dotenv; - -use llm_tools:: -{ - client::client, - Secret -}; - -#[ tokio::main ] -async fn main() -> Result< (), Box< dyn Error > > -{ - dotenv().ok(); - - let secret = Secret::load()?; - let client = client( &secret )?; - - let response = client.file_list().await?; - // println!( "Files: {:?}", response.data ); - let files : Vec< _ > = response.data.into_iter().map( | e | llm_tools::FileDataWrap( e ) ).collect(); - println! - ( - "Files:\n{}", - AsTable::new( &files ).table_to_string_with_format( &output_format::Table::default() ), - ); - - // let response = client.list_assistant( None, None, None, None ).await?; - // // println!( "Assistants: {:?}", assistants.data ); - // let assistants : Vec< _ > = response.data.into_iter().map( | e | llm_tools::AssistantObjectWrap( e ) ).collect(); - // println! - // ( - // "Assistants:\n{}", - // AsTable::new( &assistants ).table_to_string_with_format( &output_format::Records::default() ), - // ); - - Ok( () ) -} diff --git a/module/move/llm_tools/src/client.rs b/module/move/llm_tools/src/client.rs deleted file mode 100644 index 1c9fd0bbee..0000000000 --- a/module/move/llm_tools/src/client.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! -//! Client of API. -//! - -/// Define a private namespace for all its items. -mod private -{ - - pub use openai_api_rs::v1:: - { - api::OpenAIClient as Client, - assistant::AssistantObject, - }; - - use std:: - { - error::Error, - }; - - use crate::*; - use secret::Secret; - - /// Creates a new OpenAI API client using the secrets. - pub fn client( secrets : &Secret ) -> Result< Client, Box< dyn Error > > - { - Ok( Client::new( secrets.OPENAI_API_KEY.clone() ) ) - } - -} - -crate::mod_interface! -{ - exposed use - { - Client, - AssistantObject, - client - }; -} diff --git a/module/move/llm_tools/src/debug.rs b/module/move/llm_tools/src/debug.rs deleted file mode 100644 index 294333abf0..0000000000 --- a/module/move/llm_tools/src/debug.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! -//! Client of API. -//! - -/// Define a private namespace for all its items. -mod private -{ - -} - -use format_tools:: -{ - Fields, - TableWithFields, -}; -use std::borrow::Cow; - -mod assistant_object; -mod file_data; -mod run_object; - -crate::mod_interface! -{ - exposed use - { - assistant_object::AssistantObjectWrap, - file_data::FileDataWrap, - run_object::RunObjectWrap, - }; -} diff --git a/module/move/llm_tools/src/debug/assistant_object.rs b/module/move/llm_tools/src/debug/assistant_object.rs deleted file mode 100644 index 9ebcead56e..0000000000 --- a/module/move/llm_tools/src/debug/assistant_object.rs +++ /dev/null @@ -1,84 +0,0 @@ -use super::*; -use openai_api_rs::v1::assistant; - -/// A wrapper for `AssistantObject` to make pretty print. -#[ derive( Debug ) ] -pub struct AssistantObjectWrap( pub assistant::AssistantObject ); - -/// Manually implemented `Clone`, as `FileData` does not implement it. -impl Clone for AssistantObjectWrap -{ - fn clone( &self ) -> Self - { - // Manually clone each field of the wrapped AssistantObject - AssistantObjectWrap( assistant::AssistantObject - { - id : self.0.id.clone(), - object : self.0.object.clone(), - created_at : self.0.created_at, - name : self.0.name.clone(), - description : self.0.description.clone(), - model : self.0.model.clone(), - instructions : self.0.instructions.clone(), - tools : self.0.tools.clone(), - tool_resources : self.0.tool_resources.clone(), - metadata : self.0.metadata.clone(), - headers : self.0.headers.clone(), - } ) - } -} - -impl TableWithFields for AssistantObjectWrap {} -impl Fields< &'_ str, Option< Cow< '_, str > > > -for AssistantObjectWrap -{ - type Key< 'k > = &'k str; - type Val< 'v > = Option< Cow< 'v, str > >; - - fn fields( &self ) -> impl format_tools::IteratorTrait< Item = ( &'_ str, Option< Cow< '_, str > > ) > - { - use format_tools::ref_or_display_or_debug_multiline::field; - let mut dst = Vec::new(); - - // Use the field! macro for direct field references - dst.push( field!( &self.0.id ) ); - dst.push( field!( &self.0.object ) ); - dst.push( field!( &self.0.model ) ); - - // Manually handle fields that require function calls - dst.push( ( "created_at", Some( Cow::Owned( self.0.created_at.to_string() ) ) ) ); - dst.push( ( "name", self.0.name.as_deref().map( Cow::Borrowed ) ) ); - dst.push( ( "description", self.0.description.as_deref().map( Cow::Borrowed ) ) ); - dst.push( ( "instructions", self.0.instructions.as_deref().map( Cow::Borrowed ) ) ); - - // Handle complex fields like `tools`, `tool_resources`, `metadata`, and `headers` - if !self.0.tools.is_empty() - { - dst.push( ( "tools", Some( Cow::Borrowed( "tools present" ) ) ) ); - } - else - { - dst.push( ( "tools", Option::None ) ); - } - - if let Some( _metadata ) = &self.0.metadata - { - dst.push( ( "metadata", Some( Cow::Borrowed( "metadata present" ) ) ) ); - } - else - { - dst.push( ( "metadata", Option::None ) ); - } - - if let Some( _headers ) = &self.0.headers - { - dst.push( ( "headers", Some( Cow::Borrowed( "headers present" ) ) ) ); - } - else - { - dst.push( ( "headers", Option::None ) ); - } - - dst.into_iter() - } -} diff --git a/module/move/llm_tools/src/debug/file_data.rs b/module/move/llm_tools/src/debug/file_data.rs deleted file mode 100644 index b8029949c7..0000000000 --- a/module/move/llm_tools/src/debug/file_data.rs +++ /dev/null @@ -1,50 +0,0 @@ - -use super::*; -use openai_api_rs::v1::file::FileData; - -// Assuming the `format_tools` module and `field!` macro are defined elsewhere - -/// A wrapper for `FileData` to make pretty print. -#[ derive( Debug ) ] -pub struct FileDataWrap( pub FileData ); - -/// Manually implemented `Clone`, as `FileData` does not implement it. -impl Clone for FileDataWrap -{ - fn clone( &self ) -> Self - { - FileDataWrap( FileData - { - id : self.0.id.clone(), - object : self.0.object.clone(), - bytes : self.0.bytes, - created_at : self.0.created_at, - filename : self.0.filename.clone(), - purpose : self.0.purpose.clone(), - } ) - } -} - -impl TableWithFields for FileDataWrap {} -impl Fields< &'_ str, Option< Cow< '_, str > > > -for FileDataWrap -{ - type Key<'k> = &'k str; - type Val< 'v > = Option< Cow< 'v, str > >; - - fn fields( &self ) -> impl format_tools::IteratorTrait< Item = ( &'_ str, Option< Cow< '_, str > > ) > - { - use format_tools::ref_or_display_or_debug_multiline::field; - let mut dst = Vec::new(); - - // Use the field! macro for direct field references - dst.push( field!( &self.0.id ) ); - dst.push( field!( &self.0.object ) ); - dst.push( ( "bytes", Some( Cow::Owned( self.0.bytes.to_string() ) ) ) ); - dst.push( ( "created_at", Some( Cow::Owned( self.0.created_at.to_string() ) ) ) ); - dst.push( field!( &self.0.filename ) ); - dst.push( field!( &self.0.purpose ) ); - - dst.into_iter() - } -} diff --git a/module/move/llm_tools/src/debug/run_object.rs b/module/move/llm_tools/src/debug/run_object.rs deleted file mode 100644 index cc2fda5357..0000000000 --- a/module/move/llm_tools/src/debug/run_object.rs +++ /dev/null @@ -1,79 +0,0 @@ - -use super::*; -use openai_api_rs::v1::run::RunObject; - -// Assuming the `format_tools` module and `field!` macro are defined elsewhere - -/// A wrapper for `RunObject` to make pretty print. -#[ derive( Debug ) ] -pub struct RunObjectWrap( pub RunObject ); - -/// Manually implemented `Clone`, as `RunObject` does not implement it. -impl Clone for RunObjectWrap -{ - fn clone(&self) -> Self - { - RunObjectWrap - ( - RunObject - { - id : self.0.id.clone(), - object : self.0.object.clone(), - created_at : self.0.created_at, - thread_id : self.0.thread_id.clone(), - assistant_id : self.0.assistant_id.clone(), - status : self.0.status.clone(), - required_action : self.0.required_action.clone(), - last_error : self.0.last_error.clone(), - expires_at : self.0.expires_at, - started_at : self.0.started_at, - cancelled_at : self.0.cancelled_at, - failed_at : self.0.failed_at, - completed_at : self.0.completed_at, - model : self.0.model.clone(), - instructions : self.0.instructions.clone(), - tools : self.0.tools.clone(), - metadata : self.0.metadata.clone(), - headers : self.0.headers.clone(), - } - ) - } -} - -impl TableWithFields for RunObjectWrap {} -impl Fields< &'_ str, Option< Cow< '_, str > > > -for RunObjectWrap -{ - type Key< 'k > = &'k str; - type Val< 'v > = Option< Cow< 'v, str > >; - - fn fields( &self ) -> impl format_tools::IteratorTrait< Item = ( &'_ str, Option< Cow< '_, str > > ) > - { - use format_tools::ref_or_display_or_debug_multiline::field; - let mut dst = Vec::new(); - - dst.push( field!( &self.0.id ) ); - dst.push( field!( &self.0.object ) ); - dst.push( ( "created_at", Some( Cow::Owned( self.0.created_at.to_string() ) ) ) ); - dst.push( field!( &self.0.thread_id ) ); - dst.push( field!( &self.0.assistant_id ) ); - dst.push( field!( &self.0.status ) ); - - dst.push( ( "required_action", self.0.required_action.as_ref().map( |ra| Cow::Owned( format!( "{:?}", ra ) ) ) ) ); - dst.push( ( "last_error", self.0.last_error.as_ref().map( |le| Cow::Owned( format!( "{:?}", le ) ) ) ) ); - dst.push( ( "expires_at", self.0.expires_at.map( |ea| Cow::Owned( ea.to_string() ) ) ) ); - dst.push( ( "started_at", self.0.started_at.map( |sa| Cow::Owned( sa.to_string() ) ) ) ); - dst.push( ( "cancelled_at", self.0.cancelled_at.map( |ca| Cow::Owned( ca.to_string() ) ) ) ); - dst.push( ( "failed_at", self.0.failed_at.map( |fa| Cow::Owned( fa.to_string() ) ) ) ); - dst.push( ( "completed_at", self.0.completed_at.map( |ca| Cow::Owned( ca.to_string() ) ) ) ); - - dst.push( field!( &self.0.model ) ); - dst.push( ( "instructions", self.0.instructions.as_ref().map( |i| Cow::Owned( i.clone() ) ) ) ); - - dst.push( ( "tools", Some( Cow::Owned( format!( "{:?}", self.0.tools ) ) ) ) ); - dst.push( ( "metadata", Some( Cow::Owned( format!( "{:?}", self.0.metadata ) ) ) ) ); - dst.push( ( "headers", self.0.headers.as_ref().map( |h| Cow::Owned( format!( "{:?}", h ) ) ) ) ); - - dst.into_iter() - } -} diff --git a/module/move/llm_tools/src/lib.rs b/module/move/llm_tools/src/lib.rs deleted file mode 100644 index 88d27ffd1d..0000000000 --- a/module/move/llm_tools/src/lib.rs +++ /dev/null @@ -1,44 +0,0 @@ -#![ 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/assistant/latest/assistant/" ) ] -#![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] - -use mod_interface::mod_interface; -use error_tools::thiserror; - -/// Define a private namespace for all its items. -mod private -{ -} - -/// Serde-related exports. -pub mod ser -{ - pub use serde:: - { - Serialize, - Deserialize, - }; - pub use serde_with::*; -} - -// pub mod client; - -crate::mod_interface! -{ - - layer client; - layer messaging; - - layer debug; - layer secret; - layer util; - - exposed use ::reflect_tools:: - { - Fields, - _IteratorTrait, - IteratorTrait, - }; - -} diff --git a/module/move/llm_tools/src/messaging/content.rs b/module/move/llm_tools/src/messaging/content.rs deleted file mode 100644 index 6eb4507b91..0000000000 --- a/module/move/llm_tools/src/messaging/content.rs +++ /dev/null @@ -1,45 +0,0 @@ -// //! Content of a message which can be not only text, but also other media type. -// -// mod private -// { -// -// pub enum ContentType -// { -// Text, -// Image, -// Sound, -// Video, -// } -// -// pub trait Content -// { -// fn content_type( &self ) -> ContentType; -// fn content_to_string( self ) -> String; -// fn content_to_bytes( self ) -> Vec< u8 >; -// } -// -// /// Image -// #[ derive( Debug ) ] -// pub struct Image -// { -// pub source : Source, -// } -// -// /// Source -// #[ derive( Debug ) ] -// pub struct Source -// { -// pub media_type : String, -// pub encoding : String, -// pub data : AsBytes, -// } -// -// } -// -// crate::mod_interface! -// { -// orphan use private:: -// { -// Content, -// }; -// } diff --git a/module/move/llm_tools/src/messaging/conversation.rs b/module/move/llm_tools/src/messaging/conversation.rs deleted file mode 100644 index 89c7486670..0000000000 --- a/module/move/llm_tools/src/messaging/conversation.rs +++ /dev/null @@ -1,106 +0,0 @@ -//! Messaging. - -mod private -{ - use crate::*; - use messaging::{ Value, Message, Messages, Error }; - - use std:: - { - collections::HashMap, - }; - - /// Represents a conversation that holds a series of messages and relevant parameters. - #[ derive( Debug ) ] - pub struct Conversation - { - /// Systems instuction for context. - pub context : String, - /// A collection of messages exchanged within this conversation. - pub messages : Messages, - /// A collection of key-value parameters to customize LLM behavior. - pub parameters : HashMap< String, Value >, - } - - impl Conversation - { - /// Creates a new empty conversation with no parameters. - pub fn new() -> Self - { - Self - { - context : Default::default(), - messages : Messages::new(), - parameters : HashMap::new(), - } - } - - /// Adds or updates a specific parameter for the LLM. - /// - /// # Arguments - /// - /// * `key` - The name of the parameter (e.g., "temperature"). - /// * `val` - The JSON-based value to store for this parameter. - /// - /// # Errors - /// - /// Returns `Err( Error::Unknown( ...) )` for unexpected conditions. - pub fn parameter_set - ( - &mut self, - key : &str, - val : Value - ) -> Result< (), Error > - { - self.parameters.insert( key.to_string(), val ); - Ok(()) - } - - /// Inserts a context (usually from the conversation's system or user) into the conversation log. - /// - /// # Arguments - /// - /// * `context` - A new message describing the context (e.g., system instructions). - /// - /// # Errors - /// - /// Returns `Err( Error::Unknown( ... ) )` for unexpected conditions. - pub fn context - ( - &mut self, - context : Message - ) -> Result< (), Error > - { - self.messages.push( context ); - Ok(()) - } - - /// Adds a user or assistant message to the conversation. - /// - /// # Arguments - /// - /// * `message` - The new message to append to the conversation log. - /// - /// # Errors - /// - /// Returns `Err( Error::Unknown( ... ) )` for unexpected conditions. - pub fn message_add - ( - &mut self, - message : Message - ) -> Result< (), Error > - { - self.messages.push( message ); - Ok(()) - } - } - -} - -crate::mod_interface! -{ - orphan use private:: - { - Conversation, - }; -} diff --git a/module/move/llm_tools/src/messaging/message.rs b/module/move/llm_tools/src/messaging/message.rs deleted file mode 100644 index 7a94e02c53..0000000000 --- a/module/move/llm_tools/src/messaging/message.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! Messaging. - -mod private -{ - - /// Represents a single message in a conversation, either from a user or an assistant. - /// - /// It can optionally contain a dynamically-dispatched object that provides - /// asynchronous access to the message generation process. - #[ derive( Debug ) ] - pub struct Message - { - /// The role of the message sender (e.g., "user", "assistant"). - pub role : String, - /// A unique identifier for the message, if any. - pub id : Option< String >, - /// The main textual content of the message. - pub content : String, - } - - impl Message - { - /// Creates a new user-level or assistant-level message without generation data. - pub fn new( role : &str, content : &str ) -> Self - { - Self - { - role : role.to_string(), - id : None, - content : content.to_string(), - } - } - - } - -} - -crate::mod_interface! -{ - orphan use private:: - { - Message, - }; -} diff --git a/module/move/llm_tools/src/messaging/messages.rs b/module/move/llm_tools/src/messaging/messages.rs deleted file mode 100644 index 773b61def1..0000000000 --- a/module/move/llm_tools/src/messaging/messages.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! Messaging. - -mod private -{ - use crate::*; - use messaging::{ Message }; - - use std:: - { - ops::{ Deref, DerefMut }, - }; - - /// A thin wrapper around a vector of messages to store a conversation log. - #[ repr( transparent ) ] - #[ derive( Debug ) ] - pub struct Messages( pub Vec< Message > ); - - impl Messages - { - /// Creates a new, empty list of messages. - pub fn new() -> Self - { - Self( Vec::new() ) - } - } - - impl Deref for Messages - { - type Target = Vec< Message >; - - fn deref( &self ) -> &Self::Target - { - &self.0 - } - } - - impl DerefMut for Messages - { - fn deref_mut( &mut self ) -> &mut Self::Target - { - &mut self.0 - } - } - - -} - -crate::mod_interface! -{ - orphan use private:: - { - Messages, - }; -} diff --git a/module/move/llm_tools/src/messaging/mod.rs b/module/move/llm_tools/src/messaging/mod.rs deleted file mode 100644 index 94d160f787..0000000000 --- a/module/move/llm_tools/src/messaging/mod.rs +++ /dev/null @@ -1,34 +0,0 @@ -//! Messaging. - -mod private -{ - - /// Represents potential errors that may occur during LLM invocations. - #[ derive( Debug ) ] - pub enum Error - { - /// Catch-all variant for unknown or miscellaneous errors. - Unknown( String ), - } - - /// Alias for JSON-based values commonly used for LLM parameters. - pub type Value = serde_json::Value; - -} - -crate::mod_interface! -{ - - // layer content; - layer conversation; - layer message; - layer messages; - layer send; - layer stream; - - own use private:: - { - Error, - Value, - }; -} diff --git a/module/move/llm_tools/src/messaging/send.rs b/module/move/llm_tools/src/messaging/send.rs deleted file mode 100644 index 11d038a390..0000000000 --- a/module/move/llm_tools/src/messaging/send.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! Messaging. - -mod private -{ - use crate::*; - use messaging::{ Message, Conversation }; - - /// A trait defining asynchronous single-message retrieval from the LLM. - pub trait MessageSend - { - - /// Sends the current conversation state to the LLM and returns a single generated message. - /// - /// # Returns - /// - /// The newly generated message (with optional generation object inside), or an error if something went wrong. - // async fn send( &self ) -> Result< Message, messaging::Error >; - fn send( &self ) -> impl std::future::Future< Output = Result< Message, messaging::Error > > + Send; - - } - - impl MessageSend for Conversation - { - async fn send( &self ) -> Result< Message, messaging::Error > - { - // Example: returning a new message with a generation object - Ok( Message - { - role : "assistant".to_string(), - id : None, - content : "Mocked LLM Response".to_string(), - }) - } - } - -} - -crate::mod_interface! -{ - orphan use private:: - { - MessageSend, - }; -} diff --git a/module/move/llm_tools/src/messaging/stream.rs b/module/move/llm_tools/src/messaging/stream.rs deleted file mode 100644 index 07cab94d18..0000000000 --- a/module/move/llm_tools/src/messaging/stream.rs +++ /dev/null @@ -1,100 +0,0 @@ -//! Messaging. - -mod private -{ - use crate::*; - use messaging::{ Conversation }; - - use std:: - { - pin::Pin, - task::{ Context, Poll }, - }; - - use futures_core:: - { - // future::BoxFuture, - stream::Stream, - }; - - /// A default-like implementation of the generation object. - /// - /// This example doesn't truly stream anything, but it showcases how you can - /// store metadata and optionally produce partial content. - #[derive(Debug, Clone)] - pub struct MessageGeneration - { - /// The total number of tokens used for generation, if known. - pub tokens_used_val : Option< u64 >, - /// The model used to generate this message, if known. - pub model_name : Option< String >, - /// Whether the generation is considered final/complete. - pub complete : bool, - /// A queue of partial tokens or chunks for streaming (mocked). - pub partial_content : Vec< String >, - } - - /// A trait defining asynchronous stream-based retrieval from the LLM. - /// - /// The associated type is a Stream of concrete items (MessageGeneration). - pub trait MessageStream - { - /// The type of the asynchronous stream that yields generated results one by one. - type MessagesStream : Stream< Item = MessageGeneration > + Send + 'static; - - /// Sends the current conversation state to the LLM and returns a stream of generated message chunks. - /// - /// # Returns - /// - /// An asynchronous stream of MessageGeneration objects, or an error if something went wrong. - // async fn stream( &self ) -> Result< Self::MessagesStream, messaging::Error >; - fn stream( &self ) -> impl std::future::Future< Output = Result< Self::MessagesStream, messaging::Error > > + Send; - } - - /// The concrete stream type that yields MessageGeneration objects. - #[ derive( Debug ) ] - pub struct ConversationGenerationStream - { - gens : std::vec::IntoIter< MessageGeneration >, - } - - impl Stream for ConversationGenerationStream - { - type Item = MessageGeneration; - - fn poll_next - ( - self : Pin<&mut Self>, - _cx : &mut Context<'_> - ) -> Poll< Option< Self::Item > > - { - let me = self.get_mut(); - Poll::Ready( me.gens.next() ) - } - } - - impl MessageStream for Conversation - { - type MessagesStream = ConversationGenerationStream; - - async fn stream( &self ) -> Result< Self::MessagesStream, messaging::Error > - { - let all = Vec::new(); - Ok( ConversationGenerationStream - { - gens : all.into_iter(), - }) - } - } - -} - -crate::mod_interface! -{ - orphan use private:: - { - MessageGeneration, - MessageStream, - ConversationGenerationStream, - }; -} diff --git a/module/move/llm_tools/src/secret.rs b/module/move/llm_tools/src/secret.rs deleted file mode 100644 index 803a8fc85a..0000000000 --- a/module/move/llm_tools/src/secret.rs +++ /dev/null @@ -1,219 +0,0 @@ -//! -//! Tool's secrets. -//! - -/// Internal namespace. -mod private -{ - use crate::*; - use std:: - { - env, - sync::OnceLock, - }; - - use error_tools::typed::Error; - use ser::DisplayFromStr; - - /// Typed secret error. - #[ ser::serde_as ] - #[ derive( Debug, Error, ser::Serialize ) ] - #[ serde( tag = "type", content = "data" ) ] - pub enum Error - { - - /// Secret file is illformed. - #[ error( "Secret file is illformed\n{0}" ) ] - SecretFileIllformed - ( - #[ from ] - #[ serde_as( as = "DisplayFromStr" ) ] - dotenv::Error - ), - - /// Some variable in the secrets is missing. - #[ error( "Secret misssing the variable {0}" ) ] - VariableMissing( &'static str ), - - /// Some variable in the secrets is illformed. - #[ error( "Secret error processing the variable {0}\n{1}" ) ] - VariableIllformed( &'static str, String ), - - } - - /// Result type for `Secret` methods. - pub type Result< R > = core::result::Result< R, Error >; - - /// Represents the application secrets loaded from environment variables. - #[ derive( Debug ) ] - #[ allow( non_snake_case ) ] - pub struct Secret - { - /// OpenAI API key. - pub OPENAI_API_KEY : String, - } - - impl Secret - { - - /// Loads secrets from environment variables. - /// - /// # Returns - /// - /// * `Result< Self >` - On success, returns a `Secret` instance with values from environment variables. - /// * On failure, returns an error indicating which environment variable is missing or invalid. - #[ allow( non_snake_case ) ] - pub fn load() -> Result< Self > - { - let path = "./.key/-env.sh"; - - // Attempt to load environment variables from the specified file - let r = dotenv::from_filename( path ); - if let Err( ref err ) = r - { - // Only return an error if it's not an Io error, and include the file path in the error message - if !matches!( err, dotenv::Error::Io( _ ) ) - { - return Err( r.expect_err( &format!( "Failed to load {path}" ) ).into() ); - } - } - - let config = Self - { - OPENAI_API_KEY : var( "OPENAI_API_KEY", None )?, - }; - Ok( config ) - } - - /// Reads the secrets, panicking with an explanation if loading fails. - /// - /// # Returns - /// - /// * `Secret` - The loaded secrets. - /// - /// # Panics - /// - /// * Panics with a detailed explanation if the secrets cannot be loaded. - - pub fn read() -> Secret - { - Self::load().unwrap_or_else( | err | - { - let example = include_str!( "../.key/readme.md" ); - let explanation = format! - ( -r#" = Lack of secrets - -Failed to load secret or some its parameters. -{err} - - = Fix - -Either define missing environment variable or make sure `./.key/-env.toml` file has it defined. - - = More information - -{example} -"# - ); - panic!( "{}", explanation ); - }) - } - - /// Retrieves a static reference to the secrets, initializing it if necessary. - /// - /// # Returns - /// - /// * `&'static Secret` - A static reference to the secrets. - /// - /// # Warning - /// - /// * Do not use this function unless absolutely necessary. - /// * Avoid using it in `lib.rs`. - pub fn get() -> &'static Secret - { - static INSTANCE : OnceLock< Secret > = OnceLock::new(); - INSTANCE.get_or_init( || Self::read() ) - } - - } - - /// Retrieves the value of an environment variable as a `String`. - /// - /// This function attempts to fetch the value of the specified environment variable. - /// If the variable is not set, it returns a provided default value if available, or an error if not. - /// - /// # Arguments - /// - /// * `name` - The name of the environment variable to retrieve. - /// * `default` - An optional default value to return if the environment variable is not set. - /// - /// # Returns - /// - /// * `Result` - On success, returns the value of the environment variable or the default value. - /// * On failure, returns an error indicating the missing environment variable. - fn var - ( - name : &'static str, - default : Option< &'static str >, - ) -> Result< String > - { - match env::var( name ) - { - Ok( value ) => Ok( value ), - Err( _ ) => - { - if let Some( default_value ) = default - { - Ok( default_value.to_string() ) - } - else - { - Err( Error::VariableMissing( name ) ) - } - } - } - } - - /// Retrieves the value of an environment variable as an `AbsolutePath`. - /// - /// This function attempts to fetch the value of the specified environment variable and convert it into an `AbsolutePath`. - /// If the variable is not set, it returns a provided default value if available, or an error if not. - /// - /// # Arguments - /// - /// * `name` - The name of the environment variable to retrieve. - /// * `default` - An optional default value to return if the environment variable is not set. - /// - /// # Returns - /// - /// * `Result` - On success, returns the parsed `AbsolutePath`. - /// * On failure, returns an error indicating the missing or ill-formed environment variable. - fn _var_path - ( - name : &'static str, - default : Option< &'static str >, - ) -> Result< pth::AbsolutePath > - { - let p = var( name, default )?; - pth::AbsolutePath::from_paths( ( pth::CurrentPath, p ) ) - .map_err( |e| Error::VariableIllformed( name, e.to_string() ) ) - } - -} - -crate::mod_interface! -{ - - own use - { - Error, - Result, - }; - - orphan use - { - Secret, - }; - -} \ No newline at end of file diff --git a/module/move/llm_tools/src/util.rs b/module/move/llm_tools/src/util.rs deleted file mode 100644 index 7e34c0fd16..0000000000 --- a/module/move/llm_tools/src/util.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! -//! Collection of utility functions for this crate. -//! - -mod private {} - -crate::mod_interface! -{ - layer display_table; -} \ No newline at end of file diff --git a/module/move/llm_tools/src/util/display_table.rs b/module/move/llm_tools/src/util/display_table.rs deleted file mode 100644 index ffb99bf052..0000000000 --- a/module/move/llm_tools/src/util/display_table.rs +++ /dev/null @@ -1,154 +0,0 @@ -//! -//! Function for displaying tabular data according to `TableConfig`. -//! - -mod private -{ - - use std::fmt; - - use format_tools:: - { - TableFormatter, - output_format, - print, - TableOutputFormat, - }; - - // use crate::*; - // use commands::{ TableConfig }; - - /// Common collection of arguments for formatting tabular data. - #[ derive( Debug ) ] - pub struct TableConfig - { - /// Limit table widht. - // #[ arg( long, default_value_t = DEFAULT_MAX_TABLE_WIDTH ) ] - pub max_table_width : usize, - - /// Show tabular data as an ordinary table. - // #[ arg( long ) ] - pub as_table : bool, - - /// Show each record of a tabular data as a separate table. - // #[ arg( long ) ] - pub as_records : bool, - - /// Show only keys (columns) of tabular data. - // #[ arg( long ) ] - pub columns : bool, - - /// Filter columns of tabular data. - // #[ arg( long, value_delimiter( ',' ) ) ] - pub filter_columns : Vec< String >, - } - - /// Function for displaying tabular data according to `TableConfig`. - pub fn display_tabular_data<'a> - ( - data : &'a impl TableFormatter< 'a >, - f : &mut fmt::Formatter< '_ >, - table_config : &'a TableConfig, - ) -> fmt::Result - { - if table_config.as_table - { - display_table( data, f, table_config ) - } - else if table_config.as_records - { - display_records( data, f, table_config ) - } - else if table_config.columns - { - display_columns( data, f, table_config ) - } - else - { - display_table( data, f, table_config ) - } - } - - fn display_table<'a> - ( - data : &'a impl TableFormatter< 'a >, - f : &mut fmt::Formatter< '_ >, - table_config : &'a TableConfig, - ) -> fmt::Result - { - let mut format = output_format::Table::default(); - format.max_width = table_config.max_table_width; - - display_data - ( - data, - f, - format, - &table_config.filter_columns, - ) - } - - fn display_records<'a> - ( - data : &'a impl TableFormatter< 'a >, - f : &mut fmt::Formatter< '_ >, - table_config : &'a TableConfig, - ) -> fmt::Result - { - let mut format = output_format::Records::default(); - format.max_width = table_config.max_table_width; - - display_data - ( - data, - f, - format, - &table_config.filter_columns, - ) - } - - fn display_columns<'a> - ( - data : &'a impl TableFormatter< 'a >, - f : &mut fmt::Formatter< '_ >, - table_config : &'a TableConfig, - ) -> fmt::Result - { - let mut format = output_format::Records::default(); - format.max_width = table_config.max_table_width; - - display_data - ( - data, - f, - format, - &table_config.filter_columns, - ) - } - - fn display_data<'a> - ( - data : &'a impl TableFormatter< 'a >, - f : &mut fmt::Formatter< '_ >, - format : impl TableOutputFormat, - filter_columns : &'a Vec< String >, - ) -> fmt::Result - { - let mut printer = print::Printer::with_format( &format ); - let binding = | title : &str | - { - filter_columns.is_empty() || filter_columns.iter().any( |c| c.as_str() == title ) - }; - printer.filter_col = &binding; - - let mut context = print::Context::new( f, printer ); - TableFormatter::fmt( data, &mut context ) - } - -} - -crate::mod_interface! -{ - own use display_tabular_data; - own use TableConfig; -} \ No newline at end of file diff --git a/module/move/llm_tools/tests/inc/basic_test.rs b/module/move/llm_tools/tests/inc/basic_test.rs deleted file mode 100644 index 60c9a81cfb..0000000000 --- a/module/move/llm_tools/tests/inc/basic_test.rs +++ /dev/null @@ -1,7 +0,0 @@ -#[ allow( unused_imports ) ] -use super::*; - -#[ test ] -fn basic() -{ -} diff --git a/module/move/llm_tools/tests/inc/experiment.rs b/module/move/llm_tools/tests/inc/experiment.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/module/move/llm_tools/tests/inc/mod.rs b/module/move/llm_tools/tests/inc/mod.rs deleted file mode 100644 index fd3344b089..0000000000 --- a/module/move/llm_tools/tests/inc/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[ allow( unused_imports ) ] -use super::*; - -mod basic_test; -mod experiment; diff --git a/module/move/llm_tools/tests/smoke_test.rs b/module/move/llm_tools/tests/smoke_test.rs deleted file mode 100644 index 2f3e22aa74..0000000000 --- a/module/move/llm_tools/tests/smoke_test.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! Smoke testing of the crate. - -#[ test ] -fn local_smoke_test() -{ - ::test_tools::smoke_test_for_local_run(); -} - -#[ test ] -fn published_smoke_test() -{ - ::test_tools::smoke_test_for_published_run(); -} diff --git a/module/move/llm_tools/tests/tests.rs b/module/move/llm_tools/tests/tests.rs deleted file mode 100644 index b390683e4c..0000000000 --- a/module/move/llm_tools/tests/tests.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! All test. - -include!( "../../../../module/step/meta/src/module/terminal.rs" ); - -#[ allow( unused_imports ) ] -use test_tools::exposed::*; - -#[ cfg( feature = "enabled" ) ] -mod inc; diff --git a/module/move/optimization_tools/License b/module/move/optimization_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/move/optimization_tools/License +++ b/module/move/optimization_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/move/plot_interface/License b/module/move/plot_interface/License index 0804aed8e3..72c80c1308 100644 --- a/module/move/plot_interface/License +++ b/module/move/plot_interface/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/move/refiner/License b/module/move/refiner/License index 0804aed8e3..72c80c1308 100644 --- a/module/move/refiner/License +++ b/module/move/refiner/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/move/sqlx_query/License b/module/move/sqlx_query/License index 0804aed8e3..72c80c1308 100644 --- a/module/move/sqlx_query/License +++ b/module/move/sqlx_query/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/move/wca/License b/module/move/wca/License index 0804aed8e3..72c80c1308 100644 --- a/module/move/wca/License +++ b/module/move/wca/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/move/willbe/License b/module/move/willbe/License index 0804aed8e3..72c80c1308 100644 --- a/module/move/willbe/License +++ b/module/move/willbe/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/move/willbe/src/lib.rs b/module/move/willbe/src/lib.rs index a8a72e3d47..e80c1e45fd 100644 --- a/module/move/willbe/src/lib.rs +++ b/module/move/willbe/src/lib.rs @@ -3,6 +3,60 @@ #![ doc( html_root_url = "https://docs.rs/willbe/" ) ] #![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] +// qqq2 : xxx2 : fix broken sequence of publishing because of skipping debug dependencies +// +// cd module/core/former_meta +// cargo package --allow-dirty --no-verify +// +// Caused by: +// failed to select a version for `former_types`. +// ... required by package `macro_tools v0.46.0` +// ... which satisfies dependency `macro_tools = "~0.46.0"` of package `impls_index_meta v0.10.0` +// ... which satisfies dependency `impls_index_meta = "~0.10.0"` of package `test_tools v0.12.0` +// ... which satisfies dependency `test_tools = "~0.12.0"` of package `former_meta v2.12.0 (C:\pro\lib\wtools\module\core\former_meta)` +// versions that meet the requirements `~2.14.0` are: 2.14.0 +// +// all possible versions conflict with previously selected packages. +// +// previously selected package `former_types v2.15.0` +// ... which satisfies dependency `former_types = "~2.15.0"` of package `former_meta v2.12.0 (C:\pro\lib\wtools\module\core\former_meta)` +// +// failed to select a version for `former_types` which could resolve this conflict + +// qqq2 : xx2 : attempt to publish graphs_tools publish all crates do not respecting check on outdate +// +// Wrong: +// [0] interval_adapter (0.28.0 -> 0.29.0) +// [1] collection_tools (0.17.0 -> 0.18.0) +// [2] former_types (2.14.0 -> 2.15.0) +// [3] clone_dyn_types (0.28.0 -> 0.29.0) +// [4] iter_tools (0.26.0 -> 0.27.0) +// [5] macro_tools (0.46.0 -> 0.47.0) +// [6] derive_tools_meta (0.32.0 -> 0.33.0) +// [7] variadic_from (0.28.0 -> 0.29.0) +// [8] former_meta (2.12.0 -> 2.13.0) +// [9] impls_index_meta (0.10.0 -> 0.11.0) +// [10] clone_dyn_meta (0.28.0 -> 0.29.0) +// [11] clone_dyn (0.30.0 -> 0.31.0) +// [12] derive_tools (0.33.0 -> 0.34.0) +// [13] mod_interface_meta (0.30.0 -> 0.31.0) +// [14] mod_interface (0.31.0 -> 0.32.0) +// [15] for_each (0.10.0 -> 0.11.0) +// [16] impls_index (0.9.0 -> 0.10.0) +// [17] meta_tools (0.12.0 -> 0.13.0) +// [18] former (2.12.0 -> 2.13.0) +// [19] graphs_tools (0.3.0 -> 0.4.0) +// +// Correct: +// [0] impls_index (0.9.0 -> 0.10.0) +// [1] for_each (0.10.0 -> 0.11.0) +// [2] meta_tools (0.12.0 -> 0.13.0) +// [3] graphs_tools (0.3.0 -> 0.4.0) + +// qqq2 : xxx2 : another problem +// if you publish a crate and after you try to publish another which depends on the first willbe don't see any changes and don't publish second +// for example publishing impl_index -> after publising test_tools make willbe struggle to see that publishing of test_tools is required + pub use mod_interface::mod_interface; /// Define a private namespace for all its items. diff --git a/module/move/willbe/tests/inc/action_tests/test.rs b/module/move/willbe/tests/inc/action_tests/test.rs index 7928e2f802..476f92f918 100644 --- a/module/move/willbe/tests/inc/action_tests/test.rs +++ b/module/move/willbe/tests/inc/action_tests/test.rs @@ -29,7 +29,7 @@ fn fail_test() let project = ProjectBuilder::new( "fail_test" ) .toml_file( "[features]\nenabled = []" ) .test_file( r" - #[test] + #[ test ] fn should_fail() { panic!() @@ -69,7 +69,7 @@ fn fail_build() .lib_file( "compile_error!( \"achtung\" );" ) .toml_file( "[features]\nenabled = []" ) .test_file( r" - #[test] + #[ test ] fn should_pass() { assert!(true); } @@ -106,7 +106,7 @@ fn call_from_workspace_root() let fail_project = ProjectBuilder::new( "fail_test" ) .toml_file( "[features]\nenabled = []" ) .test_file( r" - #[test] + #[ test ] fn should_fail123() { panic!() } @@ -115,7 +115,7 @@ fn call_from_workspace_root() let pass_project = ProjectBuilder::new( "apass_test" ) .toml_file( "[features]\nenabled = []" ) .test_file( r" - #[test] + #[ test ] fn should_pass() { assert_eq!(1,1); } @@ -124,7 +124,7 @@ fn call_from_workspace_root() let pass_project2 = ProjectBuilder::new( "pass_test2" ) .toml_file( "[features]\nenabled = []" ) .test_file( r" - #[test] + #[ test ] fn should_pass() { assert_eq!(1,1); } @@ -165,7 +165,7 @@ fn plan() let project = ProjectBuilder::new( "plan_test" ) .toml_file( "[features]\nenabled = []" ) .test_file( r" - #[test] + #[ test ] fn should_pass() { assert!(true); } @@ -199,7 +199,7 @@ fn backtrace_should_be() let project = ProjectBuilder::new( "fail_build" ) .toml_file( "[features]\nenabled = []" ) .test_file( r" - #[test] + #[ test ] fn fail() { assert!(false); } diff --git a/module/move/willbe/tests/inc/command/tests_run.rs b/module/move/willbe/tests/inc/command/tests_run.rs index 94ec245e31..5b3c31620a 100644 --- a/module/move/willbe/tests/inc/command/tests_run.rs +++ b/module/move/willbe/tests/inc/command/tests_run.rs @@ -19,7 +19,7 @@ fn status_code_1_on_failure() let project = ProjectBuilder::new( "status_code" ) .toml_file( "" ) .test_file( r" - #[test] + #[ test ] fn should_fail() { panic!(); } @@ -43,7 +43,7 @@ fn status_code_not_zero_on_failure() let project = ProjectBuilder::new( "status_code" ) .toml_file( "" ) .test_file( r" - #[test] + #[ test ] fn should_fail() { panic!(); } @@ -67,7 +67,7 @@ fn status_code_not_zero_on_compile_error() let project = ProjectBuilder::new( "status_code" ) .toml_file( "" ) .test_file( r#" - #[test] + #[ test ] fn should_fail() { compile_error!("=-="); } diff --git a/module/move/willbe/tests/inc/tool/query_test.rs b/module/move/willbe/tests/inc/tool/query_test.rs index 9a53d60127..c5de225dbf 100644 --- a/module/move/willbe/tests/inc/tool/query_test.rs +++ b/module/move/willbe/tests/inc/tool/query_test.rs @@ -49,7 +49,7 @@ fn parse_empty_string() assert_eq!( parse( "()" ).unwrap().into_vec(), vec![] ); } -#[test] +#[ test ] fn parse_single_value() { let mut expected_map = HashMap::new(); diff --git a/module/move/wplot/License b/module/move/wplot/License index 0804aed8e3..72c80c1308 100644 --- a/module/move/wplot/License +++ b/module/move/wplot/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/postponed/_video_experiment/License b/module/postponed/_video_experiment/License index 0804aed8e3..72c80c1308 100644 --- a/module/postponed/_video_experiment/License +++ b/module/postponed/_video_experiment/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/postponed/automata_tools/License b/module/postponed/automata_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/postponed/automata_tools/License +++ b/module/postponed/automata_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/postponed/non_std/License b/module/postponed/non_std/License index 0804aed8e3..72c80c1308 100644 --- a/module/postponed/non_std/License +++ b/module/postponed/non_std/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/postponed/std_tools/License b/module/postponed/std_tools/License index 0804aed8e3..72c80c1308 100644 --- a/module/postponed/std_tools/License +++ b/module/postponed/std_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/postponed/std_x/License b/module/postponed/std_x/License index 0804aed8e3..72c80c1308 100644 --- a/module/postponed/std_x/License +++ b/module/postponed/std_x/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/postponed/type_constructor/License b/module/postponed/type_constructor/License index 0804aed8e3..72c80c1308 100644 --- a/module/postponed/type_constructor/License +++ b/module/postponed/type_constructor/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/postponed/wautomata/License b/module/postponed/wautomata/License index 0804aed8e3..72c80c1308 100644 --- a/module/postponed/wautomata/License +++ b/module/postponed/wautomata/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/postponed/wpublisher/License b/module/postponed/wpublisher/License index 0804aed8e3..72c80c1308 100644 --- a/module/postponed/wpublisher/License +++ b/module/postponed/wpublisher/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/template/template_alias/License b/module/template/template_alias/License index 0804aed8e3..72c80c1308 100644 --- a/module/template/template_alias/License +++ b/module/template/template_alias/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/template/template_blank/License b/module/template/template_blank/License index 0804aed8e3..72c80c1308 100644 --- a/module/template/template_blank/License +++ b/module/template/template_blank/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/template/template_procedural_macro/License b/module/template/template_procedural_macro/License index c32986cee3..a23529f45b 100644 --- a/module/template/template_procedural_macro/License +++ b/module/template/template_procedural_macro/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/template/template_procedural_macro_meta/License b/module/template/template_procedural_macro_meta/License index c32986cee3..a23529f45b 100644 --- a/module/template/template_procedural_macro_meta/License +++ b/module/template/template_procedural_macro_meta/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/module/template/template_procedural_macro_runtime/License b/module/template/template_procedural_macro_runtime/License index c32986cee3..a23529f45b 100644 --- a/module/template/template_procedural_macro_runtime/License +++ b/module/template/template_procedural_macro_runtime/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2024 +Copyright Kostiantyn Mysnyk and Out of the Box Systems (c) 2021-2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation