diff --git a/.cargo/config.toml b/.cargo/config.toml index 38ed1d83cd..70aeac4add 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,7 +1,14 @@ +# qqq : xxx : explain purpose of each line [env] MODULES_PATH = { value = "module", relative = true } WORKSPACE_PATH = { value = ".", relative = true } [net] # offline = true + +# [build] +# rustdocflags = [ "--cfg", "feature=\"normal_build\"" ] + +# [alias] +# test = "test --doc --features normal_build,enabled" diff --git a/.github/workflows/module_assistant_push.yml b/.github/workflows/module_asbytes_push.yml similarity index 74% rename from .github/workflows/module_assistant_push.yml rename to .github/workflows/module_asbytes_push.yml index 347fee39db..1bdb9cd947 100644 --- a/.github/workflows/module_assistant_push.yml +++ b/.github/workflows/module_asbytes_push.yml @@ -1,4 +1,4 @@ -name : assistant +name : asbytes on : push : @@ -13,12 +13,12 @@ env : jobs : - # assistant + # asbytes test : uses : Wandalen/wTools/.github/workflows/standard_rust_push.yml@alpha with : - manifest_path : 'module/move/assistant/Cargo.toml' - module_name : 'assistant' + manifest_path : 'module/core/asbytes/Cargo.toml' + module_name : 'asbytes' commit_message : ${{ github.event.head_commit.message }} commiter_username: ${{ github.event.head_commit.committer.username }} diff --git a/.github/workflows/module_async_from_push.yml b/.github/workflows/module_async_from_push.yml new file mode 100644 index 0000000000..dd4257fe08 --- /dev/null +++ b/.github/workflows/module_async_from_push.yml @@ -0,0 +1,24 @@ +name : async_from + +on : + push : + branches : + - 'alpha' + - 'beta' + - 'master' + + +env : + CARGO_TERM_COLOR : always + +jobs : + + # async_from + + test : + uses : Wandalen/wTools/.github/workflows/standard_rust_push.yml@alpha + with : + manifest_path : 'module/core/async_from/Cargo.toml' + module_name : 'async_from' + commit_message : ${{ github.event.head_commit.message }} + commiter_username: ${{ github.event.head_commit.committer.username }} diff --git a/.github/workflows/module_async_tools_push.yml b/.github/workflows/module_async_tools_push.yml new file mode 100644 index 0000000000..0310131fd5 --- /dev/null +++ b/.github/workflows/module_async_tools_push.yml @@ -0,0 +1,24 @@ +name : async_tools + +on : + push : + branches : + - 'alpha' + - 'beta' + - 'master' + + +env : + CARGO_TERM_COLOR : always + +jobs : + + # async_tools + + test : + uses : Wandalen/wTools/.github/workflows/standard_rust_push.yml@alpha + with : + manifest_path : 'module/core/async_tools/Cargo.toml' + module_name : 'async_tools' + commit_message : ${{ github.event.head_commit.message }} + commiter_username: ${{ github.event.head_commit.committer.username }} diff --git a/.github/workflows/module_graphs_tools_deprecated_push.yml b/.github/workflows/module_graphs_tools_deprecated_push.yml new file mode 100644 index 0000000000..2ebcbf9176 --- /dev/null +++ b/.github/workflows/module_graphs_tools_deprecated_push.yml @@ -0,0 +1,24 @@ +name : graphs_tools_deprecated + +on : + push : + branches : + - 'alpha' + - 'beta' + - 'master' + + +env : + CARGO_TERM_COLOR : always + +jobs : + + # graphs_tools_deprecated + + test : + uses : Wandalen/wTools/.github/workflows/standard_rust_push.yml@alpha + with : + manifest_path : 'module/move/graphs_tools_deprecated/Cargo.toml' + module_name : 'graphs_tools_deprecated' + commit_message : ${{ github.event.head_commit.message }} + commiter_username: ${{ github.event.head_commit.committer.username }} diff --git a/.github/workflows/module_graphtools_push.yml b/.github/workflows/module_graphtools_push.yml new file mode 100644 index 0000000000..81fcfc6c0d --- /dev/null +++ b/.github/workflows/module_graphtools_push.yml @@ -0,0 +1,24 @@ +name : graphtools + +on : + push : + branches : + - 'alpha' + - 'beta' + - 'master' + + +env : + CARGO_TERM_COLOR : always + +jobs : + + # graphtools + + test : + uses : Wandalen/wTools/.github/workflows/standard_rust_push.yml@alpha + with : + manifest_path : 'module/blank/graphtools/Cargo.toml' + module_name : 'graphtools' + commit_message : ${{ github.event.head_commit.message }} + commiter_username: ${{ github.event.head_commit.committer.username }} diff --git a/.github/workflows/module_gspread_push.yml b/.github/workflows/module_gspread_push.yml new file mode 100644 index 0000000000..b13aad55e7 --- /dev/null +++ b/.github/workflows/module_gspread_push.yml @@ -0,0 +1,24 @@ +name : gspread + +on : + push : + branches : + - 'alpha' + - 'beta' + - 'master' + + +env : + CARGO_TERM_COLOR : always + +jobs : + + # gspread + + test : + uses : Wandalen/wTools/.github/workflows/standard_rust_push.yml@alpha + with : + manifest_path : 'module/move/gspread/Cargo.toml' + module_name : 'gspread' + commit_message : ${{ github.event.head_commit.message }} + commiter_username: ${{ github.event.head_commit.committer.username }} diff --git a/.github/workflows/module_mindx_12_push.yml b/.github/workflows/module_mindx_12_push.yml new file mode 100644 index 0000000000..dc1bf11265 --- /dev/null +++ b/.github/workflows/module_mindx_12_push.yml @@ -0,0 +1,24 @@ +name : mindx12 + +on : + push : + branches : + - 'alpha' + - 'beta' + - 'master' + + +env : + CARGO_TERM_COLOR : always + +jobs : + + # mindx12 + + test : + uses : Wandalen/wTools/.github/workflows/standard_rust_push.yml@alpha + with : + manifest_path : 'module/blank/mindx12/Cargo.toml' + module_name : 'mindx12' + commit_message : ${{ github.event.head_commit.message }} + commiter_username: ${{ github.event.head_commit.committer.username }} diff --git a/.github/workflows/module_mingl_push.yml b/.github/workflows/module_mingl_push.yml new file mode 100644 index 0000000000..c6ce82da26 --- /dev/null +++ b/.github/workflows/module_mingl_push.yml @@ -0,0 +1,24 @@ +name : mingl + +on : + push : + branches : + - 'alpha' + - 'beta' + - 'master' + + +env : + CARGO_TERM_COLOR : always + +jobs : + + # mingl + + test : + uses : Wandalen/wTools/.github/workflows/standard_rust_push.yml@alpha + with : + manifest_path : 'module/blank/mingl/Cargo.toml' + module_name : 'mingl' + commit_message : ${{ github.event.head_commit.message }} + commiter_username: ${{ github.event.head_commit.committer.username }} diff --git a/.github/workflows/module_minmetal_push.yml b/.github/workflows/module_minmetal_push.yml new file mode 100644 index 0000000000..e76b7cf916 --- /dev/null +++ b/.github/workflows/module_minmetal_push.yml @@ -0,0 +1,24 @@ +name : minmetal + +on : + push : + branches : + - 'alpha' + - 'beta' + - 'master' + + +env : + CARGO_TERM_COLOR : always + +jobs : + + # minmetal + + test : + uses : Wandalen/wTools/.github/workflows/standard_rust_push.yml@alpha + with : + manifest_path : 'module/blank/minmetal/Cargo.toml' + module_name : 'minmetal' + commit_message : ${{ github.event.head_commit.message }} + commiter_username: ${{ github.event.head_commit.committer.username }} diff --git a/.github/workflows/module_minopengl_push.yml b/.github/workflows/module_minopengl_push.yml new file mode 100644 index 0000000000..5d412de534 --- /dev/null +++ b/.github/workflows/module_minopengl_push.yml @@ -0,0 +1,24 @@ +name : minopengl + +on : + push : + branches : + - 'alpha' + - 'beta' + - 'master' + + +env : + CARGO_TERM_COLOR : always + +jobs : + + # minopengl + + test : + uses : Wandalen/wTools/.github/workflows/standard_rust_push.yml@alpha + with : + manifest_path : 'module/blank/minopengl/Cargo.toml' + module_name : 'minopengl' + commit_message : ${{ github.event.head_commit.message }} + commiter_username: ${{ github.event.head_commit.committer.username }} diff --git a/.github/workflows/module_minvulkan_push.yml b/.github/workflows/module_minvulkan_push.yml new file mode 100644 index 0000000000..1350cf0693 --- /dev/null +++ b/.github/workflows/module_minvulkan_push.yml @@ -0,0 +1,24 @@ +name : minvulkan + +on : + push : + branches : + - 'alpha' + - 'beta' + - 'master' + + +env : + CARGO_TERM_COLOR : always + +jobs : + + # minvulkan + + test : + uses : Wandalen/wTools/.github/workflows/standard_rust_push.yml@alpha + with : + manifest_path : 'module/blank/minvulkan/Cargo.toml' + module_name : 'minvulkan' + commit_message : ${{ github.event.head_commit.message }} + commiter_username: ${{ github.event.head_commit.committer.username }} diff --git a/.github/workflows/module_minwebgl_push.yml b/.github/workflows/module_minwebgl_push.yml new file mode 100644 index 0000000000..4d63735d10 --- /dev/null +++ b/.github/workflows/module_minwebgl_push.yml @@ -0,0 +1,24 @@ +name : minwebgl + +on : + push : + branches : + - 'alpha' + - 'beta' + - 'master' + + +env : + CARGO_TERM_COLOR : always + +jobs : + + # minwebgl + + test : + uses : Wandalen/wTools/.github/workflows/standard_rust_push.yml@alpha + with : + manifest_path : 'module/blank/minwebgl/Cargo.toml' + module_name : 'minwebgl' + commit_message : ${{ github.event.head_commit.message }} + commiter_username: ${{ github.event.head_commit.committer.username }} diff --git a/.github/workflows/module_minwebgpu_push.yml b/.github/workflows/module_minwebgpu_push.yml new file mode 100644 index 0000000000..4e8992613d --- /dev/null +++ b/.github/workflows/module_minwebgpu_push.yml @@ -0,0 +1,24 @@ +name : minwebgpu + +on : + push : + branches : + - 'alpha' + - 'beta' + - 'master' + + +env : + CARGO_TERM_COLOR : always + +jobs : + + # minwebgpu + + test : + uses : Wandalen/wTools/.github/workflows/standard_rust_push.yml@alpha + with : + manifest_path : 'module/blank/minwebgpu/Cargo.toml' + module_name : 'minwebgpu' + commit_message : ${{ github.event.head_commit.message }} + commiter_username: ${{ github.event.head_commit.committer.username }} diff --git a/.github/workflows/module_minwgpu_push.yml b/.github/workflows/module_minwgpu_push.yml new file mode 100644 index 0000000000..382a15d19b --- /dev/null +++ b/.github/workflows/module_minwgpu_push.yml @@ -0,0 +1,24 @@ +name : minwgpu + +on : + push : + branches : + - 'alpha' + - 'beta' + - 'master' + + +env : + CARGO_TERM_COLOR : always + +jobs : + + # minwgpu + + test : + uses : Wandalen/wTools/.github/workflows/standard_rust_push.yml@alpha + with : + manifest_path : 'module/blank/minwgpu/Cargo.toml' + module_name : 'minwgpu' + commit_message : ${{ github.event.head_commit.message }} + commiter_username: ${{ github.event.head_commit.committer.username }} diff --git a/.github/workflows/module_proper_path_tools_push.yml b/.github/workflows/module_proper_path_tools_push.yml index ebfcf83964..238a9cdb3f 100644 --- a/.github/workflows/module_proper_path_tools_push.yml +++ b/.github/workflows/module_proper_path_tools_push.yml @@ -18,7 +18,7 @@ jobs : test : uses : Wandalen/wTools/.github/workflows/standard_rust_push.yml@alpha with : - manifest_path : 'module/core/proper_path_tools/Cargo.toml' + manifest_path : 'module/blank/proper_path_tools/Cargo.toml' module_name : 'proper_path_tools' commit_message : ${{ github.event.head_commit.message }} commiter_username: ${{ github.event.head_commit.committer.username }} diff --git a/.github/workflows/module_pth_push.yml b/.github/workflows/module_pth_push.yml new file mode 100644 index 0000000000..ddff538916 --- /dev/null +++ b/.github/workflows/module_pth_push.yml @@ -0,0 +1,24 @@ +name : pth + +on : + push : + branches : + - 'alpha' + - 'beta' + - 'master' + + +env : + CARGO_TERM_COLOR : always + +jobs : + + # pth + + test : + uses : Wandalen/wTools/.github/workflows/standard_rust_push.yml@alpha + with : + manifest_path : 'module/core/pth/Cargo.toml' + module_name : 'pth' + commit_message : ${{ github.event.head_commit.message }} + commiter_username: ${{ github.event.head_commit.committer.username }} diff --git a/.github/workflows/standard_rust_push.yml b/.github/workflows/standard_rust_push.yml index d2fd96bae2..a243d1affe 100644 --- a/.github/workflows/standard_rust_push.yml +++ b/.github/workflows/standard_rust_push.yml @@ -64,20 +64,32 @@ jobs : - name: Build module run: cd ${{ steps.rootpath.outputs.path }} && cargo build && cd - - name: Audit the modules + id: audit run: make audit continue-on-error: true - name: Generate documentation for the modules + id: documentation run: make doc open=no manifest_path=${{ inputs.manifest_path }} continue-on-error: true - name: Lint the modules + id: lint run: make lint manifest_path=${{ inputs.manifest_path }} warnings=no continue-on-error: true - name: Check the modules + id: check run: make check manifest_path=${{ inputs.manifest_path }} continue-on-error: true - name: Check the modules dependencies + id: udeps run: cargo +nightly udeps --all-targets --manifest-path ${{ inputs.manifest_path }} continue-on-error: true + # Added IDs for each step in the test job: This allows referencing the result of each step later. + # + # "Check for errors" step: Now checks the outcome status for each step. + # If any of them have a value of 'failure', this step will fail the job by returning exit 1. + - name: Check for errors + if: steps.audit.outcome != 'success' || steps.documentation.outcome != 'success' || steps.lint.outcome != 'success' || steps.check.outcome != 'success' || steps.udeps.outcome != 'success' + run: exit 1 # release: # if: contains( inputs.commit_message, '+test' ) || contains( inputs.commit_message, 'merge' ) @@ -125,6 +137,9 @@ jobs : # run: cargo miri test --manifest-path ${{ inputs.manifest_path }} will_test : +# This section ensures that `job` `will_test` will only be executed after `checkmate` and if `checkmate` fails, no tests will be run. + needs : + - checkmate if : contains( inputs.commit_message, '+test' ) || inputs.commiter_username == 'web-flow' || startsWith( inputs.commit_message, 'merge' ) concurrency : group : standard_rust_push_${{ inputs.module_name }}_${{ github.ref }}_${{ matrix.os }} 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 8e5c12e5cc..8b9862db60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,9 +29,9 @@ discord_url = "https://discord.gg/m3YfbXpUUY" # Source :: https://github.com/obox-systems/conventions/blob/master/code_style.md#lints-and-warnings # Denies non-idiomatic code for Rust 2018 edition. -rust_2018_idioms = "deny" +rust_2018_idioms = { level = "deny", priority = -1 } # Denies using features that may break in future Rust versions. -future_incompatible = "deny" +future_incompatible = { level = "deny", priority = -1 } # Warns if public items lack documentation. missing_docs = "warn" # Warns for public types not implementing Debug. @@ -41,9 +41,9 @@ unsafe-code = "warn" [workspace.lints.clippy] # Denies restrictive lints, limiting certain language features/patterns. -restriction = "warn" +#restriction = { level = "deny", priority = -1 } # Denies pedantic lints, enforcing strict coding styles and conventions. -pedantic = "warn" +pedantic = { level = "warn", priority = -1 } # Denies undocumented unsafe blocks. undocumented_unsafe_blocks = "deny" # xxx : check @@ -80,7 +80,7 @@ path = "module/alias/std_x" ## data_type [workspace.dependencies.data_type] -version = "~0.9.0" +version = "~0.13.0" path = "module/core/data_type" default-features = false @@ -98,19 +98,19 @@ default-features = false # path = "module/core/type_constructor_derive_pair_meta" [workspace.dependencies.interval_adapter] -version = "~0.23.0" +version = "~0.29.0" path = "module/core/interval_adapter" default-features = false -features = [ "enabled" ] +# features = [ "enabled" ] [workspace.dependencies.winterval] version = "~0.3.0" path = "module/alias/winterval" default-features = false -features = [ "enabled" ] +# features = [ "enabled" ] [workspace.dependencies.collection_tools] -version = "~0.11.0" +version = "~0.18.0" path = "module/core/collection_tools" default-features = false @@ -118,36 +118,43 @@ default-features = false ## derive [workspace.dependencies.derive_tools] -version = "~0.27.0" +version = "~0.35.0" path = "module/core/derive_tools" default-features = false -features = [ "enabled" ] +# features = [ "enabled" ] [workspace.dependencies.derive_tools_meta] -version = "~0.26.0" +version = "~0.34.0" path = "module/core/derive_tools_meta" default-features = false -features = [ "enabled" ] +# features = [ "enabled" ] [workspace.dependencies.reflect_tools] -version = "~0.3.0" +version = "~0.6.0" path = "module/core/reflect_tools" default-features = false -features = [ "enabled" ] +# features = [ "enabled" ] [workspace.dependencies.reflect_tools_meta] -version = "~0.3.0" +version = "~0.6.0" path = "module/core/reflect_tools_meta" default-features = false -features = [ "enabled" ] +# features = [ "enabled" ] [workspace.dependencies.format_tools] -version = "~0.2.0" +version = "~0.5.0" path = "module/core/format_tools" default-features = false -features = [ "enabled" ] +# features = [ "enabled" ] # xxx : remove features, maybe +# [workspace.dependencies.format_tools] +# version = "~0.1.0" +# path = "module/core/format_tools" +# default-features = false +# features = [ "enabled" ] +# # xxx : remove features, maybe + # [workspace.dependencies.type_constructor] # version = "~0.3.0" # path = "module/core/type_constructor" @@ -159,33 +166,33 @@ path = "module/alias/fundamental_data_type" default-features = false [workspace.dependencies.variadic_from] -version = "~0.22.0" +version = "~0.30.0" path = "module/core/variadic_from" default-features = false -features = [ "enabled" ] +# features = [ "enabled" ] [workspace.dependencies.clone_dyn] -version = "~0.23.0" +version = "~0.32.0" path = "module/core/clone_dyn" default-features = false -features = [ "enabled" ] +# features = [ "enabled" ] [workspace.dependencies.clone_dyn_meta] -version = "~0.23.0" +version = "~0.30.0" path = "module/core/clone_dyn_meta" -features = [ "enabled" ] +# features = [ "enabled" ] [workspace.dependencies.clone_dyn_types] -version = "~0.22.0" +version = "~0.31.0" path = "module/core/clone_dyn_types" default-features = false -features = [ "enabled" ] +# features = [ "enabled" ] ## mem [workspace.dependencies.mem_tools] -version = "~0.6.0" +version = "~0.8.0" path = "module/core/mem_tools" default-features = false @@ -193,7 +200,7 @@ default-features = false ## diagnostics [workspace.dependencies.diagnostics_tools] -version = "~0.8.0" +version = "~0.10.0" path = "module/core/diagnostics_tools" default-features = false @@ -201,7 +208,7 @@ default-features = false ## iter [workspace.dependencies.iter_tools] -version = "~0.20.0" +version = "~0.29.0" path = "module/core/iter_tools" default-features = false @@ -209,51 +216,61 @@ default-features = false ## meta [workspace.dependencies.meta_tools] -version = "~0.10.0" +version = "~0.12.0" path = "module/core/meta_tools" default-features = false [workspace.dependencies.for_each] -version = "~0.8.0" +version = "~0.10.0" path = "module/core/for_each" default-features = false [workspace.dependencies.former] -version = "~2.8.0" +version = "~2.17.0" path = "module/core/former" default-features = false -# [workspace.dependencies.former_stable] -# package = "former" -# version = "=2.2.0" -# default-features = false - [workspace.dependencies.former_meta] -version = "~2.8.0" +version = "~2.16.0" path = "module/core/former_meta" default-features = false [workspace.dependencies.former_types] -version = "~2.7.0" +version = "~2.15.0" path = "module/core/former_types" default-features = false +[workspace.dependencies.component_model] +version = "~0.1.0" +path = "module/core/component_model" +default-features = false + +[workspace.dependencies.component_model_meta] +version = "~0.1.0" +path = "module/core/component_model_meta" +default-features = false + +[workspace.dependencies.component_model_types] +version = "~0.1.0" +path = "module/core/component_model_types" +default-features = false + [workspace.dependencies.impls_index] -version = "~0.7.0" +version = "~0.10.0" path = "module/core/impls_index" default-features = false [workspace.dependencies.impls_index_meta] -version = "~0.7.0" +version = "~0.12.0" path = "module/core/impls_index_meta" [workspace.dependencies.mod_interface] -version = "~0.23.0" +version = "~0.33.0" path = "module/core/mod_interface" default-features = false [workspace.dependencies.mod_interface_meta] -version = "~0.23.0" +version = "~0.31.0" path = "module/core/mod_interface_meta" default-features = false @@ -279,7 +296,7 @@ default-features = false ## macro tools [workspace.dependencies.macro_tools] -version = "~0.39.0" +version = "~0.53.0" path = "module/core/macro_tools" default-features = false @@ -305,12 +322,12 @@ default-features = false ## typing [workspace.dependencies.typing_tools] -version = "~0.8.0" +version = "~0.10.0" path = "module/core/typing_tools" default-features = false [workspace.dependencies.implements] -version = "~0.8.0" +version = "~0.12.0" path = "module/core/implements" default-features = false @@ -320,20 +337,25 @@ path = "module/alias/instance_of" default-features = false [workspace.dependencies.inspect_type] -version = "~0.10.0" +version = "~0.14.0" path = "module/core/inspect_type" default-features = false [workspace.dependencies.is_slice] -version = "~0.9.0" +version = "~0.13.0" path = "module/core/is_slice" default-features = false +[workspace.dependencies.asbytes] +version = "~0.2.0" +path = "module/core/asbytes" +default-features = false + ## error [workspace.dependencies.error_tools] -version = "~0.16.0" +version = "~0.20.0" path = "module/core/error_tools" default-features = false @@ -345,7 +367,7 @@ path = "module/alias/werror" ## string tools [workspace.dependencies.strs_tools] -version = "~0.16.0" +version = "~0.18.0" path = "module/core/strs_tools" default-features = false @@ -366,23 +388,28 @@ version = "~0.1.0" path = "module/alias/file_tools" default-features = false -[workspace.dependencies.proper_path_tools] -version = "~0.9.0" -path = "module/core/proper_path_tools" +[workspace.dependencies.pth] +version = "~0.23.0" +path = "module/core/pth" default-features = false +# [workspace.dependencies.proper_path_tools] +# version = "~0.15.0" +# path = "module/core/proper_path_tools" +# default-features = false + ## process tools [workspace.dependencies.process_tools] -version = "~0.8.0" +version = "~0.13.0" path = "module/core/process_tools" default-features = false -[workspace.dependencies.process_tools_published] -package = "process_tools" -version = "~0.8.0" -default-features = false +# [workspace.dependencies.process_tools_published] +# package = "process_tools" +# version = "~0.9.0" +# default-features = false ## test @@ -392,19 +419,34 @@ version = "~0.4.0" path = "module/alias/wtest" [workspace.dependencies.test_tools] -version = "~0.9.0" +version = "~0.14.0" path = "module/core/test_tools" +features = [ "full" ] + +# [workspace.dependencies.test_tools_stable] +# package = "test_tools" +# version = "~0.10.0" +# features = [ "full" ] [workspace.dependencies.wtest_basic] version = "~0.4.0" path = "module/alias/wtest_basic" +## async + +[workspace.dependencies.async_from] +version = "~0.2.0" +path = "module/core/async_from" + +[workspace.dependencies.async_tools] +version = "~0.1.0" +path = "module/core/async_tools" ## graphs tools [workspace.dependencies.graphs_tools] -version = "~0.2.0" +version = "~0.3.0" path = "module/move/graphs_tools" default-features = false @@ -422,7 +464,7 @@ default-features = false ## ca [workspace.dependencies.wca] -version = "~0.20.0" +version = "~0.24.0" path = "module/move/wca" @@ -436,7 +478,7 @@ path = "module/move/wcensor" ## willbe [workspace.dependencies.willbe] -version = "~0.14.0" +version = "~0.21.0" path = "module/move/willbe" @@ -472,13 +514,21 @@ version = "~0.2.0" path = "module/move/sqlx_query" [workspace.dependencies.deterministic_rand] -version = "~0.5.0" +version = "~0.6.0" path = "module/move/deterministic_rand" [workspace.dependencies.crates_tools] -version = "~0.12.0" +version = "~0.15.0" path = "module/move/crates_tools" +[workspace.dependencies.assistant] +version = "~0.1.0" +path = "module/move/assistant" + +[workspace.dependencies.llm_tools] +version = "~0.2.0" +path = "module/move/llm_tools" + ## steps @@ -507,3 +557,100 @@ default-features = true version = "~0.3.0" path = "module/test/c" default-features = true + +## External + +[workspace.dependencies.async-trait] +version = "0.1.83" + +[workspace.dependencies.tokio] +version = "1.41.0" +features = [] +default-features = false + +[workspace.dependencies.anyhow] +version = "~1.0" +# features = [] +# default-features = false + +[workspace.dependencies.thiserror] +version = "~1.0" +# features = [] +# default-features = false + +[workspace.dependencies.pretty_assertions] +version = "~1.4.0" +# features = [] +# default-features = false + +[workspace.dependencies.hashbrown] +version = "~0.14.3" +# optional = true +default-features = false +# features = [ "default" ] + +[workspace.dependencies.paste] +version = "~1.0.14" +default-features = false + +[workspace.dependencies.tempdir] +version = "~0.3.7" + +[workspace.dependencies.rustversion] +version = "~1.0" + +[workspace.dependencies.num-traits] +version = "~0.2" + +[workspace.dependencies.rand] +version = "0.8.5" + +[workspace.dependencies.trybuild] +version = "1.0.85" + +[workspace.dependencies.futures-core] +version = "0.3.31" + +[workspace.dependencies.futures-util] +version = "0.3.31" + +[workspace.dependencies.regex] +version = "1.11.1" + +[workspace.dependencies.serde] +version = "1.0.219" + +[workspace.dependencies.serde_with] +version = "3.12.0" + +[workspace.dependencies.serde_json] +version = "1.0.140" + +[workspace.dependencies.serde_yaml] +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 45e341b29e..4bcf528c1b 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,10 @@ sync : git.sync # make audit audit : - cargo 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 # # === General commands diff --git a/Readme.md b/Readme.md index 36612d9970..c805b6d673 100644 --- a/Readme.md +++ b/Readme.md @@ -38,16 +38,22 @@ Collection of general purpose tools for solving problems. Fundamentally extend t | [is_slice](module/core/is_slice) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_is_slice_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_is_slice_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_is_slice_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_is_slice_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/is_slice) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fis_slice%2Fexamples%2Fis_slice_trivial.rs,RUN_POSTFIX=--example%20is_slice_trivial/https://github.com/Wandalen/wTools) | | [mod_interface](module/core/mod_interface) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_mod_interface_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_mod_interface_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_mod_interface_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_mod_interface_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/mod_interface) | | | [reflect_tools_meta](module/core/reflect_tools_meta) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_reflect_tools_meta_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_reflect_tools_meta_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_reflect_tools_meta_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_reflect_tools_meta_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/reflect_tools_meta) | | +| [async_from](module/core/async_from) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_async_from_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_async_from_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_async_from_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_async_from_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/async_from) | | | [data_type](module/core/data_type) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_data_type_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_data_type_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_data_type_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_data_type_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/data_type) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fdata_type%2Fexamples%2Fdata_type_trivial.rs,RUN_POSTFIX=--example%20data_type_trivial/https://github.com/Wandalen/wTools) | | [diagnostics_tools](module/core/diagnostics_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_diagnostics_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_diagnostics_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_diagnostics_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_diagnostics_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/diagnostics_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fdiagnostics_tools%2Fexamples%2Fdiagnostics_tools_trivial.rs,RUN_POSTFIX=--example%20diagnostics_tools_trivial/https://github.com/Wandalen/wTools) | | [error_tools](module/core/error_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_error_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_error_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_error_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_error_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/error_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Ferror_tools%2Fexamples%2Ferror_tools_trivial.rs,RUN_POSTFIX=--example%20error_tools_trivial/https://github.com/Wandalen/wTools) | | [mem_tools](module/core/mem_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_mem_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_mem_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_mem_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_mem_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/mem_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fmem_tools%2Fexamples%2Fmem_tools_trivial.rs,RUN_POSTFIX=--example%20mem_tools_trivial/https://github.com/Wandalen/wTools) | | [meta_tools](module/core/meta_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_meta_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_meta_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_meta_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_meta_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/meta_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fmeta_tools%2Fexamples%2Fmeta_tools_trivial.rs,RUN_POSTFIX=--example%20meta_tools_trivial/https://github.com/Wandalen/wTools) | -| [proper_path_tools](module/core/proper_path_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_proper_path_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_proper_path_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_proper_path_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_proper_path_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/proper_path_tools) | | +| [pth](module/core/pth) | [![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_pth_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_pth_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_pth_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_pth_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/pth) | | | [reflect_tools](module/core/reflect_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_reflect_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_reflect_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_reflect_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_reflect_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/reflect_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Freflect_tools%2Fexamples%2Freflect_tools_trivial.rs,RUN_POSTFIX=--example%20reflect_tools_trivial/https://github.com/Wandalen/wTools) | | [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) | [![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) | | +| [component_model](module/core/component_model) | [![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_component_model_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_component_model_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_component_model_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_component_model_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/component_model) | | +| [component_model_meta](module/core/component_model_meta) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_component_model_meta_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_component_model_meta_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_component_model_meta_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_component_model_meta_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/component_model_meta) | | +| [component_model_types](module/core/component_model_types) | [![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_component_model_types_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_component_model_types_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_component_model_types_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_component_model_types_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/component_model_types) | | | [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) | | | [include_md](module/core/include_md) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_include_md_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_include_md_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_include_md_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_include_md_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/include_md) | | @@ -66,8 +72,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) | | | [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 6d5ef8559f..72c80c1308 100644 --- a/module/alias/cargo_will/License +++ b/module/alias/cargo_will/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/cargo_will/src/bin/willbe.rs b/module/alias/cargo_will/src/bin/willbe.rs index 39d2429139..c2850a237c 100644 --- a/module/alias/cargo_will/src/bin/willbe.rs +++ b/module/alias/cargo_will/src/bin/willbe.rs @@ -6,7 +6,7 @@ #[ allow( unused_imports ) ] use::willbe::*; -fn main() -> Result< (), wtools::error::untyped::Error > +fn main() -> Result< (), error::untyped::Error > { Ok( willbe::run( std::env::args().collect() )? ) } diff --git a/module/alias/cargo_will/tests/smoke_test.rs b/module/alias/cargo_will/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/alias/cargo_will/tests/smoke_test.rs +++ b/module/alias/cargo_will/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/alias/file_tools/License b/module/alias/file_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/alias/file_tools/License +++ b/module/alias/file_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/tests/smoke_test.rs b/module/alias/file_tools/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/alias/file_tools/tests/smoke_test.rs +++ b/module/alias/file_tools/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/alias/fundamental_data_type/License b/module/alias/fundamental_data_type/License index 6d5ef8559f..72c80c1308 100644 --- a/module/alias/fundamental_data_type/License +++ b/module/alias/fundamental_data_type/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/tests/smoke_test.rs b/module/alias/fundamental_data_type/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/alias/fundamental_data_type/tests/smoke_test.rs +++ b/module/alias/fundamental_data_type/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/alias/instance_of/License b/module/alias/instance_of/License index e3e9e057cf..a23529f45b 100644 --- a/module/alias/instance_of/License +++ b/module/alias/instance_of/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/src/typing/implements_lib.rs b/module/alias/instance_of/src/typing/implements_lib.rs index 1a3f76aa7e..4129608ed8 100644 --- a/module/alias/instance_of/src/typing/implements_lib.rs +++ b/module/alias/instance_of/src/typing/implements_lib.rs @@ -15,7 +15,7 @@ // #[ macro_use ] mod implements_impl; -/// Internal namespace. +/// Define a private namespace for all its items. mod private { diff --git a/module/alias/instance_of/src/typing/is_slice_lib.rs b/module/alias/instance_of/src/typing/is_slice_lib.rs index 0f4a45cbc4..f3479787d1 100644 --- a/module/alias/instance_of/src/typing/is_slice_lib.rs +++ b/module/alias/instance_of/src/typing/is_slice_lib.rs @@ -12,7 +12,7 @@ #![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] -/// Internal namespace. +/// Define a private namespace for all its items. mod private { diff --git a/module/alias/instance_of/tests/smoke_test.rs b/module/alias/instance_of/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/alias/instance_of/tests/smoke_test.rs +++ b/module/alias/instance_of/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/alias/multilayer/License b/module/alias/multilayer/License index e3e9e057cf..a23529f45b 100644 --- a/module/alias/multilayer/License +++ b/module/alias/multilayer/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/tests/smoke_test.rs b/module/alias/multilayer/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/alias/multilayer/tests/smoke_test.rs +++ b/module/alias/multilayer/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/alias/proc_macro_tools/License b/module/alias/proc_macro_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/alias/proc_macro_tools/License +++ b/module/alias/proc_macro_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/alias/proc_macro_tools/Readme.md index 4fa511924a..1d5fa53144 100644 --- a/module/alias/proc_macro_tools/Readme.md +++ b/module/alias/proc_macro_tools/Readme.md @@ -2,7 +2,7 @@ # Module :: proc_macro_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_proc_macro_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_proc_macro_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/proc_macro_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/proc_macro_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Falias%2Fproc_macro_tools%2Fexamples%2Fproc_macro_tools_trivial.rs,RUN_POSTFIX=--example%20proc_macro_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_proc_macro_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_proc_macro_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/proc_macro_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/proc_macro_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Falias%2Fproc_macro_tools%2Fexamples%2Fproc_macro_tools_trivial.rs,RUN_POSTFIX=--example%20module%2Falias%2Fproc_macro_tools%2Fexamples%2Fproc_macro_tools_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Tools for writing procedural macros. diff --git a/module/alias/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/proc_macro_tools/tests/smoke_test.rs b/module/alias/proc_macro_tools/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/alias/proc_macro_tools/tests/smoke_test.rs +++ b/module/alias/proc_macro_tools/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/alias/proper_tools/License b/module/alias/proper_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/alias/proper_tools/License +++ b/module/alias/proper_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/proper_tools/tests/smoke_test.rs b/module/alias/proper_tools/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/alias/proper_tools/tests/smoke_test.rs +++ b/module/alias/proper_tools/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/alias/werror/License b/module/alias/werror/License index 6d5ef8559f..72c80c1308 100644 --- a/module/alias/werror/License +++ b/module/alias/werror/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/alias/werror/Readme.md index dc7e2b669a..f4b87ccaf6 100644 --- a/module/alias/werror/Readme.md +++ b/module/alias/werror/Readme.md @@ -2,7 +2,7 @@ # Module :: werror - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_werror_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_werror_push.yml) [![docs.rs](https://img.shields.io/docsrs/werror?color=e3e8f0&logo=docs.rs)](https://docs.rs/werror) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Falias%2Fwerror%2Fexamples%2Fwerror_tools_trivial.rs,RUN_POSTFIX=--example%20werror_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_werror_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_werror_push.yml) [![docs.rs](https://img.shields.io/docsrs/werror?color=e3e8f0&logo=docs.rs)](https://docs.rs/werror) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Falias%2Fwerror%2Fexamples%2Fwerror_tools_trivial.rs,RUN_POSTFIX=--example%20module%2Falias%2Fwerror%2Fexamples%2Fwerror_tools_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Basic exceptions handling mechanism. diff --git a/module/alias/werror/tests/smoke_test.rs b/module/alias/werror/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/alias/werror/tests/smoke_test.rs +++ b/module/alias/werror/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/alias/willbe2/License b/module/alias/willbe2/License index 6d5ef8559f..72c80c1308 100644 --- a/module/alias/willbe2/License +++ b/module/alias/willbe2/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/tests/smoke_test.rs b/module/alias/willbe2/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/alias/willbe2/tests/smoke_test.rs +++ b/module/alias/willbe2/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/alias/winterval/License b/module/alias/winterval/License index 6d5ef8559f..72c80c1308 100644 --- a/module/alias/winterval/License +++ b/module/alias/winterval/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/alias/winterval/Readme.md index a853161c8c..b26669fdc5 100644 --- a/module/alias/winterval/Readme.md +++ b/module/alias/winterval/Readme.md @@ -2,7 +2,7 @@ # Module :: winterval - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_winterval_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_winterval_push.yml) [![docs.rs](https://img.shields.io/docsrs/winterval?color=e3e8f0&logo=docs.rs)](https://docs.rs/winterval) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Falias%2Fwinterval%2Fexamples%2Fwinterval_trivial.rs,RUN_POSTFIX=--example%20winterval_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_winterval_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_winterval_push.yml) [![docs.rs](https://img.shields.io/docsrs/winterval?color=e3e8f0&logo=docs.rs)](https://docs.rs/winterval) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Falias%2Fwinterval%2Fexamples%2Fwinterval_trivial.rs,RUN_POSTFIX=--example%20module%2Falias%2Fwinterval%2Fexamples%2Fwinterval_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Integer interval adapter for both Range and RangeInclusive. diff --git a/module/alias/wproc_macro/License b/module/alias/wproc_macro/License index 6d5ef8559f..72c80c1308 100644 --- a/module/alias/wproc_macro/License +++ b/module/alias/wproc_macro/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/tests/smoke_test.rs b/module/alias/wproc_macro/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/alias/wproc_macro/tests/smoke_test.rs +++ b/module/alias/wproc_macro/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/alias/wstring_tools/License b/module/alias/wstring_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/alias/wstring_tools/License +++ b/module/alias/wstring_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/alias/wstring_tools/Readme.md index ff1e2a4f75..68e309be8a 100644 --- a/module/alias/wstring_tools/Readme.md +++ b/module/alias/wstring_tools/Readme.md @@ -2,7 +2,7 @@ # Module :: wstring_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_wstring_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_wstring_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/wstring_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/wstring_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Falias%2Fwstring_tools%2Fexamples%2Fwstring_toolst_trivial_sample.rs,RUN_POSTFIX=--example%20wstring_toolst_trivial_sample/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_wstring_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_wstring_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/wstring_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/wstring_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Falias%2Fwstring_tools%2Fexamples%2Fwstring_toolst_trivial_sample.rs,RUN_POSTFIX=--example%20module%2Falias%2Fwstring_tools%2Fexamples%2Fwstring_toolst_trivial_sample.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Tools to manipulate strings. diff --git a/module/alias/wstring_tools/tests/smoke_test.rs b/module/alias/wstring_tools/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/alias/wstring_tools/tests/smoke_test.rs +++ b/module/alias/wstring_tools/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/alias/wtest/License b/module/alias/wtest/License index e3e9e057cf..a23529f45b 100644 --- a/module/alias/wtest/License +++ b/module/alias/wtest/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/alias/wtest/Readme.md index 0f3d21c9a8..7fbe8a29fe 100644 --- a/module/alias/wtest/Readme.md +++ b/module/alias/wtest/Readme.md @@ -2,7 +2,7 @@ # Module :: wtest - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_wtest_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_wtest_push.yml) [![docs.rs](https://img.shields.io/docsrs/wtest?color=e3e8f0&logo=docs.rs)](https://docs.rs/wtest) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Falias%2Fwtest%2Fexamples%2Fwtest_trivial_sample.rs,RUN_POSTFIX=--example%20wtest_trivial_sample/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_wtest_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_wtest_push.yml) [![docs.rs](https://img.shields.io/docsrs/wtest?color=e3e8f0&logo=docs.rs)](https://docs.rs/wtest) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Falias%2Fwtest%2Fexamples%2Fwtest_trivial_sample.rs,RUN_POSTFIX=--example%20module%2Falias%2Fwtest%2Fexamples%2Fwtest_trivial_sample.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Tools for writing and running tests. diff --git a/module/alias/wtest_basic/Cargo.toml b/module/alias/wtest_basic/Cargo.toml index 3d5e3d1218..6e6ceb65fd 100644 --- a/module/alias/wtest_basic/Cargo.toml +++ b/module/alias/wtest_basic/Cargo.toml @@ -58,7 +58,7 @@ enabled = [ "test_tools/enabled" ] [dependencies] -test_tools = { workspace = true, features = [ "full" ] } +test_tools = { workspace = true, default-features = true } # ## external # @@ -76,5 +76,5 @@ test_tools = { workspace = true, features = [ "full" ] } # data_type = { workspace = true, features = [ "full" ] } # diagnostics_tools = { workspace = true, features = [ "full" ] } -# [dev-dependencies] -# test_tools = { workspace = true } +[dev-dependencies] +test_tools = { workspace = true } diff --git a/module/alias/wtest_basic/License b/module/alias/wtest_basic/License index 6d5ef8559f..72c80c1308 100644 --- a/module/alias/wtest_basic/License +++ b/module/alias/wtest_basic/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/src/test/basic/helper.rs b/module/alias/wtest_basic/src/test/basic/helper.rs index fd3f8907d2..dc9ed8372b 100644 --- a/module/alias/wtest_basic/src/test/basic/helper.rs +++ b/module/alias/wtest_basic/src/test/basic/helper.rs @@ -3,7 +3,7 @@ //! Helpers. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { diff --git a/module/alias/wtest_basic/src/test/basic/mod.rs b/module/alias/wtest_basic/src/test/basic/mod.rs index 9e9e011623..034ebb427a 100644 --- a/module/alias/wtest_basic/src/test/basic/mod.rs +++ b/module/alias/wtest_basic/src/test/basic/mod.rs @@ -3,7 +3,7 @@ //! Basic tools for testing. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { } diff --git a/module/blank/brain_tools/License b/module/blank/brain_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/blank/brain_tools/License +++ b/module/blank/brain_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/tests/inc/mod.rs b/module/blank/brain_tools/tests/inc/mod.rs index dde9de6f94..7c40be710f 100644 --- a/module/blank/brain_tools/tests/inc/mod.rs +++ b/module/blank/brain_tools/tests/inc/mod.rs @@ -1,4 +1,4 @@ -#[ allow( unused_imports ) ] use super::*; +use test_tools::exposed::*; mod basic_test; diff --git a/module/blank/draw_lang/License b/module/blank/draw_lang/License index 6d5ef8559f..72c80c1308 100644 --- a/module/blank/draw_lang/License +++ b/module/blank/draw_lang/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/tests/inc/mod.rs b/module/blank/draw_lang/tests/inc/mod.rs index dde9de6f94..7c40be710f 100644 --- a/module/blank/draw_lang/tests/inc/mod.rs +++ b/module/blank/draw_lang/tests/inc/mod.rs @@ -1,4 +1,4 @@ -#[ allow( unused_imports ) ] use super::*; +use test_tools::exposed::*; mod basic_test; diff --git a/module/blank/draw_lang/tests/smoke_test.rs b/module/blank/draw_lang/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/blank/draw_lang/tests/smoke_test.rs +++ b/module/blank/draw_lang/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/blank/drawboard/License b/module/blank/drawboard/License index 6d5ef8559f..72c80c1308 100644 --- a/module/blank/drawboard/License +++ b/module/blank/drawboard/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/tests/inc/mod.rs b/module/blank/drawboard/tests/inc/mod.rs index dde9de6f94..7c40be710f 100644 --- a/module/blank/drawboard/tests/inc/mod.rs +++ b/module/blank/drawboard/tests/inc/mod.rs @@ -1,4 +1,4 @@ -#[ allow( unused_imports ) ] use super::*; +use test_tools::exposed::*; mod basic_test; diff --git a/module/blank/drawboard/tests/smoke_test.rs b/module/blank/drawboard/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/blank/drawboard/tests/smoke_test.rs +++ b/module/blank/drawboard/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/blank/drawql/License b/module/blank/drawql/License index 6d5ef8559f..72c80c1308 100644 --- a/module/blank/drawql/License +++ b/module/blank/drawql/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/tests/inc/mod.rs b/module/blank/drawql/tests/inc/mod.rs index dde9de6f94..7c40be710f 100644 --- a/module/blank/drawql/tests/inc/mod.rs +++ b/module/blank/drawql/tests/inc/mod.rs @@ -1,4 +1,4 @@ -#[ allow( unused_imports ) ] use super::*; +use test_tools::exposed::*; mod basic_test; diff --git a/module/blank/drawql/tests/smoke_test.rs b/module/blank/drawql/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/blank/drawql/tests/smoke_test.rs +++ b/module/blank/drawql/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/blank/exe_tools/License b/module/blank/exe_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/blank/exe_tools/License +++ b/module/blank/exe_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/tests/inc/mod.rs b/module/blank/exe_tools/tests/inc/mod.rs index dde9de6f94..7c40be710f 100644 --- a/module/blank/exe_tools/tests/inc/mod.rs +++ b/module/blank/exe_tools/tests/inc/mod.rs @@ -1,4 +1,4 @@ -#[ allow( unused_imports ) ] use super::*; +use test_tools::exposed::*; mod basic_test; diff --git a/module/blank/exe_tools/tests/smoke_test.rs b/module/blank/exe_tools/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/blank/exe_tools/tests/smoke_test.rs +++ b/module/blank/exe_tools/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/move/assistant/Cargo.toml b/module/blank/graphtools/Cargo.toml similarity index 54% rename from module/move/assistant/Cargo.toml rename to module/blank/graphtools/Cargo.toml index 6e2af1a870..67a3c06564 100644 --- a/module/move/assistant/Cargo.toml +++ b/module/blank/graphtools/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "assistant" +name = "graphtools" version = "0.1.0" edition = "2021" authors = [ @@ -7,11 +7,11 @@ authors = [ ] 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" +documentation = "https://docs.rs/graphtools" +repository = "https://github.com/Wandalen/wTools/tree/master/module/blank/graphtools" +homepage = "https://github.com/Wandalen/wTools/tree/master/module/blank/graphtools" description = """ -Assist AI in writing code. +Tools to manipulate graphs. """ categories = [ "algorithms", "development-tools" ] keywords = [ "fundamental", "general-purpose" ] @@ -26,20 +26,9 @@ all-features = false [features] default = [ "enabled" ] full = [ "enabled" ] -enabled = [ - "former/enabled", - "format_tools/enabled", - "reflect_tools/enabled", -] +enabled = [] [dependencies] -# xxx : qqq : optimze features -former = { workspace = true, features = [ "full" ] } -format_tools = { workspace = true, features = [ "full" ] } -reflect_tools = { workspace = true, features = [ "full" ] } -openai-api-rs = { version = "4.0.9" } -tokio = { version = "1", features = ["full"] } -dotenv = "0.15" [dev-dependencies] test_tools = { workspace = true } diff --git a/module/move/assistant/License b/module/blank/graphtools/License similarity index 93% rename from module/move/assistant/License rename to module/blank/graphtools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/move/assistant/License +++ b/module/blank/graphtools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/blank/graphtools/Readme.md new file mode 100644 index 0000000000..175776ccd1 --- /dev/null +++ b/module/blank/graphtools/Readme.md @@ -0,0 +1,33 @@ + + +# Module :: graphtools +[![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/Modulebrain_toolsPush.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/Modulebrain_toolsPush.yml) [![docs.rs](https://img.shields.io/docsrs/graphtools?color=e3e8f0&logo=docs.rs)](https://docs.rs/graphtools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + +Tools to manipulate graphs. + + diff --git a/module/blank/graphtools/src/lib.rs b/module/blank/graphtools/src/lib.rs new file mode 100644 index 0000000000..4168554e8f --- /dev/null +++ b/module/blank/graphtools/src/lib.rs @@ -0,0 +1,11 @@ + +#![ 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/brain_tools/latest/brain_tools/" ) ] +#![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] + +/// Function description. +#[ cfg( feature = "enabled" ) ] +pub fn f1() +{ +} diff --git a/module/move/assistant/tests/inc/basic_test.rs b/module/blank/graphtools/tests/inc/basic_test.rs similarity index 100% rename from module/move/assistant/tests/inc/basic_test.rs rename to module/blank/graphtools/tests/inc/basic_test.rs diff --git a/module/blank/graphtools/tests/inc/mod.rs b/module/blank/graphtools/tests/inc/mod.rs new file mode 100644 index 0000000000..7c40be710f --- /dev/null +++ b/module/blank/graphtools/tests/inc/mod.rs @@ -0,0 +1,4 @@ +use super::*; +use test_tools::exposed::*; + +mod basic_test; diff --git a/module/move/assistant/tests/smoke_test.rs b/module/blank/graphtools/tests/smoke_test.rs similarity index 100% rename from module/move/assistant/tests/smoke_test.rs rename to module/blank/graphtools/tests/smoke_test.rs diff --git a/module/blank/graphtools/tests/tests.rs b/module/blank/graphtools/tests/tests.rs new file mode 100644 index 0000000000..574f34b114 --- /dev/null +++ b/module/blank/graphtools/tests/tests.rs @@ -0,0 +1,10 @@ + +include!( "../../../../module/step/meta/src/module/terminal.rs" ); + +#[ allow( unused_imports ) ] +use brain_tools as the_module; +#[ allow( unused_imports ) ] +use test_tools::exposed::*; + +#[ cfg( feature = "enabled" ) ] +mod inc; diff --git a/module/blank/image_tools/License b/module/blank/image_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/blank/image_tools/License +++ b/module/blank/image_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/tests/smoke_test.rs b/module/blank/image_tools/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/blank/image_tools/tests/smoke_test.rs +++ b/module/blank/image_tools/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/blank/math_tools/License b/module/blank/math_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/blank/math_tools/License +++ b/module/blank/math_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/tests/smoke_test.rs b/module/blank/math_tools/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/blank/math_tools/tests/smoke_test.rs +++ b/module/blank/math_tools/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/blank/mindx12/Cargo.toml b/module/blank/mindx12/Cargo.toml new file mode 100644 index 0000000000..6d78fd190d --- /dev/null +++ b/module/blank/mindx12/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "mindx12" +version = "0.1.0" +edition = "2021" +authors = [ + "Kostiantyn Wandalen ", +] +license = "MIT" +readme = "Readme.md" +documentation = "https://docs.rs/mindx12" +repository = "https://github.com/Wandalen/wTools/tree/master/module/core/mindx12" +homepage = "https://github.com/Wandalen/wTools/tree/master/module/core/mindx12" +description = """ +Type-based data assignment and extraction between structs. +""" +categories = [ "algorithms", "development-tools" ] +keywords = [ "fundamental", "general-purpose" ] + +[lints] +workspace = true + +[package.metadata.docs.rs] +features = [ "full" ] +all-features = false + +[features] +default = [ "enabled" ] +full = [ "enabled" ] +enabled = [] + +[dependencies] + +[dev-dependencies] +test_tools = { workspace = true } diff --git a/module/core/proper_path_tools/License b/module/blank/mindx12/License similarity index 93% rename from module/core/proper_path_tools/License rename to module/blank/mindx12/License index 6d5ef8559f..72c80c1308 100644 --- a/module/core/proper_path_tools/License +++ b/module/blank/mindx12/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/blank/mindx12/Readme.md new file mode 100644 index 0000000000..adc110369e --- /dev/null +++ b/module/blank/mindx12/Readme.md @@ -0,0 +1,33 @@ + + +# Module :: mindx12 +[![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/Modulemindx12Push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/Modulemindx12Push.yml) [![docs.rs](https://img.shields.io/docsrs/mindx12?color=e3e8f0&logo=docs.rs)](https://docs.rs/mindx12) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + +Draw language. + + diff --git a/module/blank/mindx12/src/lib.rs b/module/blank/mindx12/src/lib.rs new file mode 100644 index 0000000000..8736456366 --- /dev/null +++ b/module/blank/mindx12/src/lib.rs @@ -0,0 +1,10 @@ + +#![ 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 = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] + +/// Function description. +#[ cfg( feature = "enabled" ) ] +pub fn f1() +{ +} diff --git a/module/move/assistant/tests/inc/mod.rs b/module/blank/mindx12/tests/inc/basic_test.rs similarity index 57% rename from module/move/assistant/tests/inc/mod.rs rename to module/blank/mindx12/tests/inc/basic_test.rs index 0706620c6e..60c9a81cfb 100644 --- a/module/move/assistant/tests/inc/mod.rs +++ b/module/blank/mindx12/tests/inc/basic_test.rs @@ -1,6 +1,7 @@ #[ allow( unused_imports ) ] use super::*; -mod basic_test; - -mod experiment; +#[ test ] +fn basic() +{ +} diff --git a/module/blank/mindx12/tests/inc/mod.rs b/module/blank/mindx12/tests/inc/mod.rs new file mode 100644 index 0000000000..7c40be710f --- /dev/null +++ b/module/blank/mindx12/tests/inc/mod.rs @@ -0,0 +1,4 @@ +use super::*; +use test_tools::exposed::*; + +mod basic_test; diff --git a/module/blank/mindx12/tests/smoke_test.rs b/module/blank/mindx12/tests/smoke_test.rs new file mode 100644 index 0000000000..c9b1b4daae --- /dev/null +++ b/module/blank/mindx12/tests/smoke_test.rs @@ -0,0 +1,13 @@ +//! Smoke testing of the package. + +#[ 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/blank/mindx12/tests/tests.rs similarity index 87% rename from module/move/assistant/tests/tests.rs rename to module/blank/mindx12/tests/tests.rs index c94c4d074f..5a33e742f0 100644 --- a/module/move/assistant/tests/tests.rs +++ b/module/blank/mindx12/tests/tests.rs @@ -2,7 +2,7 @@ include!( "../../../../module/step/meta/src/module/terminal.rs" ); #[ allow( unused_imports ) ] -use assistant as the_module; +use mindx12 as the_module; #[ allow( unused_imports ) ] use test_tools::exposed::*; diff --git a/module/blank/mingl/Cargo.toml b/module/blank/mingl/Cargo.toml new file mode 100644 index 0000000000..dbd89af97e --- /dev/null +++ b/module/blank/mingl/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "mingl" +version = "0.1.0" +edition = "2021" +authors = [ + "Kostiantyn Wandalen ", +] +license = "MIT" +readme = "Readme.md" +documentation = "https://docs.rs/mingl" +repository = "https://github.com/Wandalen/wTools/tree/master/module/core/mingl" +homepage = "https://github.com/Wandalen/wTools/tree/master/module/core/mingl" +description = """ +Draw language. +""" +categories = [ "algorithms", "development-tools" ] +keywords = [ "fundamental", "general-purpose" ] + +[lints] +workspace = true + +[package.metadata.docs.rs] +features = [ "full" ] +all-features = false + +[features] +default = [ "enabled" ] +full = [ "enabled" ] +enabled = [] + +[dependencies] + +[dev-dependencies] +test_tools = { workspace = true } diff --git a/module/blank/mingl/License b/module/blank/mingl/License new file mode 100644 index 0000000000..72c80c1308 --- /dev/null +++ b/module/blank/mingl/License @@ -0,0 +1,22 @@ +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 +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/blank/mingl/Readme.md similarity index 55% rename from module/move/assistant/Readme.md rename to module/blank/mingl/Readme.md index 0e9402c634..3a3390cd6c 100644 --- a/module/move/assistant/Readme.md +++ b/module/blank/mingl/Readme.md @@ -1,15 +1,15 @@ -# 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) +# Module :: draw_lang +[![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/Moduledraw_langPush.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/Moduledraw_langPush.yml) [![docs.rs](https://img.shields.io/docsrs/draw_lang?color=e3e8f0&logo=docs.rs)](https://docs.rs/draw_lang) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) -Assist AI in writing code. +Draw language. diff --git a/module/blank/mingl/src/lib.rs b/module/blank/mingl/src/lib.rs new file mode 100644 index 0000000000..8736456366 --- /dev/null +++ b/module/blank/mingl/src/lib.rs @@ -0,0 +1,10 @@ + +#![ 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 = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] + +/// Function description. +#[ cfg( feature = "enabled" ) ] +pub fn f1() +{ +} diff --git a/module/blank/mingl/tests/inc/basic_test.rs b/module/blank/mingl/tests/inc/basic_test.rs new file mode 100644 index 0000000000..60c9a81cfb --- /dev/null +++ b/module/blank/mingl/tests/inc/basic_test.rs @@ -0,0 +1,7 @@ +#[ allow( unused_imports ) ] +use super::*; + +#[ test ] +fn basic() +{ +} diff --git a/module/blank/mingl/tests/inc/mod.rs b/module/blank/mingl/tests/inc/mod.rs new file mode 100644 index 0000000000..7c40be710f --- /dev/null +++ b/module/blank/mingl/tests/inc/mod.rs @@ -0,0 +1,4 @@ +use super::*; +use test_tools::exposed::*; + +mod basic_test; diff --git a/module/blank/mingl/tests/smoke_test.rs b/module/blank/mingl/tests/smoke_test.rs new file mode 100644 index 0000000000..c9b1b4daae --- /dev/null +++ b/module/blank/mingl/tests/smoke_test.rs @@ -0,0 +1,13 @@ +//! Smoke testing of the package. + +#[ 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/blank/mingl/tests/tests.rs b/module/blank/mingl/tests/tests.rs new file mode 100644 index 0000000000..3e3cefe2bd --- /dev/null +++ b/module/blank/mingl/tests/tests.rs @@ -0,0 +1,10 @@ + +include!( "../../../../module/step/meta/src/module/terminal.rs" ); + +#[ allow( unused_imports ) ] +use mingl as the_module; +#[ allow( unused_imports ) ] +use test_tools::exposed::*; + +#[ cfg( feature = "enabled" ) ] +mod inc; diff --git a/module/blank/minmetal/Cargo.toml b/module/blank/minmetal/Cargo.toml new file mode 100644 index 0000000000..72527fb754 --- /dev/null +++ b/module/blank/minmetal/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "minmetal" +version = "0.1.0" +edition = "2021" +authors = [ + "Kostiantyn Wandalen ", +] +license = "MIT" +readme = "Readme.md" +documentation = "https://docs.rs/minmetal" +repository = "https://github.com/Wandalen/wTools/tree/master/module/core/minmetal" +homepage = "https://github.com/Wandalen/wTools/tree/master/module/core/minmetal" +description = """ +Draw language. +""" +categories = [ "algorithms", "development-tools" ] +keywords = [ "fundamental", "general-purpose" ] + +[lints] +workspace = true + +[package.metadata.docs.rs] +features = [ "full" ] +all-features = false + +[features] +default = [ "enabled" ] +full = [ "enabled" ] +enabled = [] + +[dependencies] + +[dev-dependencies] +test_tools = { workspace = true } diff --git a/module/blank/minmetal/License b/module/blank/minmetal/License new file mode 100644 index 0000000000..72c80c1308 --- /dev/null +++ b/module/blank/minmetal/License @@ -0,0 +1,22 @@ +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 +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/module/blank/minmetal/Readme.md b/module/blank/minmetal/Readme.md new file mode 100644 index 0000000000..f701fbedf7 --- /dev/null +++ b/module/blank/minmetal/Readme.md @@ -0,0 +1,33 @@ + + +# Module :: minmetal +[![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/ModuleminmetalPush.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/ModuleminmetalPush.yml) [![docs.rs](https://img.shields.io/docsrs/minmetal?color=e3e8f0&logo=docs.rs)](https://docs.rs/minmetal) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + +Draw language. + + diff --git a/module/blank/minmetal/src/lib.rs b/module/blank/minmetal/src/lib.rs new file mode 100644 index 0000000000..8736456366 --- /dev/null +++ b/module/blank/minmetal/src/lib.rs @@ -0,0 +1,10 @@ + +#![ 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 = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] + +/// Function description. +#[ cfg( feature = "enabled" ) ] +pub fn f1() +{ +} diff --git a/module/blank/minmetal/tests/inc/basic_test.rs b/module/blank/minmetal/tests/inc/basic_test.rs new file mode 100644 index 0000000000..60c9a81cfb --- /dev/null +++ b/module/blank/minmetal/tests/inc/basic_test.rs @@ -0,0 +1,7 @@ +#[ allow( unused_imports ) ] +use super::*; + +#[ test ] +fn basic() +{ +} diff --git a/module/blank/minmetal/tests/inc/mod.rs b/module/blank/minmetal/tests/inc/mod.rs new file mode 100644 index 0000000000..7c40be710f --- /dev/null +++ b/module/blank/minmetal/tests/inc/mod.rs @@ -0,0 +1,4 @@ +use super::*; +use test_tools::exposed::*; + +mod basic_test; diff --git a/module/blank/minmetal/tests/smoke_test.rs b/module/blank/minmetal/tests/smoke_test.rs new file mode 100644 index 0000000000..c9b1b4daae --- /dev/null +++ b/module/blank/minmetal/tests/smoke_test.rs @@ -0,0 +1,13 @@ +//! Smoke testing of the package. + +#[ 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/blank/minmetal/tests/tests.rs b/module/blank/minmetal/tests/tests.rs new file mode 100644 index 0000000000..f2f68bee4f --- /dev/null +++ b/module/blank/minmetal/tests/tests.rs @@ -0,0 +1,10 @@ + +include!( "../../../../module/step/meta/src/module/terminal.rs" ); + +#[ allow( unused_imports ) ] +use minmetal as the_module; +#[ allow( unused_imports ) ] +use test_tools::exposed::*; + +#[ cfg( feature = "enabled" ) ] +mod inc; diff --git a/module/blank/minopengl/Cargo.toml b/module/blank/minopengl/Cargo.toml new file mode 100644 index 0000000000..8be8629874 --- /dev/null +++ b/module/blank/minopengl/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "minopengl" +version = "0.1.0" +edition = "2021" +authors = [ + "Kostiantyn Wandalen ", +] +license = "MIT" +readme = "Readme.md" +documentation = "https://docs.rs/minopengl" +repository = "https://github.com/Wandalen/wTools/tree/master/module/core/minopengl" +homepage = "https://github.com/Wandalen/wTools/tree/master/module/core/minopengl" +description = """ +Draw language. +""" +categories = [ "algorithms", "development-tools" ] +keywords = [ "fundamental", "general-purpose" ] + +[lints] +workspace = true + +[package.metadata.docs.rs] +features = [ "full" ] +all-features = false + +[features] +default = [ "enabled" ] +full = [ "enabled" ] +enabled = [] + +[dependencies] + +[dev-dependencies] +test_tools = { workspace = true } diff --git a/module/blank/minopengl/License b/module/blank/minopengl/License new file mode 100644 index 0000000000..72c80c1308 --- /dev/null +++ b/module/blank/minopengl/License @@ -0,0 +1,22 @@ +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 +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/module/blank/minopengl/Readme.md b/module/blank/minopengl/Readme.md new file mode 100644 index 0000000000..7d19f733ea --- /dev/null +++ b/module/blank/minopengl/Readme.md @@ -0,0 +1,33 @@ + + +# Module :: minopengl +[![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/ModuleminopenglPush.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/ModuleminopenglPush.yml) [![docs.rs](https://img.shields.io/docsrs/minopengl?color=e3e8f0&logo=docs.rs)](https://docs.rs/minopengl) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + +Draw language. + + diff --git a/module/blank/minopengl/src/lib.rs b/module/blank/minopengl/src/lib.rs new file mode 100644 index 0000000000..8736456366 --- /dev/null +++ b/module/blank/minopengl/src/lib.rs @@ -0,0 +1,10 @@ + +#![ 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 = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] + +/// Function description. +#[ cfg( feature = "enabled" ) ] +pub fn f1() +{ +} diff --git a/module/blank/minopengl/tests/inc/basic_test.rs b/module/blank/minopengl/tests/inc/basic_test.rs new file mode 100644 index 0000000000..60c9a81cfb --- /dev/null +++ b/module/blank/minopengl/tests/inc/basic_test.rs @@ -0,0 +1,7 @@ +#[ allow( unused_imports ) ] +use super::*; + +#[ test ] +fn basic() +{ +} diff --git a/module/blank/minopengl/tests/inc/mod.rs b/module/blank/minopengl/tests/inc/mod.rs new file mode 100644 index 0000000000..7c40be710f --- /dev/null +++ b/module/blank/minopengl/tests/inc/mod.rs @@ -0,0 +1,4 @@ +use super::*; +use test_tools::exposed::*; + +mod basic_test; diff --git a/module/blank/minopengl/tests/smoke_test.rs b/module/blank/minopengl/tests/smoke_test.rs new file mode 100644 index 0000000000..c9b1b4daae --- /dev/null +++ b/module/blank/minopengl/tests/smoke_test.rs @@ -0,0 +1,13 @@ +//! Smoke testing of the package. + +#[ 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/blank/minopengl/tests/tests.rs b/module/blank/minopengl/tests/tests.rs new file mode 100644 index 0000000000..8a64879a19 --- /dev/null +++ b/module/blank/minopengl/tests/tests.rs @@ -0,0 +1,10 @@ + +include!( "../../../../module/step/meta/src/module/terminal.rs" ); + +#[ allow( unused_imports ) ] +use minopengl as the_module; +#[ allow( unused_imports ) ] +use test_tools::exposed::*; + +#[ cfg( feature = "enabled" ) ] +mod inc; diff --git a/module/blank/minvulkan/Cargo.toml b/module/blank/minvulkan/Cargo.toml new file mode 100644 index 0000000000..69ce9bda5d --- /dev/null +++ b/module/blank/minvulkan/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "minvulkan" +version = "0.1.0" +edition = "2021" +authors = [ + "Kostiantyn Wandalen ", +] +license = "MIT" +readme = "Readme.md" +documentation = "https://docs.rs/minvulkan" +repository = "https://github.com/Wandalen/wTools/tree/master/module/core/minvulkan" +homepage = "https://github.com/Wandalen/wTools/tree/master/module/core/minvulkan" +description = """ +Draw language. +""" +categories = [ "algorithms", "development-tools" ] +keywords = [ "fundamental", "general-purpose" ] + +[lints] +workspace = true + +[package.metadata.docs.rs] +features = [ "full" ] +all-features = false + +[features] +default = [ "enabled" ] +full = [ "enabled" ] +enabled = [] + +[dependencies] + +[dev-dependencies] +test_tools = { workspace = true } diff --git a/module/blank/minvulkan/License b/module/blank/minvulkan/License new file mode 100644 index 0000000000..72c80c1308 --- /dev/null +++ b/module/blank/minvulkan/License @@ -0,0 +1,22 @@ +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 +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/module/blank/minvulkan/Readme.md b/module/blank/minvulkan/Readme.md new file mode 100644 index 0000000000..69b19ad55c --- /dev/null +++ b/module/blank/minvulkan/Readme.md @@ -0,0 +1,33 @@ + + +# Module :: minvulkan +[![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/ModuleminvulkanPush.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/ModuleminvulkanPush.yml) [![docs.rs](https://img.shields.io/docsrs/minvulkan?color=e3e8f0&logo=docs.rs)](https://docs.rs/minvulkan) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + +Draw language. + + diff --git a/module/blank/minvulkan/src/lib.rs b/module/blank/minvulkan/src/lib.rs new file mode 100644 index 0000000000..8736456366 --- /dev/null +++ b/module/blank/minvulkan/src/lib.rs @@ -0,0 +1,10 @@ + +#![ 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 = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] + +/// Function description. +#[ cfg( feature = "enabled" ) ] +pub fn f1() +{ +} diff --git a/module/blank/minvulkan/tests/inc/basic_test.rs b/module/blank/minvulkan/tests/inc/basic_test.rs new file mode 100644 index 0000000000..60c9a81cfb --- /dev/null +++ b/module/blank/minvulkan/tests/inc/basic_test.rs @@ -0,0 +1,7 @@ +#[ allow( unused_imports ) ] +use super::*; + +#[ test ] +fn basic() +{ +} diff --git a/module/blank/minvulkan/tests/inc/mod.rs b/module/blank/minvulkan/tests/inc/mod.rs new file mode 100644 index 0000000000..7c40be710f --- /dev/null +++ b/module/blank/minvulkan/tests/inc/mod.rs @@ -0,0 +1,4 @@ +use super::*; +use test_tools::exposed::*; + +mod basic_test; diff --git a/module/blank/minvulkan/tests/smoke_test.rs b/module/blank/minvulkan/tests/smoke_test.rs new file mode 100644 index 0000000000..c9b1b4daae --- /dev/null +++ b/module/blank/minvulkan/tests/smoke_test.rs @@ -0,0 +1,13 @@ +//! Smoke testing of the package. + +#[ 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/blank/minvulkan/tests/tests.rs b/module/blank/minvulkan/tests/tests.rs new file mode 100644 index 0000000000..d2d5f19233 --- /dev/null +++ b/module/blank/minvulkan/tests/tests.rs @@ -0,0 +1,10 @@ + +include!( "../../../../module/step/meta/src/module/terminal.rs" ); + +#[ allow( unused_imports ) ] +use minvulkan as the_module; +#[ allow( unused_imports ) ] +use test_tools::exposed::*; + +#[ cfg( feature = "enabled" ) ] +mod inc; diff --git a/module/blank/minwebgl/Cargo.toml b/module/blank/minwebgl/Cargo.toml new file mode 100644 index 0000000000..06d52581fb --- /dev/null +++ b/module/blank/minwebgl/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "minwebgl" +version = "0.1.0" +edition = "2021" +authors = [ + "Kostiantyn Wandalen ", +] +license = "MIT" +readme = "Readme.md" +documentation = "https://docs.rs/minwebgl" +repository = "https://github.com/Wandalen/wTools/tree/master/module/core/minwebgl" +homepage = "https://github.com/Wandalen/wTools/tree/master/module/core/minwebgl" +description = """ +Draw language. +""" +categories = [ "algorithms", "development-tools" ] +keywords = [ "fundamental", "general-purpose" ] + +[lints] +workspace = true + +[package.metadata.docs.rs] +features = [ "full" ] +all-features = false + +[features] +default = [ "enabled" ] +full = [ "enabled" ] +enabled = [] + +[dependencies] + +[dev-dependencies] +test_tools = { workspace = true } diff --git a/module/blank/minwebgl/License b/module/blank/minwebgl/License new file mode 100644 index 0000000000..72c80c1308 --- /dev/null +++ b/module/blank/minwebgl/License @@ -0,0 +1,22 @@ +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 +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/module/blank/minwebgl/Readme.md b/module/blank/minwebgl/Readme.md new file mode 100644 index 0000000000..5b92138916 --- /dev/null +++ b/module/blank/minwebgl/Readme.md @@ -0,0 +1,33 @@ + + +# Module :: minwebgl +[![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/ModuleminwebglPush.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/ModuleminwebglPush.yml) [![docs.rs](https://img.shields.io/docsrs/minwebgl?color=e3e8f0&logo=docs.rs)](https://docs.rs/minwebgl) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + +Draw language. + + diff --git a/module/blank/minwebgl/src/lib.rs b/module/blank/minwebgl/src/lib.rs new file mode 100644 index 0000000000..8736456366 --- /dev/null +++ b/module/blank/minwebgl/src/lib.rs @@ -0,0 +1,10 @@ + +#![ 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 = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] + +/// Function description. +#[ cfg( feature = "enabled" ) ] +pub fn f1() +{ +} diff --git a/module/blank/minwebgl/tests/inc/basic_test.rs b/module/blank/minwebgl/tests/inc/basic_test.rs new file mode 100644 index 0000000000..60c9a81cfb --- /dev/null +++ b/module/blank/minwebgl/tests/inc/basic_test.rs @@ -0,0 +1,7 @@ +#[ allow( unused_imports ) ] +use super::*; + +#[ test ] +fn basic() +{ +} diff --git a/module/blank/minwebgl/tests/inc/mod.rs b/module/blank/minwebgl/tests/inc/mod.rs new file mode 100644 index 0000000000..7c40be710f --- /dev/null +++ b/module/blank/minwebgl/tests/inc/mod.rs @@ -0,0 +1,4 @@ +use super::*; +use test_tools::exposed::*; + +mod basic_test; diff --git a/module/blank/minwebgl/tests/smoke_test.rs b/module/blank/minwebgl/tests/smoke_test.rs new file mode 100644 index 0000000000..c9b1b4daae --- /dev/null +++ b/module/blank/minwebgl/tests/smoke_test.rs @@ -0,0 +1,13 @@ +//! Smoke testing of the package. + +#[ 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/blank/minwebgl/tests/tests.rs b/module/blank/minwebgl/tests/tests.rs new file mode 100644 index 0000000000..f830fcaa61 --- /dev/null +++ b/module/blank/minwebgl/tests/tests.rs @@ -0,0 +1,10 @@ + +include!( "../../../../module/step/meta/src/module/terminal.rs" ); + +#[ allow( unused_imports ) ] +use minwebgl as the_module; +#[ allow( unused_imports ) ] +use test_tools::exposed::*; + +#[ cfg( feature = "enabled" ) ] +mod inc; diff --git a/module/blank/minwebgpu/Cargo.toml b/module/blank/minwebgpu/Cargo.toml new file mode 100644 index 0000000000..c543c5be36 --- /dev/null +++ b/module/blank/minwebgpu/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "minwebgpu" +version = "0.1.0" +edition = "2021" +authors = [ + "Kostiantyn Wandalen ", +] +license = "MIT" +readme = "Readme.md" +documentation = "https://docs.rs/minwebgpu" +repository = "https://github.com/Wandalen/wTools/tree/master/module/core/minwebgpu" +homepage = "https://github.com/Wandalen/wTools/tree/master/module/core/minwebgpu" +description = """ +Draw language. +""" +categories = [ "algorithms", "development-tools" ] +keywords = [ "fundamental", "general-purpose" ] + +[lints] +workspace = true + +[package.metadata.docs.rs] +features = [ "full" ] +all-features = false + +[features] +default = [ "enabled" ] +full = [ "enabled" ] +enabled = [] + +[dependencies] + +[dev-dependencies] +test_tools = { workspace = true } diff --git a/module/blank/minwebgpu/License b/module/blank/minwebgpu/License new file mode 100644 index 0000000000..72c80c1308 --- /dev/null +++ b/module/blank/minwebgpu/License @@ -0,0 +1,22 @@ +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 +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/module/blank/minwebgpu/Readme.md b/module/blank/minwebgpu/Readme.md new file mode 100644 index 0000000000..259dee0ab2 --- /dev/null +++ b/module/blank/minwebgpu/Readme.md @@ -0,0 +1,33 @@ + + +# Module :: minwebgpu +[![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/ModuleminwebgpuPush.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/ModuleminwebgpuPush.yml) [![docs.rs](https://img.shields.io/docsrs/minwebgpu?color=e3e8f0&logo=docs.rs)](https://docs.rs/minwebgpu) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + +Draw language. + + diff --git a/module/blank/minwebgpu/src/lib.rs b/module/blank/minwebgpu/src/lib.rs new file mode 100644 index 0000000000..8736456366 --- /dev/null +++ b/module/blank/minwebgpu/src/lib.rs @@ -0,0 +1,10 @@ + +#![ 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 = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] + +/// Function description. +#[ cfg( feature = "enabled" ) ] +pub fn f1() +{ +} diff --git a/module/blank/minwebgpu/tests/inc/basic_test.rs b/module/blank/minwebgpu/tests/inc/basic_test.rs new file mode 100644 index 0000000000..60c9a81cfb --- /dev/null +++ b/module/blank/minwebgpu/tests/inc/basic_test.rs @@ -0,0 +1,7 @@ +#[ allow( unused_imports ) ] +use super::*; + +#[ test ] +fn basic() +{ +} diff --git a/module/blank/minwebgpu/tests/inc/mod.rs b/module/blank/minwebgpu/tests/inc/mod.rs new file mode 100644 index 0000000000..7c40be710f --- /dev/null +++ b/module/blank/minwebgpu/tests/inc/mod.rs @@ -0,0 +1,4 @@ +use super::*; +use test_tools::exposed::*; + +mod basic_test; diff --git a/module/blank/minwebgpu/tests/smoke_test.rs b/module/blank/minwebgpu/tests/smoke_test.rs new file mode 100644 index 0000000000..c9b1b4daae --- /dev/null +++ b/module/blank/minwebgpu/tests/smoke_test.rs @@ -0,0 +1,13 @@ +//! Smoke testing of the package. + +#[ 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/blank/minwebgpu/tests/tests.rs b/module/blank/minwebgpu/tests/tests.rs new file mode 100644 index 0000000000..849473f639 --- /dev/null +++ b/module/blank/minwebgpu/tests/tests.rs @@ -0,0 +1,10 @@ + +include!( "../../../../module/step/meta/src/module/terminal.rs" ); + +#[ allow( unused_imports ) ] +use minwebgpu as the_module; +#[ allow( unused_imports ) ] +use test_tools::exposed::*; + +#[ cfg( feature = "enabled" ) ] +mod inc; diff --git a/module/blank/minwgpu/Cargo.toml b/module/blank/minwgpu/Cargo.toml new file mode 100644 index 0000000000..25841190ba --- /dev/null +++ b/module/blank/minwgpu/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "minwgpu" +version = "0.1.0" +edition = "2021" +authors = [ + "Kostiantyn Wandalen ", +] +license = "MIT" +readme = "Readme.md" +documentation = "https://docs.rs/minwgpu" +repository = "https://github.com/Wandalen/wTools/tree/master/module/core/minwgpu" +homepage = "https://github.com/Wandalen/wTools/tree/master/module/core/minwgpu" +description = """ +Draw language. +""" +categories = [ "algorithms", "development-tools" ] +keywords = [ "fundamental", "general-purpose" ] + +[lints] +workspace = true + +[package.metadata.docs.rs] +features = [ "full" ] +all-features = false + +[features] +default = [ "enabled" ] +full = [ "enabled" ] +enabled = [] + +[dependencies] + +[dev-dependencies] +test_tools = { workspace = true } diff --git a/module/blank/minwgpu/License b/module/blank/minwgpu/License new file mode 100644 index 0000000000..72c80c1308 --- /dev/null +++ b/module/blank/minwgpu/License @@ -0,0 +1,22 @@ +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 +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/module/blank/minwgpu/Readme.md b/module/blank/minwgpu/Readme.md new file mode 100644 index 0000000000..1c8ca98d8a --- /dev/null +++ b/module/blank/minwgpu/Readme.md @@ -0,0 +1,33 @@ + + +# Module :: minwgpu +[![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/ModuleminwgpuPush.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/ModuleminwgpuPush.yml) [![docs.rs](https://img.shields.io/docsrs/minwgpu?color=e3e8f0&logo=docs.rs)](https://docs.rs/minwgpu) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + +Draw language. + + diff --git a/module/blank/minwgpu/src/lib.rs b/module/blank/minwgpu/src/lib.rs new file mode 100644 index 0000000000..8736456366 --- /dev/null +++ b/module/blank/minwgpu/src/lib.rs @@ -0,0 +1,10 @@ + +#![ 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 = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] + +/// Function description. +#[ cfg( feature = "enabled" ) ] +pub fn f1() +{ +} diff --git a/module/blank/minwgpu/tests/inc/basic_test.rs b/module/blank/minwgpu/tests/inc/basic_test.rs new file mode 100644 index 0000000000..60c9a81cfb --- /dev/null +++ b/module/blank/minwgpu/tests/inc/basic_test.rs @@ -0,0 +1,7 @@ +#[ allow( unused_imports ) ] +use super::*; + +#[ test ] +fn basic() +{ +} diff --git a/module/blank/minwgpu/tests/inc/mod.rs b/module/blank/minwgpu/tests/inc/mod.rs new file mode 100644 index 0000000000..7c40be710f --- /dev/null +++ b/module/blank/minwgpu/tests/inc/mod.rs @@ -0,0 +1,4 @@ +use super::*; +use test_tools::exposed::*; + +mod basic_test; diff --git a/module/blank/minwgpu/tests/smoke_test.rs b/module/blank/minwgpu/tests/smoke_test.rs new file mode 100644 index 0000000000..c9b1b4daae --- /dev/null +++ b/module/blank/minwgpu/tests/smoke_test.rs @@ -0,0 +1,13 @@ +//! Smoke testing of the package. + +#[ 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/blank/minwgpu/tests/tests.rs b/module/blank/minwgpu/tests/tests.rs new file mode 100644 index 0000000000..9bcb27960e --- /dev/null +++ b/module/blank/minwgpu/tests/tests.rs @@ -0,0 +1,10 @@ + +include!( "../../../../module/step/meta/src/module/terminal.rs" ); + +#[ allow( unused_imports ) ] +use minwgpu as the_module; +#[ allow( unused_imports ) ] +use test_tools::exposed::*; + +#[ cfg( feature = "enabled" ) ] +mod inc; diff --git a/module/blank/paths_tools/License b/module/blank/paths_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/blank/paths_tools/License +++ b/module/blank/paths_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/tests/inc/mod.rs b/module/blank/paths_tools/tests/inc/mod.rs index dde9de6f94..7c40be710f 100644 --- a/module/blank/paths_tools/tests/inc/mod.rs +++ b/module/blank/paths_tools/tests/inc/mod.rs @@ -1,4 +1,4 @@ -#[ allow( unused_imports ) ] use super::*; +use test_tools::exposed::*; mod basic_test; diff --git a/module/blank/proper_path_tools/Cargo.toml b/module/blank/proper_path_tools/Cargo.toml new file mode 100644 index 0000000000..4fe862c57e --- /dev/null +++ b/module/blank/proper_path_tools/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "proper_path_tools" +version = "0.1.0" +edition = "2021" +authors = [ + "Kostiantyn Wandalen ", +] +license = "MIT" +readme = "Readme.md" +documentation = "https://docs.rs/proper_path_tools" +repository = "https://github.com/Wandalen/wTools/tree/master/module/core/proper_path_tools" +homepage = "https://github.com/Wandalen/wTools/tree/master/module/core/proper_path_tools" +description = """ +Tools for second brain. +""" +categories = [ "algorithms", "development-tools" ] +keywords = [ "fundamental", "general-purpose" ] + +[lints] +workspace = true + +[package.metadata.docs.rs] +features = [ "full" ] +all-features = false + +[features] +default = [ "enabled" ] +full = [ "enabled" ] +enabled = [] + +[dependencies] + +[dev-dependencies] +test_tools = { workspace = true } diff --git a/module/blank/proper_path_tools/License b/module/blank/proper_path_tools/License new file mode 100644 index 0000000000..72c80c1308 --- /dev/null +++ b/module/blank/proper_path_tools/License @@ -0,0 +1,22 @@ +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 +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/module/blank/proper_path_tools/Readme.md b/module/blank/proper_path_tools/Readme.md new file mode 100644 index 0000000000..7fd9f99168 --- /dev/null +++ b/module/blank/proper_path_tools/Readme.md @@ -0,0 +1,33 @@ + + +# Module :: proper_path_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/Moduleproper_path_toolsPush.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/Moduleproper_path_toolsPush.yml) [![docs.rs](https://img.shields.io/docsrs/proper_path_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/proper_path_tools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + +Tools for second brain. + + diff --git a/module/core/proper_path_tools/src/lib.rs b/module/blank/proper_path_tools/src/lib.rs similarity index 53% rename from module/core/proper_path_tools/src/lib.rs rename to module/blank/proper_path_tools/src/lib.rs index 1ac23b4abd..b96a03ed21 100644 --- a/module/core/proper_path_tools/src/lib.rs +++ b/module/blank/proper_path_tools/src/lib.rs @@ -1,28 +1,11 @@ -#![ cfg_attr( feature = "no_std", no_std ) ] + #![ doc( html_logo_url = "https://raw.githubusercontent.com/Wandalen/wTools/master/asset/img/logo_v3_trans_square.png" ) ] #![ doc( html_favicon_url = "https://raw.githubusercontent.com/Wandalen/wTools/alpha/asset/img/logo_v3_trans_square_icon_small_v2.ico" ) ] #![ doc( html_root_url = "https://docs.rs/proper_path_tools/latest/proper_path_tools/" ) ] #![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] +/// Function description. #[ cfg( feature = "enabled" ) ] -use mod_interface::mod_interface; - -#[ cfg( feature="no_std" ) ] -#[ macro_use ] -extern crate alloc; - -#[ cfg( feature = "enabled" ) ] -mod_interface! +pub fn f1() { - - /// Basic functionality. - layer path; - - /// Transitive TryFrom and TryInto. - layer transitive; - - #[ cfg( feature = "path_utf8" ) ] - own use ::camino::{ Utf8Path, Utf8PathBuf }; - own use ::std::path::{ PathBuf, Path }; - } diff --git a/module/blank/proper_path_tools/tests/inc/basic_test.rs b/module/blank/proper_path_tools/tests/inc/basic_test.rs new file mode 100644 index 0000000000..60c9a81cfb --- /dev/null +++ b/module/blank/proper_path_tools/tests/inc/basic_test.rs @@ -0,0 +1,7 @@ +#[ allow( unused_imports ) ] +use super::*; + +#[ test ] +fn basic() +{ +} diff --git a/module/blank/proper_path_tools/tests/inc/mod.rs b/module/blank/proper_path_tools/tests/inc/mod.rs new file mode 100644 index 0000000000..7c40be710f --- /dev/null +++ b/module/blank/proper_path_tools/tests/inc/mod.rs @@ -0,0 +1,4 @@ +use super::*; +use test_tools::exposed::*; + +mod basic_test; diff --git a/module/core/proper_path_tools/tests/smoke_test.rs b/module/blank/proper_path_tools/tests/smoke_test.rs similarity index 98% rename from module/core/proper_path_tools/tests/smoke_test.rs rename to module/blank/proper_path_tools/tests/smoke_test.rs index 828e9b016b..663dd6fb9f 100644 --- a/module/core/proper_path_tools/tests/smoke_test.rs +++ b/module/blank/proper_path_tools/tests/smoke_test.rs @@ -1,12 +1,10 @@ - #[ test ] fn local_smoke_test() { ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/core/proper_path_tools/tests/tests.rs b/module/blank/proper_path_tools/tests/tests.rs similarity index 100% rename from module/core/proper_path_tools/tests/tests.rs rename to module/blank/proper_path_tools/tests/tests.rs diff --git a/module/blank/rustql/License b/module/blank/rustql/License index 6d5ef8559f..72c80c1308 100644 --- a/module/blank/rustql/License +++ b/module/blank/rustql/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/tests/inc/mod.rs b/module/blank/rustql/tests/inc/mod.rs index dde9de6f94..7c40be710f 100644 --- a/module/blank/rustql/tests/inc/mod.rs +++ b/module/blank/rustql/tests/inc/mod.rs @@ -1,4 +1,4 @@ -#[ allow( unused_imports ) ] use super::*; +use test_tools::exposed::*; mod basic_test; diff --git a/module/blank/rustql/tests/smoke_test.rs b/module/blank/rustql/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/blank/rustql/tests/smoke_test.rs +++ b/module/blank/rustql/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/blank/second_brain/License b/module/blank/second_brain/License index 6d5ef8559f..72c80c1308 100644 --- a/module/blank/second_brain/License +++ b/module/blank/second_brain/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/tests/inc/mod.rs b/module/blank/second_brain/tests/inc/mod.rs index dde9de6f94..7c40be710f 100644 --- a/module/blank/second_brain/tests/inc/mod.rs +++ b/module/blank/second_brain/tests/inc/mod.rs @@ -1,4 +1,4 @@ -#[ allow( unused_imports ) ] use super::*; +use test_tools::exposed::*; mod basic_test; diff --git a/module/blank/w4d/License b/module/blank/w4d/License index 6d5ef8559f..72c80c1308 100644 --- a/module/blank/w4d/License +++ b/module/blank/w4d/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/tests/smoke_test.rs b/module/blank/w4d/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/blank/w4d/tests/smoke_test.rs +++ b/module/blank/w4d/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/blank/wlang/License b/module/blank/wlang/License index 6d5ef8559f..72c80c1308 100644 --- a/module/blank/wlang/License +++ b/module/blank/wlang/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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 new file mode 100644 index 0000000000..5614306486 --- /dev/null +++ b/module/core/asbytes/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = "asbytes" +version = "0.2.0" +edition = "2021" +authors = [ + "Kostiantyn Wandalen ", +] +license = "MIT" +readme = "Readme.md" +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 = """ +Traits for viewing data as byte slices or consuming data into byte vectors. Relies on bytemuck for POD safety. +""" +categories = [ "algorithms", "development-tools", "data-structures" ] # Added data-structures +keywords = [ "fundamental", "general-purpose", "bytes", "pod", "bytemuck" ] # Added keywords + +[lints] +workspace = true + +[package.metadata.docs.rs] +features = [ "full" ] +all-features = false + +[features] +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" ] # 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 new file mode 100644 index 0000000000..72c80c1308 --- /dev/null +++ b/module/core/asbytes/License @@ -0,0 +1,22 @@ +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 +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/core/asbytes/Readme.md b/module/core/asbytes/Readme.md new file mode 100644 index 0000000000..60d557bb69 --- /dev/null +++ b/module/core/asbytes/Readme.md @@ -0,0 +1,215 @@ + + +# 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 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 `.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 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 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 + +### `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 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 ); +} +``` + +### `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 + +```sh +git clone https://github.com/Wandalen/wTools +cd wTools +# 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 new file mode 100644 index 0000000000..5833fef16e --- /dev/null +++ b/module/core/asbytes/src/lib.rs @@ -0,0 +1,143 @@ +#![ 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/" ) ] +#![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] + +/// Namespace with dependencies. +#[ 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; +} + +/// Define a private namespace for all its items. +#[ cfg( feature = "enabled" ) ] +mod private +{ +} + +#[ cfg( feature = "as_bytes" ) ] +mod as_bytes; +#[ cfg( feature = "into_bytes" ) ] +mod into_bytes; + +#[ cfg( feature = "enabled" ) ] +#[ doc( inline ) ] +#[ allow( unused_imports ) ] +pub use own::*; + +/// Own namespace of the module. +#[ cfg( feature = "enabled" ) ] +#[ allow( unused_imports ) ] +pub mod own +{ + use super::*; + + #[ 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:: + { + checked, + offset_of, + bytes_of, + bytes_of_mut, + cast, + cast_mut, + cast_ref, + cast_slice, + cast_slice_mut, + fill_zeroes, + from_bytes, + from_bytes_mut, + pod_align_to, + pod_align_to_mut, + pod_read_unaligned, + try_cast, + try_cast_mut, + try_cast_ref, + try_cast_slice, + try_cast_slice_mut, + try_from_bytes, + try_from_bytes_mut, + try_pod_read_unaligned, + write_zeroes, + CheckedBitPattern, + PodCastError, + AnyBitPattern, + Contiguous, + NoUninit, + Pod, + PodInOption, + TransparentWrapper, + 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" ) ] +#[ doc( inline ) ] +#[ allow( unused_imports ) ] +pub use own::*; + +/// Orphan namespace of the module. +#[ cfg( feature = "enabled" ) ] +#[ allow( unused_imports ) ] +pub mod orphan +{ + use super::*; + + #[ doc( inline ) ] + pub use exposed::*; + +} + +/// Exposed namespace of the module. +#[ cfg( feature = "enabled" ) ] +#[ allow( unused_imports ) ] +pub mod exposed +{ + use super::*; + + #[ doc( inline ) ] + #[ cfg( feature = "as_bytes" ) ] + pub use as_bytes::exposed::*; + #[ doc( inline ) ] + #[ cfg( feature = "into_bytes" ) ] + pub use into_bytes::exposed::*; + + #[ doc( inline ) ] + pub use prelude::*; + +} + +/// Prelude to use essentials: `use my_module::prelude::*`. +#[ cfg( feature = "enabled" ) ] +#[ allow( unused_imports ) ] +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/as_bytes_test.rs b/module/core/asbytes/tests/inc/as_bytes_test.rs new file mode 100644 index 0000000000..fcd29535c9 --- /dev/null +++ b/module/core/asbytes/tests/inc/as_bytes_test.rs @@ -0,0 +1,114 @@ +#![ cfg( all( feature = "enabled", feature = "as_bytes" ) ) ] + +// Define a simple POD struct for testing +#[ repr( C ) ] +#[ derive( Clone, Copy, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable ) ] +struct Point +{ + x : i32, + y : i32, +} + +#[ test ] +fn test_tuple_scalar_as_bytes() +{ + { + use asbytes::AsBytes; + use std::mem; + + let scalar_tuple = ( 123u32, ); + let bytes = scalar_tuple.as_bytes(); + let expected_length = mem::size_of::< u32 >(); + + assert_eq!( bytes.len(), expected_length ); + assert_eq!( scalar_tuple.byte_size(), expected_length ); + assert_eq!( scalar_tuple.len(), 1 ); // Length of tuple is 1 element + + // Verify content (assuming little-endian) + assert_eq!( bytes, &123u32.to_le_bytes() ); + } +} + +#[ test ] +fn test_tuple_struct_as_bytes() +{ + { + use asbytes::AsBytes; + use std::mem; + + let point = Point { x : 10, y : -20 }; + let struct_tuple = ( point, ); + let bytes = struct_tuple.as_bytes(); + let expected_length = mem::size_of::< Point >(); + + assert_eq!( bytes.len(), expected_length ); + assert_eq!( struct_tuple.byte_size(), expected_length ); + assert_eq!( struct_tuple.len(), 1 ); // Length of tuple is 1 element + + // Verify content using bytemuck::bytes_of for comparison + assert_eq!( bytes, bytemuck::bytes_of( &point ) ); + } +} + +#[ test ] +fn test_vec_as_bytes() +{ + { + use asbytes::AsBytes; + use std::mem; + let v = vec![ 1u32, 2, 3, 4 ]; + let bytes = v.as_bytes(); + let expected_length = v.len() * mem::size_of::< u32 >(); + assert_eq!( bytes.len(), expected_length ); + assert_eq!( v.byte_size(), expected_length ); + assert_eq!( v.len(), 4 ); // Length of Vec is number of elements + } +} + +#[ test ] +fn test_slice_as_bytes() +{ + { + use asbytes::exposed::AsBytes; // Using exposed path + use std::mem; + let slice : &[ u32 ] = & [ 10, 20, 30 ]; + let bytes = slice.as_bytes(); + let expected_length = slice.len() * mem::size_of::< u32 >(); + assert_eq!( bytes.len(), expected_length ); + assert_eq!( slice.byte_size(), expected_length ); + assert_eq!( slice.len(), 3 ); // Length of slice is number of elements + } +} + +#[ test ] +fn test_array_as_bytes() +{ + { + use asbytes::own::AsBytes; // Using own path + use std::mem; + let arr : [ u32 ; 3 ] = [ 100, 200, 300 ]; + let bytes = arr.as_bytes(); + let expected_length = arr.len() * mem::size_of::< u32 >(); + assert_eq!( bytes.len(), expected_length ); + assert_eq!( arr.byte_size(), expected_length ); + assert_eq!( arr.len(), 3 ); // Length of array is compile-time size N + } +} + +#[ test ] +fn test_vec_struct_as_bytes() +{ + { + use asbytes::AsBytes; + use std::mem; + let points = vec![ Point { x : 1, y : 2 }, Point { x : 3, y : 4 } ]; + let bytes = points.as_bytes(); + let expected_length = points.len() * mem::size_of::< Point >(); + assert_eq!( bytes.len(), expected_length ); + assert_eq!( points.byte_size(), expected_length ); + assert_eq!( points.len(), 2 ); + + // Verify content using bytemuck::cast_slice for comparison + assert_eq!( bytes, bytemuck::cast_slice( &points[ .. ] ) ); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..1be093f8b6 --- /dev/null +++ b/module/core/asbytes/tests/inc/mod.rs @@ -0,0 +1,4 @@ +use super::*; + +mod as_bytes_test; +mod into_bytes_test; diff --git a/module/core/asbytes/tests/tests.rs b/module/core/asbytes/tests/tests.rs new file mode 100644 index 0000000000..86cb09e4aa --- /dev/null +++ b/module/core/asbytes/tests/tests.rs @@ -0,0 +1,9 @@ +//! All tests. +#![ allow( unused_imports ) ] + +include!( "../../../../module/step/meta/src/module/terminal.rs" ); + +use asbytes as the_module; + +#[ cfg( feature = "enabled" ) ] +mod inc; diff --git a/module/core/async_from/Cargo.toml b/module/core/async_from/Cargo.toml new file mode 100644 index 0000000000..b6be30c5c7 --- /dev/null +++ b/module/core/async_from/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "async_from" +version = "0.2.0" +edition = "2021" +authors = [ + "Kostiantyn Wandalen ", +] +license = "MIT" +readme = "Readme.md" +documentation = "https://docs.rs/async_from" +repository = "https://github.com/Wandalen/wTools/tree/master/module/core/async_from" +homepage = "https://github.com/Wandalen/wTools/tree/master/module/core/async_from" +description = """ +Async version of From, Into, TryFrom, TryInto. +""" +categories = [ "algorithms", "development-tools" ] +keywords = [ "fundamental", "general-purpose" ] + +[lints] +workspace = true + +[package.metadata.docs.rs] +features = [ "full" ] +all-features = false + +[features] +default = [ "enabled", "async_from", "async_try_from" ] +full = [ "default" ] +enabled = [] +async_from = [] +async_try_from = [] + +[dependencies] +async-trait = { workspace = true } + +[dev-dependencies] +# test_tools = { workspace = true } +tokio = { workspace = true, features = [ "rt-multi-thread", "time", "macros" ] } diff --git a/module/core/async_from/License b/module/core/async_from/License new file mode 100644 index 0000000000..72c80c1308 --- /dev/null +++ b/module/core/async_from/License @@ -0,0 +1,22 @@ +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 +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/core/async_from/Readme.md b/module/core/async_from/Readme.md new file mode 100644 index 0000000000..374fd4595c --- /dev/null +++ b/module/core/async_from/Readme.md @@ -0,0 +1,91 @@ + + +# Module :: async_from +[![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/Moduleasync_fromPush.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/Moduleasync_fromPush.yml) [![docs.rs](https://img.shields.io/docsrs/async_from?color=e3e8f0&logo=docs.rs)](https://docs.rs/async_from) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + +Async version of From, Into, TryFrom, TryInto. + +The `async_from` crate provides asynchronous versions of the well-known `From`, `Into`, `TryFrom`, and `TryInto` traits. These traits are essential for handling conversions in Rust, and their asynchronous counterparts, allowing for conversions that involve asynchronous operations. + +## Why Asynchronous Conversion Traits? + +In Rust, the `From`, `Into`, `TryFrom`, and `TryInto` traits provide a standardized way to handle type conversions. The `async_from` module extends this functionality to asynchronous contexts with `AsyncFrom`, `AsyncInto`, `AsyncTryFrom`, and `AsyncTryInto` traits, offering several key benefits: + +- **Simplicity**: Allow straightforward conversions without boilerplate, even in asynchronous contexts. +- **Consistency**: Provide a uniform interface for conversions across different types, aiding in writing predictable and maintainable code. +- **Error Handling**: Enable safe and explicit handling of conversion failures, essential for robust error management in commercial applications. +- **Asynchronous Contexts**: Facilitate conversions involving asynchronous operations, such as network requests or database queries, which are common in modern applications. + +The `async_from` provides developers with the tools needed to handle complex conversions in an async context efficiently, which is particularly important for commercial applications requiring reliable and efficient handling of asynchronous operations. + +### `AsyncFrom` and `AsyncInto` + +Trait for asynchronous conversions from a type T. + +These traits are designed for infallible asynchronous conversions. They allow you to convert types asynchronously, returning the result directly. + +```rust +use async_from::{ async_trait, AsyncFrom, AsyncInto }; + +struct MyNumber( u32 ); + +#[ async_trait ] +impl AsyncFrom< String > for MyNumber +{ + async fn async_from( value : String ) -> Self + { + let num = value.parse::< u32 >().unwrap_or( 0 ); + MyNumber( num ) + } +} + +#[ tokio::main ] +async fn main() +{ + let num = MyNumber::async_from( "42".to_string() ).await; + println!( "Converted: {}", num.0 ); + let num : MyNumber = "42".to_string().async_into().await; + println!( "Converted: {}", num.0 ); +} +``` + +### `AsyncTryFrom` and `AsyncTryInto` + +Trait for asynchronous fallible conversions from a type T. + +These traits are for fallible asynchronous conversions, where the conversion might fail. They return a `Result` wrapped in a `Future`, allowing you to handle errors gracefully. + +```rust +use async_from::{ async_trait, AsyncTryFrom, AsyncTryInto }; +use std::num::ParseIntError; + +struct MyNumber( u32 ); + +#[ async_trait ] +impl AsyncTryFrom< String > for MyNumber +{ + type Error = ParseIntError; + + async fn async_try_from( value : String ) -> Result< Self, Self::Error > + { + let num = value.parse::< u32 >()?; + Ok( MyNumber( num ) ) + } +} + +#[ tokio::main ] +async fn main() +{ + match MyNumber::async_try_from( "42".to_string() ).await + { + Ok( my_num ) => println!( "Converted successfully: {}", my_num.0 ), + Err( e ) => println!( "Conversion failed: {:?}", e ), + } + 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 ), + } +} +``` diff --git a/module/core/async_from/src/lib.rs b/module/core/async_from/src/lib.rs new file mode 100644 index 0000000000..1424b6e497 --- /dev/null +++ b/module/core/async_from/src/lib.rs @@ -0,0 +1,325 @@ + +#![ 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/async_from/latest/async_from/" ) ] +#![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] + +/// Namespace with dependencies. +#[ cfg( feature = "enabled" ) ] +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 +{ + + pub use async_trait::async_trait; + use std::fmt::Debug; + + /// Trait for asynchronous conversions from a type `T`. + /// + /// This trait allows for conversions that occur asynchronously, returning a `Future`. + /// + /// # Example + /// + /// ```rust + /// use async_from::{ async_trait, AsyncFrom }; + /// + /// struct MyNumber( u32 ); + /// + /// #[ async_trait ] + /// impl AsyncFrom< String > for MyNumber + /// { + /// async fn async_from( value : String ) -> Self + /// { + /// let num = value.parse::< u32 >().unwrap_or( 0 ); + /// MyNumber( num ) + /// } + /// } + /// + /// #[ tokio::main ] + /// async fn main() + /// { + /// let num = MyNumber::async_from( "42".to_string() ).await; + /// println!( "Converted: {}", num.0 ); + /// } + /// ``` + #[ cfg( feature = "async_from" ) ] + #[ async_trait ] + pub trait AsyncFrom< T > : Sized + { + /// Asynchronously converts a value of type `T` into `Self`. + /// + /// # Arguments + /// + /// * `value` - The value to be converted. + /// + /// # Returns + /// + /// * `Self` - The converted value. + async fn async_from( value : T ) -> Self; + } + + /// Trait for asynchronous conversions into a type `T`. + /// + /// This trait provides a method to convert `Self` into `T` asynchronously. + /// + /// # Example + /// + /// ```rust + /// use async_from::{ async_trait, AsyncFrom, AsyncInto }; + /// + /// struct MyNumber( u32 ); + /// + /// #[ async_trait ] + /// impl AsyncFrom< String > for MyNumber + /// { + /// async fn async_from( value : String ) -> Self + /// { + /// let num = value.parse::< u32 >().unwrap_or( 0 ); + /// MyNumber( num ) + /// } + /// } + /// + /// #[ tokio::main ] + /// async fn main() + /// { + /// let num : MyNumber = "42".to_string().async_into().await; + /// println!( "Converted: {}", num.0 ); + /// } + /// ``` + #[ async_trait ] + #[ cfg( feature = "async_from" ) ] + pub trait AsyncInto< T > : Sized + { + /// Asynchronously converts `Self` into a value of type `T`. + /// + /// # Returns + /// + /// * `T` - The converted value. + async fn async_into( self ) -> T; + } + + /// Blanket implementation of `AsyncInto` for any type that implements `AsyncFrom`. + /// + /// This implementation allows any type `T` that implements `AsyncFrom` to also implement `AsyncInto`. + #[ async_trait ] + #[ cfg( feature = "async_from" ) ] + impl< T, U > AsyncInto< U > for T + where + U : AsyncFrom< T > + Send, + T : Send, + { + /// Asynchronously converts `Self` into a value of type `U` using `AsyncFrom`. + /// + /// # Returns + /// + /// * `U` - The converted value. + async fn async_into( self ) -> U + { + U::async_from( self ).await + } + } + + /// Trait for asynchronous fallible conversions from a type `T`. + /// + /// This trait allows for conversions that may fail, returning a `Result` wrapped in a `Future`. + /// + /// # Example + /// + /// ```rust + /// use async_from::{ async_trait, AsyncTryFrom }; + /// use std::num::ParseIntError; + /// + /// struct MyNumber( u32 ); + /// + /// #[ async_trait ] + /// impl AsyncTryFrom< String > for MyNumber + /// { + /// type Error = ParseIntError; + /// + /// async fn async_try_from( value : String ) -> Result< Self, Self::Error > + /// { + /// let num = value.parse::< u32 >()?; + /// Ok( MyNumber( num ) ) + /// } + /// } + /// + /// #[ tokio::main ] + /// async fn main() + /// { + /// match MyNumber::async_try_from( "42".to_string() ).await + /// { + /// Ok( my_num ) => println!( "Converted successfully: {}", my_num.0 ), + /// Err( e ) => println!( "Conversion failed: {:?}", e ), + /// } + /// } + /// ``` + #[ async_trait ] + #[ cfg( feature = "async_try_from" ) ] + pub trait AsyncTryFrom< T > : Sized + { + /// The error type returned if the conversion fails. + type Error : Debug; + + /// Asynchronously attempts to convert a value of type `T` into `Self`. + /// + /// # Arguments + /// + /// * `value` - The value to be converted. + /// + /// # Returns + /// + /// * `Result` - On success, returns the converted value. On failure, returns an error. + async fn async_try_from( value : T ) -> Result< Self, Self::Error >; + } + + /// Trait for asynchronous fallible conversions into a type `T`. + /// + /// This trait provides a method to convert `Self` into `T`, potentially returning an error. + /// + /// # Example + /// + /// ```rust + /// use async_from::{ async_trait, AsyncTryFrom, AsyncTryInto }; + /// use std::num::ParseIntError; + /// + /// struct MyNumber( u32 ); + /// + /// #[ async_trait ] + /// impl AsyncTryFrom< String > for MyNumber + /// { + /// type Error = ParseIntError; + /// + /// async fn async_try_from( value : String ) -> Result< Self, Self::Error > + /// { + /// let num = value.parse::< u32 >()?; + /// Ok( MyNumber( num ) ) + /// } + /// } + /// + /// #[ tokio::main ] + /// async fn main() + /// { + /// 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 ), + /// } + /// } + /// ``` + #[ async_trait ] + #[ cfg( feature = "async_try_from" ) ] + pub trait AsyncTryInto< T > : Sized + { + /// The error type returned if the conversion fails. + type Error : Debug; + + /// Asynchronously attempts to convert `Self` into a value of type `T`. + /// + /// # Returns + /// + /// * `Result` - On success, returns the converted value. On failure, returns an error. + async fn async_try_into( self ) -> Result< T, Self::Error >; + } + + /// Blanket implementation of `AsyncTryInto` for any type that implements `AsyncTryFrom`. + /// + /// This implementation allows any type `T` that implements `AsyncTryFrom` to also implement `AsyncTryInto`. + #[ async_trait ] + #[ cfg( feature = "async_try_from" ) ] + impl< T, U > AsyncTryInto< U > for T + where + U : AsyncTryFrom< T > + Send, + T : Send, + { + type Error = U::Error; + + /// Asynchronously converts `Self` into a value of type `U` using `AsyncTryFrom`. + /// + /// # Returns + /// + /// * `Result` - On success, returns the converted value. On failure, returns an error. + async fn async_try_into( self ) -> Result< U, Self::Error > + { + U::async_try_from( self ).await + } + } + +} + +#[ cfg( feature = "enabled" ) ] +#[ doc( inline ) ] +#[ allow( unused_imports ) ] +pub use own::*; + +/// Own namespace of the module. +#[ cfg( feature = "enabled" ) ] +#[ allow( unused_imports ) ] +pub mod own +{ + use super::*; + #[ doc( inline ) ] + pub use orphan::*; +} + +/// Orphan namespace of the module. +#[ cfg( feature = "enabled" ) ] +#[ allow( unused_imports ) ] +pub mod orphan +{ + use super::*; + #[ doc( inline ) ] + pub use exposed::*; + +} + +/// Exposed namespace of the module. +#[ cfg( feature = "enabled" ) ] +#[ allow( unused_imports ) ] +pub mod exposed +{ + use super::*; + + #[ doc( inline ) ] + pub use prelude::*; + + #[ doc( inline ) ] + pub use ::async_trait::async_trait; + + #[ cfg( feature = "async_from" ) ] + pub use private:: + { + AsyncFrom, + AsyncInto, + }; + + #[ cfg( feature = "async_try_from" ) ] + pub use private:: + { + AsyncTryFrom, + AsyncTryInto, + }; + +} + +/// Prelude to use essentials: `use my_module::prelude::*`. +#[ cfg( feature = "enabled" ) ] +#[ allow( unused_imports ) ] +pub mod prelude +{ + use super::*; +} diff --git a/module/core/async_from/tests/inc/basic_test.rs b/module/core/async_from/tests/inc/basic_test.rs new file mode 100644 index 0000000000..18d6fa2d94 --- /dev/null +++ b/module/core/async_from/tests/inc/basic_test.rs @@ -0,0 +1,84 @@ +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_from( 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_from( "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_from/tests/inc/mod.rs b/module/core/async_from/tests/inc/mod.rs new file mode 100644 index 0000000000..329271ad56 --- /dev/null +++ b/module/core/async_from/tests/inc/mod.rs @@ -0,0 +1,3 @@ +use super::*; + +mod basic_test; diff --git a/module/core/async_from/tests/tests.rs b/module/core/async_from/tests/tests.rs new file mode 100644 index 0000000000..299521de4e --- /dev/null +++ b/module/core/async_from/tests/tests.rs @@ -0,0 +1,9 @@ +#![ allow( unused_imports ) ] + +include!( "../../../../module/step/meta/src/module/terminal.rs" ); + +use async_from as the_module; +// use test_tools::exposed::*; + +#[ cfg( feature = "enabled" ) ] +mod inc; diff --git a/module/core/async_tools/Cargo.toml b/module/core/async_tools/Cargo.toml new file mode 100644 index 0000000000..0f6c4f835b --- /dev/null +++ b/module/core/async_tools/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "async_tools" +version = "0.1.0" +edition = "2021" +authors = [ + "Kostiantyn Wandalen ", +] +license = "MIT" +readme = "Readme.md" +documentation = "https://docs.rs/async_tools" +repository = "https://github.com/Wandalen/wTools/tree/master/module/core/async_tools" +homepage = "https://github.com/Wandalen/wTools/tree/master/module/core/async_tools" +description = """ +Toolkit for asynchronous programming. +""" +categories = [ "algorithms", "development-tools" ] +keywords = [ "fundamental", "general-purpose" ] + +[lints] +workspace = true + +[package.metadata.docs.rs] +features = [ "full" ] +all-features = false + +[features] +default = [ "enabled", "async_from", "async_try_from" ] +full = [ "default" ] +enabled = [] +async_from = [ "async_from/async_from" ] +async_try_from = [ "async_from/async_try_from" ] + +[dependencies] +async-trait = { workspace = true } +async_from = { workspace = true } + +[dev-dependencies] +# test_tools = { workspace = true } +tokio = { workspace = true, default-features = false, features = [ "rt-multi-thread", "time", "macros" ] } diff --git a/module/core/async_tools/License b/module/core/async_tools/License new file mode 100644 index 0000000000..72c80c1308 --- /dev/null +++ b/module/core/async_tools/License @@ -0,0 +1,22 @@ +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 +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/core/async_tools/Readme.md b/module/core/async_tools/Readme.md new file mode 100644 index 0000000000..0b469a2688 --- /dev/null +++ b/module/core/async_tools/Readme.md @@ -0,0 +1,6 @@ + + +# Module :: async_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/Moduleasync_fromPush.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/Moduleasync_fromPush.yml) [![docs.rs](https://img.shields.io/docsrs/async_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/async_tools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + +Toolkit for asynchronous programming. diff --git a/module/move/assistant/src/lib.rs b/module/core/async_tools/src/lib.rs similarity index 60% rename from module/move/assistant/src/lib.rs rename to module/core/async_tools/src/lib.rs index 92684e4e90..0390e0dbe2 100644 --- a/module/move/assistant/src/lib.rs +++ b/module/core/async_tools/src/lib.rs @@ -1,84 +1,79 @@ -#![ cfg_attr( feature = "no_std", no_std ) ] + #![ doc( html_logo_url = "https://raw.githubusercontent.com/Wandalen/wTools/master/asset/img/logo_v3_trans_square.png" ) ] #![ doc( html_favicon_url = "https://raw.githubusercontent.com/Wandalen/wTools/alpha/asset/img/logo_v3_trans_square_icon_small_v2.ico" ) ] -#![ doc( html_root_url = "https://docs.rs/assistant/latest/assistant/" ) ] +#![ doc( html_root_url = "https://docs.rs/async_tools/latest/async_tools/" ) ] #![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] +/// Namespace with dependencies. +#[ cfg( feature = "enabled" ) ] +pub mod dependency +{ + pub use ::async_trait; + pub use ::async_from; +} -/// Internal namespace. +/// Define a private namespace for all its items. +#[ cfg( feature = "enabled" ) ] mod private { } -pub mod client; - +#[ cfg( feature = "enabled" ) ] +#[ doc( inline ) ] #[ allow( unused_imports ) ] pub use own::*; /// Own namespace of the module. +#[ cfg( feature = "enabled" ) ] #[ allow( unused_imports ) ] pub mod own { use super::*; - #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use super:: - { - client::orphan::*, - }; + pub use orphan::*; #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use orphan::*; + pub use ::async_from::orphan::*; } /// Orphan namespace of the module. +#[ cfg( feature = "enabled" ) ] #[ allow( unused_imports ) ] pub mod orphan { use super::*; #[ doc( inline ) ] - #[ allow( unused_imports ) ] pub use exposed::*; + } /// Exposed namespace of the module. +#[ cfg( feature = "enabled" ) ] #[ allow( unused_imports ) ] pub mod exposed { use super::*; #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use super:: - { - client::exposed::*, - }; + pub use prelude::*; + + #[ doc( inline ) ] + pub use ::async_trait::async_trait; #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use reflect_tools:: - { - Fields, - _IteratorTrait, - IteratorTrait, - }; + pub use ::async_from::exposed::*; } /// Prelude to use essentials: `use my_module::prelude::*`. +#[ cfg( feature = "enabled" ) ] #[ allow( unused_imports ) ] pub mod prelude { use super::*; #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use super:: - { - client::prelude::*, - }; + pub use ::async_from::prelude::*; } diff --git a/module/core/async_tools/tests/tests.rs b/module/core/async_tools/tests/tests.rs new file mode 100644 index 0000000000..415170a560 --- /dev/null +++ b/module/core/async_tools/tests/tests.rs @@ -0,0 +1,10 @@ +//! All tests +#![ allow( unused_imports ) ] + +include!( "../../../../module/step/meta/src/module/terminal.rs" ); + +use async_tools as the_module; + +#[ cfg( feature = "enabled" ) ] +#[ path = "../../../../module/core/async_from/tests/inc/mod.rs" ] +mod inc; diff --git a/module/core/clone_dyn/Cargo.toml b/module/core/clone_dyn/Cargo.toml index 922f03fc69..18b1b5e373 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.23.0" +version = "0.32.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -26,16 +26,17 @@ all-features = false [features] -default = [ "enabled", "clone_dyn_types", "clone_dyn_meta" ] -full = [ "enabled", "clone_dyn_types", "clone_dyn_meta" ] +default = [ "enabled", "clone_dyn_types", "derive_clone_dyn" ] +full = [ "enabled", "clone_dyn_types", "derive_clone_dyn" ] enabled = [] clone_dyn_types = [ "dep:clone_dyn_types", "clone_dyn_types/enabled" ] -clone_dyn_meta = [ "dep:clone_dyn_meta", "clone_dyn_meta/enabled", "clone_dyn_types" ] +derive_clone_dyn = [ "dep:clone_dyn_meta", "clone_dyn_meta/enabled", "clone_dyn_types" ] [dependencies] clone_dyn_meta = { workspace = true, optional = true } clone_dyn_types = { workspace = true, optional = true } +# clone_dyn_types = { version = "0.27.0", optional = true } [dev-dependencies] test_tools = { workspace = true } diff --git a/module/core/clone_dyn/License b/module/core/clone_dyn/License index 6d5ef8559f..72c80c1308 100644 --- a/module/core/clone_dyn/License +++ b/module/core/clone_dyn/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/core/clone_dyn/Readme.md index f23c03a8c9..331eeb7b8b 100644 --- a/module/core/clone_dyn/Readme.md +++ b/module/core/clone_dyn/Readme.md @@ -1,7 +1,7 @@ # Module :: clone_dyn - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_clone_dyn_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_clone_dyn_push.yml) [![docs.rs](https://img.shields.io/docsrs/clone_dyn?color=e3e8f0&logo=docs.rs)](https://docs.rs/clone_dyn) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fclone_dyn%2Fexamples%2Fclone_dyn_trivial.rs,RUN_POSTFIX=--example%20clone_dyn_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_clone_dyn_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_clone_dyn_push.yml) [![docs.rs](https://img.shields.io/docsrs/clone_dyn?color=e3e8f0&logo=docs.rs)](https://docs.rs/clone_dyn) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fclone_dyn%2Fexamples%2Fclone_dyn_trivial.rs,RUN_POSTFIX=--example%20module%2Fcore%2Fclone_dyn%2Fexamples%2Fclone_dyn_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Derive to clone dyn structures. @@ -70,9 +70,9 @@ The main function demonstrates the overall usage by creating a vector, obtaining ```rust -# #[ cfg( not( all( feature = "enabled", feature = "clone_dyn_meta" ) ) ) ] +# #[ cfg( not( all( feature = "enabled", feature = "derive_clone_dyn" ) ) ) ] # fn main() {} -# #[ cfg( all( feature = "enabled", feature = "clone_dyn_meta" ) ) ] +# #[ cfg( all( feature = "enabled", feature = "derive_clone_dyn" ) ) ] # fn main() # { diff --git a/module/core/clone_dyn/examples/clone_dyn_trivial.rs b/module/core/clone_dyn/examples/clone_dyn_trivial.rs index cd67d1cbea..aecf14563d 100644 --- a/module/core/clone_dyn/examples/clone_dyn_trivial.rs +++ b/module/core/clone_dyn/examples/clone_dyn_trivial.rs @@ -56,9 +56,9 @@ //! The main function demonstrates the overall usage by creating a vector, obtaining an iterator, and using the iterator to print elements. //! -#[ cfg( not( all( feature = "enabled", feature = "clone_dyn_meta" ) ) ) ] +#[ cfg( not( all( feature = "enabled", feature = "derive_clone_dyn" ) ) ) ] fn main() {} -#[ cfg( all( feature = "enabled", feature = "clone_dyn_meta" ) ) ] +#[ cfg( all( feature = "enabled", feature = "derive_clone_dyn" ) ) ] fn main() { use clone_dyn::{ clone_dyn, CloneDyn }; diff --git a/module/core/clone_dyn/src/lib.rs b/module/core/clone_dyn/src/lib.rs index 2f2ba25890..57ae64c7f8 100644 --- a/module/core/clone_dyn/src/lib.rs +++ b/module/core/clone_dyn/src/lib.rs @@ -5,17 +5,16 @@ #![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] /// Namespace with dependencies. - #[ cfg( feature = "enabled" ) ] pub mod dependency { - #[ cfg( feature = "clone_dyn_meta" ) ] + #[ cfg( feature = "derive_clone_dyn" ) ] pub use ::clone_dyn_meta; #[ cfg( feature = "clone_dyn_types" ) ] pub use ::clone_dyn_types; } -/// Internal namespace. +/// Define a private namespace for all its items. #[ cfg( feature = "enabled" ) ] mod private { @@ -72,7 +71,7 @@ pub mod prelude #[ doc( inline ) ] #[ allow( unused_imports ) ] - #[ cfg( feature = "clone_dyn_meta" ) ] + #[ cfg( feature = "derive_clone_dyn" ) ] pub use ::clone_dyn_meta::clone_dyn; #[ doc( inline ) ] diff --git a/module/core/clone_dyn/tests/inc/mod.rs b/module/core/clone_dyn/tests/inc/mod.rs index fc05ba6236..6e0cb7295a 100644 --- a/module/core/clone_dyn/tests/inc/mod.rs +++ b/module/core/clone_dyn/tests/inc/mod.rs @@ -2,9 +2,9 @@ #[ allow( unused_imports ) ] use super::*; -#[ cfg( feature = "clone_dyn_meta" ) ] +#[ cfg( feature = "derive_clone_dyn" ) ] pub mod basic_manual; -#[ cfg( feature = "clone_dyn_meta" ) ] +#[ cfg( feature = "derive_clone_dyn" ) ] pub mod basic; -#[ cfg( feature = "clone_dyn_meta" ) ] +#[ cfg( feature = "derive_clone_dyn" ) ] pub mod parametrized; diff --git a/module/core/clone_dyn/tests/smoke_test.rs b/module/core/clone_dyn/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/core/clone_dyn/tests/smoke_test.rs +++ b/module/core/clone_dyn/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/core/clone_dyn_meta/Cargo.toml b/module/core/clone_dyn_meta/Cargo.toml index 35a9783798..3bce911a14 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.23.0" +version = "0.30.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/clone_dyn_meta/License b/module/core/clone_dyn_meta/License index 6d5ef8559f..72c80c1308 100644 --- a/module/core/clone_dyn_meta/License +++ b/module/core/clone_dyn_meta/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/tests/smoke_test.rs b/module/core/clone_dyn_meta/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/core/clone_dyn_meta/tests/smoke_test.rs +++ b/module/core/clone_dyn_meta/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/core/clone_dyn_types/Cargo.toml b/module/core/clone_dyn_types/Cargo.toml index 9c5db44b4a..6734d7e5c9 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.22.0" +version = "0.31.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/clone_dyn_types/License b/module/core/clone_dyn_types/License index 6d5ef8559f..72c80c1308 100644 --- a/module/core/clone_dyn_types/License +++ b/module/core/clone_dyn_types/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/core/clone_dyn_types/Readme.md index 12cc1e5f46..a00356dfd2 100644 --- a/module/core/clone_dyn_types/Readme.md +++ b/module/core/clone_dyn_types/Readme.md @@ -1,7 +1,7 @@ -# Module :: clone_dyn_types +# Module :: `clone_dyn_types` - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_clone_dyn_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_clone_dyn_push.yml) [![docs.rs](https://img.shields.io/docsrs/clone_dyn_types?color=e3e8f0&logo=docs.rs)](https://docs.rs/clone_dyn_types) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fclone_dyn%2Fexamples%2Fclone_dyn_trivial.rs,RUN_POSTFIX=--example%20clone_dyn_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_clone_dyn_types_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_clone_dyn_types_push.yml) [![docs.rs](https://img.shields.io/docsrs/clone_dyn_types?color=e3e8f0&logo=docs.rs)](https://docs.rs/clone_dyn_types) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fclone_dyn_types%2Fexamples%2Fclone_dyn_types_trivial.rs,RUN_POSTFIX=--example%20module%2Fcore%2Fclone_dyn_types%2Fexamples%2Fclone_dyn_types_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Derive to clone dyn structures. diff --git a/module/core/clone_dyn_types/src/lib.rs b/module/core/clone_dyn_types/src/lib.rs index 3fc94133df..522f4c6b62 100644 --- a/module/core/clone_dyn_types/src/lib.rs +++ b/module/core/clone_dyn_types/src/lib.rs @@ -10,7 +10,7 @@ pub mod dependency { } -/// Internal namespace. +/// Define a private namespace for all its items. // #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] #[ cfg( feature = "enabled" ) ] mod private @@ -39,6 +39,7 @@ mod private T : Clone, { #[ inline ] + #[ allow( clippy::implicit_return, clippy::as_conversions, clippy::ptr_as_ptr ) ] fn __clone_dyn( &self, _ : DontCallMe ) -> *mut () { Box::< T >::into_raw( Box::new( self.clone() ) ) as *mut () @@ -51,6 +52,7 @@ mod private T : Clone, { #[ inline ] + #[ allow( clippy::implicit_return, clippy::as_conversions, clippy::ptr_as_ptr ) ] fn __clone_dyn( &self, _ : DontCallMe ) -> *mut () { Box::< [ T ] >::into_raw( self.iter().cloned().collect() ) as *mut () @@ -61,6 +63,7 @@ mod private impl CloneDyn for str { #[ inline ] + #[ allow( clippy::as_conversions, clippy::ptr_as_ptr, clippy::implicit_return ) ] fn __clone_dyn( &self, _ : DontCallMe ) -> *mut () { Box::< str >::into_raw( Box::from( self ) ) as *mut () @@ -68,7 +71,7 @@ mod private } /// - /// True clone which is applicable not only to clonable entities, but to trait objects implementing CloneDyn. + /// True clone which is applicable not only to clonable entities, but to trait objects implementing `CloneDyn`. /// /// # Example /// @@ -86,7 +89,6 @@ mod private /// /// assert_eq!( original.value, cloned.value ); /// ``` - #[ inline ] pub fn clone< T >( src : &T ) -> T where @@ -100,7 +102,7 @@ mod private // that the `CloneDyn` trait is correctly implemented for the given type `T`, ensuring that `__clone_dyn` returns a // valid pointer to a cloned instance of `T`. // - #[ allow( unsafe_code ) ] + #[ allow( unsafe_code, clippy::as_conversions, clippy::ptr_as_ptr, clippy::implicit_return, clippy::undocumented_unsafe_blocks ) ] unsafe { *Box::from_raw( < T as CloneDyn >::__clone_dyn( src, DontCallMe ) as *mut T ) @@ -171,7 +173,6 @@ mod private /// let cloned : Box< dyn MyTrait > = clone_into_box( &MyStruct { value : 42 } ); /// /// ``` - #[ inline ] pub fn clone_into_box< T >( ref_dyn : &T ) -> Box< T > where @@ -185,11 +186,14 @@ mod private // The safety of this function relies on the correct implementation of the `CloneDyn` trait for the given type `T`. // Specifically, `__clone_dyn` must return a valid pointer to a cloned instance of `T`. // - #[ allow( unsafe_code ) ] + #[ allow( unsafe_code, clippy::implicit_return, clippy::as_conversions, clippy::ptr_cast_constness, clippy::ptr_as_ptr, clippy::multiple_unsafe_ops_per_block, clippy::undocumented_unsafe_blocks, clippy::ref_as_ptr, clippy::borrow_as_ptr ) ] unsafe { let mut ptr = ref_dyn as *const T; - let data_ptr = &mut ptr as *mut *const T as *mut *mut (); + #[ 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 ) } @@ -207,13 +211,14 @@ mod private impl< T : Clone > Sealed for [ T ] {} impl Sealed for str {} } - use sealed::*; + use sealed::{ DontCallMe, Sealed }; } #[ cfg( feature = "enabled" ) ] #[ doc( inline ) ] #[ allow( unused_imports ) ] +#[ allow( clippy::pub_use ) ] pub use own::*; /// Own namespace of the module. @@ -221,8 +226,9 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { - use super::*; + use super::orphan; #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] pub use orphan::*; } @@ -231,8 +237,9 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { - use super::*; + use super::exposed; #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] pub use exposed::*; } @@ -241,8 +248,9 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { - use super::*; + use super::prelude; #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] pub use prelude::*; } @@ -251,8 +259,9 @@ pub mod exposed #[ allow( unused_imports ) ] pub mod prelude { - use super::*; + use super::private; #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] pub use private:: { CloneDyn, diff --git a/module/core/clone_dyn_types/tests/smoke_test.rs b/module/core/clone_dyn_types/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/core/clone_dyn_types/tests/smoke_test.rs +++ b/module/core/clone_dyn_types/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/core/collection_tools/Cargo.toml b/module/core/collection_tools/Cargo.toml index 303edaa2df..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.11.0" +version = "0.18.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -11,16 +11,14 @@ documentation = "https://docs.rs/collection_tools" repository = "https://github.com/Wandalen/wTools/tree/master/module/core/collection_tools" homepage = "https://github.com/Wandalen/wTools/tree/master/module/core/collection_tools" description = """ -Collection of general purpose tools to manipulate collections( containers like Vec/HashMap/HashSet ). +General purpose tools to manipulate collections( containers like Vec/HashMap/HashSet ). """ categories = [ "algorithms", "development-tools" ] keywords = [ "fundamental", "general-purpose" ] - [lints] workspace = true - [package.metadata.docs.rs] features = [ "full" ] all-features = false @@ -28,31 +26,26 @@ all-features = false [features] no_std = [ - "test_tools/no_std", ] use_alloc = [ - "no_std", # qqq : for Anton : why is that better? -- use_alloc means that we do not use std, but alloc and hashbrown + "no_std", "hashbrown", - # "test_tools/use_alloc", // why is it needed? -- not needed, removed ] default = [ "enabled", - # "reexports", "collection_constructors", "collection_into_constructors", ] full = [ "enabled", - # "reexports", "collection_constructors", "collection_into_constructors", ] enabled = [] -# reexports = [] # Collection constructors, like `hmap!{ "key" => "val" }` collection_constructors = [] @@ -63,7 +56,7 @@ collection_into_constructors = [] [dependencies] ## external -hashbrown = { version = "~0.14.3", optional = true, default-features = false, features = [ "default" ] } +hashbrown = { workspace = true, optional = true, default-features = false, features = [ "default" ] } [dev-dependencies] test_tools = { workspace = true } diff --git a/module/core/collection_tools/License b/module/core/collection_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/core/collection_tools/License +++ b/module/core/collection_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/collection_tools/Readme.md b/module/core/collection_tools/Readme.md index 1430c6d6ef..fadff95c94 100644 --- a/module/core/collection_tools/Readme.md +++ b/module/core/collection_tools/Readme.md @@ -1,11 +1,11 @@ -# Module :: collection_tools +# Module :: `collection_tools` - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_collection_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_collection_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/collection_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/collection_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fcollection_tools%2Fexamples%2Fcollection_tools_trivial.rs,RUN_POSTFIX=--example%20collection_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_collection_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_collection_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/collection_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/collection_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fcollection_tools%2Fexamples%2Fcollection_tools_trivial.rs,RUN_POSTFIX=--example%20module%2Fcore%2Fcollection_tools%2Fexamples%2Fcollection_tools_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) -Collection of general purpose tools to manipulate collections( containers like Vec/HashMap/HashSet... ). +General purpose tools to manipulate collections( containers like Vec/HashMap/HashSet... ). ### Basic Use Case :: Variadic Constructors for Collections @@ -71,7 +71,7 @@ assert_eq!( meta_list, meta_list ); ### Basic Use Case :: `no_std` `HashSet` / `HashMap` -When implementing a `no_std` environment with the `use_alloc` feature in your Rust project, you'll encounter a challenge: collections like `Vec` are imported differently depending on the availability of the `std` library. Moreover, to use data structures such as `HashSet` or `HashMap` in a `no_std` context, it's necessary to depend on third-party crates, as these are not provided by the `alloc` crate directly. This crate aims to simplify the process of designing Rust libraries or applications that require these collections in a `no_std` environment, offering a more streamlined approach to working with dynamic data structures without the standard library. +When implementing a `no_std` ( `!use_std` ) environment with the `use_alloc` feature in your Rust project, you'll encounter a challenge: collections like `Vec` are imported differently depending on the availability of the `std` library. Moreover, to use data structures such as `HashSet` or `HashMap` in a `no_std` context, it's necessary to depend on third-party crates, as these are not provided by the `alloc` crate directly. This crate aims to simplify the process of designing Rust libraries or applications that require these collections in a `no_std` environment, offering a more streamlined approach to working with dynamic data structures without the standard library. You can do @@ -98,7 +98,7 @@ Instead of # #[ cfg( any( feature = "use_alloc", not( feature = "no_std" ) ) ) ] # { -#[ cfg( feature = "use_alloc" ) ] +#[ cfg( all( feature = "no_std", feature = "use_alloc" ) ) ] use hashbrown::HashSet; // a `no_std` replacement for `HashSet` #[ cfg( not( feature = "no_std" ) ) ] use std::collections::HashSet; @@ -120,7 +120,8 @@ While strict macros require you to have all members of the same type, more relax For example: ```rust -# #[ cfg( all( feature = "enabled", feature = "collection_into_constructors", any( not( feature = "no_std" ), feature = "use_alloc" ) ) ) ] +# #[ cfg( all( feature = "enabled", feature = "collection_into_constructors" ) ) ] +# #[ cfg( any( feature = "use_alloc", not( feature = "no_std" ) ) ) ] # { use std::borrow::Cow; let vec : Vec< String > = collection_tools::into_vec!( "&str", "String".to_string(), Cow::from( "Cow" ) ); diff --git a/module/core/collection_tools/examples/collection_tools_trivial.rs b/module/core/collection_tools/examples/collection_tools_trivial.rs index 8a11bb85bf..79ff09bf0d 100644 --- a/module/core/collection_tools/examples/collection_tools_trivial.rs +++ b/module/core/collection_tools/examples/collection_tools_trivial.rs @@ -19,18 +19,15 @@ //! a `HashMap`, making your code cleaner and more concise. This is particularly useful in cases //! where you need to define a map with a known set of key-value pairs upfront. -#[ cfg( not( all -( -// not( feature = "use_alloc" ) ) ], - all( feature = "enabled", feature = "collection_constructors" ), - any( not( feature = "no_std" ), feature = "use_alloc" ) +#[ cfg( not( all( + feature = "enabled", + feature = "collection_constructors", + any( feature = "use_alloc", not( feature = "no_std" ) ) )))] -fn main(){} +fn main() {} -// zzz : aaa : rid of `#[ cfg( not( feature = "use_alloc" ) ) ]` -- Rid of by not relying on std -// #[ cfg( not( feature = "use_alloc" ) ) ] #[ cfg( all( feature = "enabled", feature = "collection_constructors" ) ) ] -#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +#[ cfg( any( feature = "use_alloc", not( feature = "no_std" ) ) ) ] fn main() { use collection_tools::*; diff --git a/module/core/collection_tools/src/collection.rs b/module/core/collection_tools/src/collection.rs deleted file mode 100644 index ed2b504917..0000000000 --- a/module/core/collection_tools/src/collection.rs +++ /dev/null @@ -1,33 +0,0 @@ -/// Not meant to be called directly. -#[ doc( hidden ) ] -#[ macro_export( local_inner_macros ) ] -macro_rules! count -{ - ( @single $( $x : tt )* ) => ( () ); - - ( - @count $( $rest : expr ),* - ) - => - ( - < [ () ] >::len( &[ $( count!( @single $rest ) ),* ] ) - ); -} - -/// [std::collections::BTreeMap] macros -pub mod bmap; -/// [std::collections::BTreeSet] macros -pub mod bset; -/// [std::collections::BinaryHeap] macros -pub mod heap; -/// [std::collections::HashMap] macros -pub mod hmap; -/// [std::collections::HashSet] macros -pub mod hset; -/// [std::collections::LinkedList] macros -pub mod llist; -/// [Vec] macros -pub mod vec; -/// [std::collections::VecDeque] macros -pub mod deque; - diff --git a/module/core/collection_tools/src/collection/heap.rs b/module/core/collection_tools/src/collection/binary_heap.rs similarity index 88% rename from module/core/collection_tools/src/collection/heap.rs rename to module/core/collection_tools/src/collection/binary_heap.rs index 8d38492497..faaa934427 100644 --- a/module/core/collection_tools/src/collection/heap.rs +++ b/module/core/collection_tools/src/collection/binary_heap.rs @@ -1,6 +1,10 @@ +#[ allow( unused_imports, clippy::wildcard_imports ) ] +use super::*; + #[ doc( inline ) ] #[ allow( unused_imports ) ] - pub use alloc::collections::binary_heap::*; +#[ allow( clippy::pub_use ) ] +pub use alloc::collections::binary_heap::*; /// Creates a `BinaryHeap` from a list of elements. /// @@ -29,8 +33,8 @@ /// # Parameters /// /// - `$( $key:expr ),* $( , )?`: A comma-separated list of elements to insert into the `BinaryHeap`. -/// Each element can be of any type that implements the `Into` trait, where `T` is the -/// type stored in the `BinaryHeap`. +/// Each element can be of any type that implements the `Into` trait, where `T` is the +/// type stored in the `BinaryHeap`. /// /// # Returns /// @@ -57,7 +61,7 @@ macro_rules! heap => {{ let _cap = count!( @count $( $key ),* ); - let mut _heap = $crate::heap::BinaryHeap::with_capacity( _cap ); + let mut _heap = $crate::collection::BinaryHeap::with_capacity( _cap ); $( _heap.push( $key ); )* @@ -98,8 +102,8 @@ macro_rules! heap /// # Parameters /// /// - `$( $key:expr ),* $( , )?`: A comma-separated list of elements to insert into the `BinaryHeap`. -/// Each element can be of any type that implements the `Into` trait, where `T` is the -/// type stored in the `BinaryHeap`. +/// Each element can be of any type that implements the `Into` trait, where `T` is the +/// type stored in the `BinaryHeap`. /// /// # Returns /// @@ -146,7 +150,7 @@ macro_rules! into_heap => {{ let _cap = count!( @count $( $key ),* ); - let mut _heap = $crate::heap::BinaryHeap::with_capacity( _cap ); + let mut _heap = $crate::collection::BinaryHeap::with_capacity( _cap ); $( _heap.push( Into::into( $key ) ); )* diff --git a/module/core/collection_tools/src/collection/bmap.rs b/module/core/collection_tools/src/collection/btree_map.rs similarity index 89% rename from module/core/collection_tools/src/collection/bmap.rs rename to module/core/collection_tools/src/collection/btree_map.rs index e96f045e84..fc79de564b 100644 --- a/module/core/collection_tools/src/collection/bmap.rs +++ b/module/core/collection_tools/src/collection/btree_map.rs @@ -1,5 +1,9 @@ +#[ allow( unused_imports, clippy::wildcard_imports ) ] +use super::*; + #[ doc( inline ) ] #[ allow( unused_imports ) ] +#[ allow( clippy::pub_use ) ] pub use alloc::collections::btree_map::*; /// Creates a `BTreeMap` from a list of key-value pairs. @@ -29,8 +33,8 @@ pub use alloc::collections::btree_map::*; /// # Parameters /// /// - `$( $key:expr => $value:expr ),* $( , )?`: A comma-separated list of key-value pairs to insert into the `BTreeMap`. -/// Each key and value can be of any type that implements the `Into< K >` and `Into< V >` traits, where `K` and `V` are the -/// types stored in the `BTreeMap` as keys and values, respectively. +/// Each key and value can be of any type that implements the `Into< K >` and `Into< V >` traits, where `K` and `V` are the +/// types stored in the `BTreeMap` as keys and values, respectively. /// /// # Returns /// @@ -70,7 +74,7 @@ macro_rules! bmap ) => {{ - let mut _map = $crate::bmap::BTreeMap::new(); + let mut _map = $crate::collection::BTreeMap::new(); $( let _ = _map.insert( $key , $value ); )* @@ -111,8 +115,8 @@ macro_rules! bmap /// # Parameters /// /// - `$( $key:expr => $value:expr ),* $( , )?`: A comma-separated list of key-value pairs to insert into the `BTreeMap`. -/// Each key and value can be of any type that implements the `Into< K >` and `Into< V >` traits, where `K` and `V` are the -/// types stored in the `BTreeMap` as keys and values, respectively. +/// Each key and value can be of any type that implements the `Into< K >` and `Into< V >` traits, where `K` and `V` are the +/// types stored in the `BTreeMap` as keys and values, respectively. /// /// # Returns /// @@ -163,7 +167,7 @@ macro_rules! into_bmap ) => {{ - let mut _map = $crate::bmap::BTreeMap::new(); + let mut _map = $crate::collection::BTreeMap::new(); $( let _ = _map.insert( Into::into( $key ), Into::into( $value ) ); )* diff --git a/module/core/collection_tools/src/collection/bset.rs b/module/core/collection_tools/src/collection/btree_set.rs similarity index 89% rename from module/core/collection_tools/src/collection/bset.rs rename to module/core/collection_tools/src/collection/btree_set.rs index c0c6d249ed..d7b22ababc 100644 --- a/module/core/collection_tools/src/collection/bset.rs +++ b/module/core/collection_tools/src/collection/btree_set.rs @@ -1,5 +1,9 @@ +#[ allow( unused_imports, clippy::wildcard_imports ) ] +use super::*; + #[ doc( inline ) ] #[ allow( unused_imports ) ] +#[ allow( clippy::pub_use ) ] pub use alloc::collections::btree_set::*; /// Creates a `BTreeSet` from a list of elements. @@ -26,8 +30,8 @@ pub use alloc::collections::btree_set::*; /// # Parameters /// /// - `$( $key:expr ),* $( , )?`: A comma-separated list of elements to insert into the `BTreeSet`. -/// Each element can be of any type that implements the `Into` trait, where `T` is the -/// type stored in the `BTreeSet`. +/// Each element can be of any type that implements the `Into` trait, where `T` is the +/// type stored in the `BTreeSet`. /// /// # Returns /// @@ -56,7 +60,7 @@ macro_rules! bset ) => {{ - let mut _set = $crate::bset::BTreeSet::new(); + let mut _set = $crate::collection::BTreeSet::new(); $( _set.insert( $key ); )* @@ -97,8 +101,8 @@ macro_rules! bset /// # Parameters /// /// - `$( $key:expr ),* $( , )?`: A comma-separated list of elements to insert into the `BTreeSet`. -/// Each element can be of any type that implements the `Into` trait, where `T` is the -/// type stored in the `BTreeSet`. +/// Each element can be of any type that implements the `Into` trait, where `T` is the +/// type stored in the `BTreeSet`. /// /// # Returns /// @@ -149,7 +153,7 @@ macro_rules! into_bset ) => {{ - let mut _set = $crate::bset::BTreeSet::new(); + let mut _set = $crate::collection::BTreeSet::new(); $( _set.insert( Into::into( $key ) ); )* diff --git a/module/core/collection_tools/src/collection/hmap.rs b/module/core/collection_tools/src/collection/hash_map.rs similarity index 87% rename from module/core/collection_tools/src/collection/hmap.rs rename to module/core/collection_tools/src/collection/hash_map.rs index eceac4ee9b..2b2a8226a6 100644 --- a/module/core/collection_tools/src/collection/hmap.rs +++ b/module/core/collection_tools/src/collection/hash_map.rs @@ -1,10 +1,16 @@ -#[ cfg( feature = "use_alloc" ) ] +#[ allow( unused_imports ) ] +use super::*; + +// xxx : qqq : wrong +#[ cfg( all( feature = "no_std", feature = "use_alloc" ) ) ] #[ doc( inline ) ] #[ allow( unused_imports ) ] pub use crate::dependency::hashbrown::hash_map::*; + #[ cfg( not( feature = "no_std" ) ) ] #[ doc( inline ) ] #[ allow( unused_imports ) ] +#[ allow( clippy::pub_use ) ] pub use std::collections::hash_map::*; /// Creates a `HashMap` from a list of key-value pairs. @@ -14,7 +20,7 @@ pub use std::collections::hash_map::*; /// # Origin /// /// This collection can be reexported from different crates: -/// - from `std`, if `no_std` flag if off +/// - from `std`, if `use_std` is on ( `no_std` flag if off ) /// - from `hashbrown`, if `use_alloc` flag if on /// /// # Syntax @@ -36,8 +42,8 @@ pub use std::collections::hash_map::*; /// # Parameters /// /// - `$( $key:expr => $value:expr ),* $( , )?`: A comma-separated list of key-value pairs to insert into the `HashMap`. -/// Each key and value can be of any type that implements the `Into` and `Into` traits, where `K` and `V` are the -/// types stored in the `HashMap` as keys and values, respectively. +/// Each key and value can be of any type that implements the `Into` and `Into` traits, where `K` and `V` are the +/// types stored in the `HashMap` as keys and values, respectively. /// /// # Returns /// @@ -77,7 +83,7 @@ macro_rules! hmap => {{ let _cap = count!( @count $( $key ),* ); - let mut _map = $crate::hmap::HashMap::with_capacity( _cap ); + let mut _map = $crate::collection::HashMap::with_capacity( _cap ); $( let _ = _map.insert( $key, $value ); )* @@ -98,7 +104,7 @@ macro_rules! hmap /// # Origin /// /// This collection can be reexported from different crates: -/// - from `std`, if `no_std` flag if off +/// - from `std`, if `use_std` is on ( `no_std` flag if off ) /// - from `hashbrown`, if `use_alloc` flag if on /// /// # Syntax @@ -120,8 +126,8 @@ macro_rules! hmap /// # Parameters /// /// - `$( $key:expr => $value:expr ),* $( , )?`: A comma-separated list of key-value pairs to insert into the `HashMap`. -/// Each key and value can be of any type that implements the `Into` and `Into` traits, where `K` and `V` are the -/// types stored in the `HashMap` as keys and values, respectively. +/// Each key and value can be of any type that implements the `Into` and `Into` traits, where `K` and `V` are the +/// types stored in the `HashMap` as keys and values, respectively. /// /// # Returns /// @@ -172,7 +178,7 @@ macro_rules! into_hmap => {{ let _cap = count!( @count $( $key ),* ); - let mut _map = $crate::hmap::HashMap::with_capacity( _cap ); + let mut _map = $crate::collection::HashMap::with_capacity( _cap ); $( let _ = _map.insert( Into::into( $key ), Into::into( $value ) ); )* diff --git a/module/core/collection_tools/src/collection/hset.rs b/module/core/collection_tools/src/collection/hash_set.rs similarity index 89% rename from module/core/collection_tools/src/collection/hset.rs rename to module/core/collection_tools/src/collection/hash_set.rs index b9b2d682da..f2a73c5faf 100644 --- a/module/core/collection_tools/src/collection/hset.rs +++ b/module/core/collection_tools/src/collection/hash_set.rs @@ -1,10 +1,15 @@ +#[ allow( unused_imports ) ] +use super::*; + #[ cfg( feature = "use_alloc" ) ] #[ doc( inline ) ] #[ allow( unused_imports ) ] pub use crate::dependency::hashbrown::hash_set::*; + #[ cfg( not( feature = "no_std" ) ) ] #[ doc( inline ) ] #[ allow( unused_imports ) ] +#[ allow( clippy::pub_use ) ] pub use std::collections::hash_set::*; /// Creates a `HashSet` from a list of elements. @@ -14,7 +19,7 @@ pub use std::collections::hash_set::*; /// # Origin /// /// This collection can be reexported from different crates: -/// - from `std`, if `no_std` flag if off +/// - from `std`, if `use_std` is on ( `no_std` flag if off ) /// - from `hashbrown`, if `use_alloc` flag if on /// /// # Syntax @@ -36,8 +41,8 @@ pub use std::collections::hash_set::*; /// # Parameters /// /// - `$( $key:expr ),* $( , )?`: A comma-separated list of elements to insert into the `HashSet`. -/// Each element can be of any type that implements the `Into< T >` trait, where `T` is the -/// type stored in the `HashSet`. +/// Each element can be of any type that implements the `Into< T >` trait, where `T` is the +/// type stored in the `HashSet`. /// /// # Returns /// @@ -77,7 +82,7 @@ macro_rules! hset => {{ let _cap = count!( @count $( $key ),* ); - let mut _set = $crate::hset::HashSet::with_capacity( _cap ); + let mut _set = $crate::collection::HashSet::with_capacity( _cap ); $( let _ = _set.insert( $key ); )* @@ -96,9 +101,9 @@ macro_rules! hset /// type `T` used in the `HashSet`. Also, this means that sometimes you must specify the type of collection's items. /// /// # Origin -/// +/// /// This collection can be reexported from different crates: -/// - from `std`, if `no_std` flag if off +/// - from `std`, if `use_std` is on ( `no_std` flag if off ) /// - from `hashbrown`, if `use_alloc` flag if on /// /// # Syntax @@ -120,8 +125,8 @@ macro_rules! hset /// # Parameters /// /// - `$( $key:expr ),* $( , )?`: A comma-separated list of elements to insert into the `HashSet`. -/// Each element can be of any type that implements the `Into< T >` trait, where `T` is the -/// type stored in the `HashSet`. +/// Each element can be of any type that implements the `Into< T >` trait, where `T` is the +/// type stored in the `HashSet`. /// /// # Returns /// @@ -173,7 +178,7 @@ macro_rules! into_hset => {{ let _cap = count!( @count $( $key ),* ); - let mut _set = $crate::hset::HashSet::with_capacity( _cap ); + let mut _set = $crate::collection::HashSet::with_capacity( _cap ); $( let _ = _set.insert( Into::into( $key ) ); )* diff --git a/module/core/collection_tools/src/collection/llist.rs b/module/core/collection_tools/src/collection/linked_list.rs similarity index 91% rename from module/core/collection_tools/src/collection/llist.rs rename to module/core/collection_tools/src/collection/linked_list.rs index e6c8ddbe68..7fbaba79fa 100644 --- a/module/core/collection_tools/src/collection/llist.rs +++ b/module/core/collection_tools/src/collection/linked_list.rs @@ -1,5 +1,9 @@ +#[ allow( unused_imports, clippy::wildcard_imports ) ] +use super::*; + #[ doc( inline ) ] #[ allow( unused_imports ) ] +#[ allow( clippy::pub_use ) ] pub use alloc::collections::linked_list::*; /// Creates a `LinkedList` from a llist of elements. @@ -29,8 +33,8 @@ pub use alloc::collections::linked_list::*; /// # Parameters /// /// - `$( $key:expr ),* $( , )?`: A comma-separated llist of elements to insert into the `LinkedList`. -/// Each element can be of any type that implements the `Into` trait, where `T` is the -/// type stored in the `LinkedList`. +/// Each element can be of any type that implements the `Into` trait, where `T` is the +/// type stored in the `LinkedList`. /// /// # Returns /// @@ -70,7 +74,7 @@ macro_rules! llist {{ // "The LinkedList allows pushing and popping elements at either end in constant time." // So no `with_capacity` - let mut _lst = $crate::llist::LinkedList::new(); + let mut _lst = $crate::collection::LinkedList::new(); $( _lst.push_back( $key ); )* @@ -111,8 +115,8 @@ macro_rules! llist /// # Parameters /// /// - `$( $key:expr ),* $( , )?`: A comma-separated llist of elements to insert into the `LinkedList`. -/// Each element can be of any type that implements the `Into` trait, where `T` is the -/// type stored in the `LinkedList`. +/// Each element can be of any type that implements the `Into` trait, where `T` is the +/// type stored in the `LinkedList`. /// /// # Returns /// @@ -164,7 +168,7 @@ macro_rules! into_llist {{ // "The LinkedList allows pushing and popping elements at either end in constant time." // So no `with_capacity` - let mut _lst = $crate::llist::LinkedList::new(); + let mut _lst = $crate::collection::LinkedList::new(); $( _lst.push_back( Into::into( $key ) ); )* diff --git a/module/core/collection_tools/src/collection/mod.rs b/module/core/collection_tools/src/collection/mod.rs new file mode 100644 index 0000000000..5508e1263e --- /dev/null +++ b/module/core/collection_tools/src/collection/mod.rs @@ -0,0 +1,175 @@ +/// Not meant to be called directly. +#[ doc( hidden ) ] +#[ macro_export( local_inner_macros ) ] +macro_rules! count +{ + ( @single $( $x : tt )* ) => ( () ); + + ( + @count $( $rest : expr ),* + ) + => + ( + < [ () ] >::len( &[ $( count!( @single $rest ) ),* ] ) + ); +} + +#[ cfg( feature = "enabled" ) ] +#[ cfg( any( feature = "use_alloc", not( feature = "no_std" ) ) ) ] +extern crate alloc; + +/// [`std::collections::BTreeMap`] macros +pub mod btree_map; +/// [`std::collections::BTreeSet`] macros +pub mod btree_set; +/// [`std::collections::BinaryHeap`] macros +pub mod binary_heap; +/// [`std::collections::HashMap`] macros +pub mod hash_map; +/// [`std::collections::HashSet`] macros +pub mod hash_set; +/// [`std::collections::LinkedList`] macros +pub mod linked_list; +/// [Vec] macros +pub mod vector; +/// [`std::collections::VecDeque`] macros +pub mod vec_deque; + +#[ doc( inline ) ] +#[ allow( unused_imports ) ] +#[ cfg( feature = "enabled" ) ] +#[ allow( clippy::pub_use ) ] +pub use own::*; + +/// Own namespace of the module. +#[ cfg( feature = "enabled" ) ] +#[ allow( unused_imports ) ] +pub mod own +{ + #[ allow( clippy::wildcard_imports ) ] + use super::*; + + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use orphan::*; + // xxx2 : check + +} + +/// Parented namespace of the module. +#[ cfg( feature = "enabled" ) ] +#[ allow( unused_imports ) ] +pub mod orphan +{ + #[ allow( clippy::wildcard_imports ) ] + use super::*; + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use exposed::*; +} + +/// Exposed namespace of the module. +#[ cfg( feature = "enabled" ) ] +#[ allow( unused_imports ) ] +pub mod exposed +{ + #[ allow( clippy::wildcard_imports ) ] + use super::*; + + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use prelude::*; + + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use super::super::collection; + + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use super:: + { + btree_map, + btree_set, + binary_heap, + hash_map, + hash_set, + linked_list, + vector, + vec_deque, + }; + + #[ doc( inline ) ] + #[ cfg( any( feature = "use_alloc", not( feature = "no_std" ) ) ) ] + #[ cfg( feature = "collection_constructors" ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use crate:: + { + vec as dlist, + deque, + llist, + hset, + hmap, + bmap, + bset, + }; + + #[ doc( inline ) ] + #[ cfg( any( feature = "use_alloc", not( feature = "no_std" ) ) ) ] + #[ cfg( feature = "collection_into_constructors" ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use crate:: + { + into_vec, + into_vec as into_dlist, + into_vecd, + into_llist, + into_hset, + into_hmap, + into_bmap, + into_bset, + }; + + // #[ cfg( feature = "reexports" ) ] + #[ cfg( any( feature = "use_alloc", not( feature = "no_std" ) ) ) ] + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use + { + btree_map::BTreeMap, + btree_set::BTreeSet, + binary_heap::BinaryHeap, + hash_map::HashMap, + hash_set::HashSet, + linked_list::LinkedList, + vector::Vec, + vec_deque::VecDeque, + }; + + // #[ cfg( feature = "reexports" ) ] + #[ cfg( any( feature = "use_alloc", not( feature = "no_std" ) ) ) ] + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use + { + LinkedList as Llist, + Vec as Dlist, + VecDeque as Deque, + HashMap as Map, + HashMap as Hmap, + HashSet as Set, + HashSet as Hset, + BTreeMap as Bmap, + BTreeSet as Bset, + }; + + // qqq : cover by tests presence of all containers immidiately in collection_tools::* and in collection_tools::exposed::* + +} + +/// Prelude to use essentials: `use my_module::prelude::*`. +#[ cfg( feature = "enabled" ) ] +#[ allow( unused_imports ) ] +pub mod prelude +{ + use super::*; +} diff --git a/module/core/collection_tools/src/collection/deque.rs b/module/core/collection_tools/src/collection/vec_deque.rs similarity index 91% rename from module/core/collection_tools/src/collection/deque.rs rename to module/core/collection_tools/src/collection/vec_deque.rs index 66b106c6ec..218f64e7ed 100644 --- a/module/core/collection_tools/src/collection/deque.rs +++ b/module/core/collection_tools/src/collection/vec_deque.rs @@ -1,5 +1,9 @@ +#[ allow( unused_imports, clippy::wildcard_imports ) ] +use super::*; + #[ doc( inline ) ] #[ allow( unused_imports ) ] +#[ allow( clippy::pub_use ) ] pub use alloc::collections::vec_deque::*; /// Creates a `VecDeque` from a list of elements. @@ -35,8 +39,8 @@ pub use alloc::collections::vec_deque::*; /// # Parameters /// /// - `$( $key:expr ),* $( , )?`: A comma-separated list of elements to insert into the `VecDeque`. -/// Each element can be of any type that implements the `Into< T >` trait, where `T` is the -/// type stored in the `VecDeque`. +/// Each element can be of any type that implements the `Into< T >` trait, where `T` is the +/// type stored in the `VecDeque`. /// /// # Returns /// @@ -75,7 +79,7 @@ macro_rules! deque => {{ let _cap = count!( @count $( $key ),* ); - let mut _vecd = $crate::deque::VecDeque::with_capacity( _cap ); + let mut _vecd = $crate::collection::VecDeque::with_capacity( _cap ); $( _vecd.push_back( $key ); )* @@ -116,8 +120,8 @@ macro_rules! deque /// # Parameters /// /// - `$( $key:expr ),* $( , )?`: A comma-separated list of elements to insert into the `VecDeque`. -/// Each element can be of any type that implements the `Into< T >` trait, where `T` is the -/// type stored in the `VecDeque`. +/// Each element can be of any type that implements the `Into< T >` trait, where `T` is the +/// type stored in the `VecDeque`. /// /// # Returns /// @@ -168,7 +172,7 @@ macro_rules! into_vecd => {{ let _cap = count!( @count $( $key ),* ); - let mut _vecd = $crate::deque::VecDeque::with_capacity( _cap ); + let mut _vecd = $crate::collection::VecDeque::with_capacity( _cap ); $( _vecd.push_back( Into::into( $key ) ); )* diff --git a/module/core/collection_tools/src/collection/vec.rs b/module/core/collection_tools/src/collection/vector.rs similarity index 89% rename from module/core/collection_tools/src/collection/vec.rs rename to module/core/collection_tools/src/collection/vector.rs index 2c19db388f..568642d0c4 100644 --- a/module/core/collection_tools/src/collection/vec.rs +++ b/module/core/collection_tools/src/collection/vector.rs @@ -1,8 +1,14 @@ +#[ allow( unused_imports, clippy::wildcard_imports ) ] +use super::*; + #[ doc( inline ) ] #[ allow( unused_imports ) ] +#[ allow( clippy::pub_use ) ] pub use alloc::vec::*; + #[ doc( inline ) ] #[ allow( unused_imports ) ] +#[ allow( clippy::pub_use ) ] pub use core::slice::{ Iter, IterMut }; /// Creates a `Vec` from a list of elements. @@ -32,8 +38,8 @@ pub use core::slice::{ Iter, IterMut }; /// # Parameters /// /// - `$( $key : expr ),* $( , )?`: A comma-separated list of elements to insert into the `Vec`. -/// Each element can be of any type that implements the `Into` trait, where `T` is the -/// type stored in the `Vec`. +/// Each element can be of any type that implements the `Into` trait, where `T` is the +/// type stored in the `Vec`. /// /// # Returns /// @@ -73,7 +79,7 @@ macro_rules! vec => {{ let _cap = count!( @count $( $key ),* ); - let mut _vec = $crate::vec::Vec::with_capacity( _cap ); + let mut _vec = $crate::collection::Vec::with_capacity( _cap ); $( _vec.push( $key ); )* @@ -114,8 +120,8 @@ macro_rules! vec /// # Parameters /// /// - `$( $key : expr ),* $( , )?`: A comma-separated list of elements to insert into the `Vec`. -/// Each element can be of any type that implements the `Into` trait, where `T` is the -/// type stored in the `Vec`. +/// Each element can be of any type that implements the `Into` trait, where `T` is the +/// type stored in the `Vec`. /// /// # Returns /// @@ -167,7 +173,7 @@ macro_rules! into_vec => {{ let _cap = count!( @count $( $key ),* ); - let mut _vec = $crate::vec::Vec::with_capacity( _cap ); + let mut _vec = $crate::collection::Vec::with_capacity( _cap ); $( _vec.push( Into::into( $key ) ); )* diff --git a/module/core/collection_tools/src/lib.rs b/module/core/collection_tools/src/lib.rs index 5d0c5976a4..18b8e84037 100644 --- a/module/core/collection_tools/src/lib.rs +++ b/module/core/collection_tools/src/lib.rs @@ -3,18 +3,19 @@ #![ 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/collection_tools/latest/collection_tools/" ) ] #![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] - -#[ cfg( feature = "enabled" ) ] -#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] -extern crate alloc; +#![ allow( clippy::mod_module_files ) ] +// #[ cfg( feature = "enabled" ) ] +// #[ cfg( any( feature = "use_alloc", not( feature = "no_std" ) ) ) ] +// extern crate alloc; /// Module containing all collection macros #[ cfg( feature = "enabled" ) ] -#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] -mod collection; -#[ cfg( feature = "enabled" ) ] -#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] -pub use collection::*; +#[ cfg( any( feature = "use_alloc", not( feature = "no_std" ) ) ) ] +pub mod collection; + +// #[ cfg( feature = "enabled" ) ] +// #[ cfg( any( feature = "use_alloc", not( feature = "no_std" ) ) ) ] +// pub use collection::*; /// Namespace with dependencies. #[ cfg( feature = "enabled" ) ] @@ -29,6 +30,7 @@ pub mod dependency #[ doc( inline ) ] #[ allow( unused_imports ) ] #[ cfg( feature = "enabled" ) ] +#[ allow( clippy::pub_use ) ] pub use own::*; /// Own namespace of the module. @@ -36,10 +38,15 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { - use super::*; + // use super::*; #[ doc( inline ) ] - pub use orphan::*; + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use super::orphan::*; + + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use super::collection::own::*; } @@ -48,9 +55,16 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] pub use exposed::*; + + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use collection::orphan::*; + } /// Exposed namespace of the module. @@ -58,79 +72,37 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] pub use prelude::*; #[ doc( inline ) ] - #[ cfg( feature = "collection_constructors" ) ] - pub use crate:: - { - vec as dlist, - deque, - llist, - hset, - hmap, - bmap, - bset, - }; - - #[ doc( inline ) ] - #[ cfg( feature = "collection_into_constructors" ) ] - pub use crate:: - { - into_vec, - into_vec as into_dlist, - into_vecd, - into_llist, - into_hset, - into_hmap, - into_bmap, - into_bset, - }; - - // #[ cfg( feature = "reexports" ) ] - #[ cfg( any( feature = "use_alloc", not( feature = "no_std" ) ) ) ] - #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use crate:: - { - bmap::BTreeMap, - bset::BTreeSet, - heap::BinaryHeap, - hmap::HashMap, - hset::HashSet, - llist::LinkedList, - vec::Vec, - deque::VecDeque, - }; - - // #[ cfg( feature = "reexports" ) ] - #[ cfg( any( feature = "use_alloc", not( feature = "no_std" ) ) ) ] - #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use - { - LinkedList as Llist, - Vec as Dlist, - VecDeque as Deque, - HashMap as Map, - HashMap as Hmap, - HashSet as Set, - HashSet as Hset, - BTreeMap as Bmap, - BTreeSet as Bset, - }; - - // qqq : cover by tests presence of all containers immidiately in collection_tools::* and in collection_tools::exposed::* + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use collection::exposed::*; } /// Prelude to use essentials: `use my_module::prelude::*`. #[ cfg( feature = "enabled" ) ] +#[ cfg( any( feature = "use_alloc", not( feature = "no_std" ) ) ) ] #[ allow( unused_imports ) ] pub mod prelude { - use super::*; + use super::collection; + + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use collection::prelude::*; + } + +// pub use own::collection as xxx; +// pub use hmap as xxx; +// pub use own::HashMap as xxx; +// pub fn x() +// { +// let x : HashMap< usize, usize > = hmap!{}; +// } diff --git a/module/core/collection_tools/tests/inc/bmap.rs b/module/core/collection_tools/tests/inc/bmap.rs index af3d54dae5..113e69f810 100644 --- a/module/core/collection_tools/tests/inc/bmap.rs +++ b/module/core/collection_tools/tests/inc/bmap.rs @@ -68,7 +68,7 @@ fn iters() impl IntoIterator for MyContainer { type Item = ( i32, i32 ); - type IntoIter = the_module::bmap::IntoIter< i32, i32 >; + type IntoIter = the_module::btree_map::IntoIter< i32, i32 >; fn into_iter( self ) -> Self::IntoIter { @@ -79,7 +79,7 @@ fn iters() impl< 'a > IntoIterator for &'a MyContainer { type Item = ( &'a i32, &'a i32 ); - type IntoIter = the_module::bmap::Iter< 'a, i32, i32 >; + type IntoIter = the_module::btree_map::Iter< 'a, i32, i32 >; fn into_iter( self ) -> Self::IntoIter { diff --git a/module/core/collection_tools/tests/inc/bset.rs b/module/core/collection_tools/tests/inc/bset.rs index 2a427d0a26..9fb625bf30 100644 --- a/module/core/collection_tools/tests/inc/bset.rs +++ b/module/core/collection_tools/tests/inc/bset.rs @@ -67,7 +67,7 @@ fn iters() impl IntoIterator for MyContainer { type Item = i32; - type IntoIter = the_module::bset::IntoIter< i32 >; + type IntoIter = the_module::btree_set::IntoIter< i32 >; fn into_iter( self ) -> Self::IntoIter { @@ -78,7 +78,7 @@ fn iters() impl< 'a > IntoIterator for &'a MyContainer { type Item = &'a i32; - type IntoIter = the_module::bset::Iter< 'a, i32 >; + type IntoIter = the_module::btree_set::Iter< 'a, i32 >; fn into_iter( self ) -> Self::IntoIter { diff --git a/module/core/collection_tools/tests/inc/deque.rs b/module/core/collection_tools/tests/inc/deque.rs index 41c3d323b1..d58c72d8cc 100644 --- a/module/core/collection_tools/tests/inc/deque.rs +++ b/module/core/collection_tools/tests/inc/deque.rs @@ -50,8 +50,8 @@ fn into_constructor() exp.push_front( 3 ); assert_eq!( got, exp ); - let _got : DequeList< &str > = the_module::deque!( "b" ); - let _got : DequeList< &str > = the_module::exposed::deque!( "b" ); + let _got = the_module::deque!( "b" ); + let _got = the_module::exposed::deque!( "b" ); } @@ -66,7 +66,7 @@ fn iters() impl IntoIterator for MyContainer { type Item = i32; - type IntoIter = the_module::deque::IntoIter< i32 >; + type IntoIter = the_module::vec_deque::IntoIter< i32 >; fn into_iter( self ) -> Self::IntoIter { @@ -77,7 +77,7 @@ fn iters() impl< 'a > IntoIterator for &'a MyContainer { type Item = &'a i32; - type IntoIter = the_module::deque::Iter< 'a, i32 >; + type IntoIter = the_module::vec_deque::Iter< 'a, i32 >; fn into_iter( self ) -> Self::IntoIter { @@ -88,7 +88,7 @@ fn iters() impl< 'a > IntoIterator for &'a mut MyContainer { type Item = &'a mut i32; - type IntoIter = the_module::deque::IterMut< 'a, i32 >; + type IntoIter = the_module::vec_deque::IterMut< 'a, i32 >; fn into_iter( self ) -> Self::IntoIter { diff --git a/module/core/collection_tools/tests/inc/heap.rs b/module/core/collection_tools/tests/inc/heap.rs index a342548cfc..ad251e0b39 100644 --- a/module/core/collection_tools/tests/inc/heap.rs +++ b/module/core/collection_tools/tests/inc/heap.rs @@ -62,7 +62,7 @@ fn iters() impl IntoIterator for MyContainer { type Item = i32; - type IntoIter = the_module::heap::IntoIter< i32 >; + type IntoIter = the_module::binary_heap::IntoIter< i32 >; fn into_iter( self ) -> Self::IntoIter { @@ -73,7 +73,7 @@ fn iters() impl< 'a > IntoIterator for &'a MyContainer { type Item = &'a i32; - type IntoIter = the_module::heap::Iter< 'a, i32 >; + type IntoIter = the_module::binary_heap::Iter< 'a, i32 >; fn into_iter( self ) -> Self::IntoIter { diff --git a/module/core/collection_tools/tests/inc/hmap.rs b/module/core/collection_tools/tests/inc/hmap.rs index 629c7155a6..042b4c8653 100644 --- a/module/core/collection_tools/tests/inc/hmap.rs +++ b/module/core/collection_tools/tests/inc/hmap.rs @@ -77,7 +77,7 @@ fn iters() impl IntoIterator for MyContainer { type Item = ( i32, i32 ); - type IntoIter = the_module::hmap::IntoIter< i32, i32 >; + type IntoIter = the_module::hash_map::IntoIter< i32, i32 >; fn into_iter( self ) -> Self::IntoIter { @@ -88,7 +88,7 @@ fn iters() impl< 'a > IntoIterator for &'a MyContainer { type Item = ( &'a i32, &'a i32 ); - type IntoIter = the_module::hmap::Iter< 'a, i32, i32 >; + type IntoIter = the_module::hash_map::Iter< 'a, i32, i32 >; fn into_iter( self ) -> Self::IntoIter { @@ -99,7 +99,7 @@ fn iters() impl< 'a > IntoIterator for &'a mut MyContainer { type Item = ( &'a i32, &'a mut i32 ); - type IntoIter = the_module::hmap::IterMut< 'a, i32, i32 >; + type IntoIter = the_module::hash_map::IterMut< 'a, i32, i32 >; fn into_iter( self ) -> Self::IntoIter { diff --git a/module/core/collection_tools/tests/inc/hset.rs b/module/core/collection_tools/tests/inc/hset.rs index c844836874..b3af31cb2d 100644 --- a/module/core/collection_tools/tests/inc/hset.rs +++ b/module/core/collection_tools/tests/inc/hset.rs @@ -74,7 +74,7 @@ fn iters() impl IntoIterator for MyContainer { type Item = i32; - type IntoIter = the_module::hset::IntoIter< i32 >; + type IntoIter = the_module::hash_set::IntoIter< i32 >; fn into_iter( self ) -> Self::IntoIter { @@ -85,7 +85,7 @@ fn iters() impl< 'a > IntoIterator for &'a MyContainer { type Item = &'a i32; - type IntoIter = the_module::hset::Iter< 'a, i32 >; + type IntoIter = the_module::hash_set::Iter< 'a, i32 >; fn into_iter( self ) -> Self::IntoIter { diff --git a/module/core/collection_tools/tests/inc/llist.rs b/module/core/collection_tools/tests/inc/llist.rs index 68620e2a69..3a861b0ec2 100644 --- a/module/core/collection_tools/tests/inc/llist.rs +++ b/module/core/collection_tools/tests/inc/llist.rs @@ -67,7 +67,7 @@ fn iters() impl IntoIterator for MyContainer { type Item = i32; - type IntoIter = the_module::llist::IntoIter< i32 >; + type IntoIter = the_module::linked_list::IntoIter< i32 >; fn into_iter( self ) -> Self::IntoIter { @@ -78,7 +78,7 @@ fn iters() impl< 'a > IntoIterator for &'a MyContainer { type Item = &'a i32; - type IntoIter = the_module::llist::Iter< 'a, i32 >; + type IntoIter = the_module::linked_list::Iter< 'a, i32 >; fn into_iter( self ) -> Self::IntoIter { @@ -89,7 +89,7 @@ fn iters() impl< 'a > IntoIterator for &'a mut MyContainer { type Item = &'a mut i32; - type IntoIter = the_module::llist::IterMut< 'a, i32 >; + type IntoIter = the_module::linked_list::IterMut< 'a, i32 >; fn into_iter( self ) -> Self::IntoIter { diff --git a/module/core/collection_tools/tests/inc/mod.rs b/module/core/collection_tools/tests/inc/mod.rs index ddd10e261d..6e7902f291 100644 --- a/module/core/collection_tools/tests/inc/mod.rs +++ b/module/core/collection_tools/tests/inc/mod.rs @@ -1,5 +1,8 @@ use super::*; +#[ allow( unused_imports ) ] +use test_tools::exposed::*; + mod bmap; mod bset; mod heap; @@ -9,6 +12,7 @@ mod llist; mod vec; mod deque; +mod namespace_test; mod components; // qqq : make subdirectory for each container -- done diff --git a/module/core/collection_tools/tests/inc/namespace_test.rs b/module/core/collection_tools/tests/inc/namespace_test.rs new file mode 100644 index 0000000000..841ecac64f --- /dev/null +++ b/module/core/collection_tools/tests/inc/namespace_test.rs @@ -0,0 +1,12 @@ +use super::*; + +#[ test ] +fn exposed_main_namespace() +{ + + let _v : Vec< u32 > = the_module::collection::Vec::new(); + let _v : Vec< u32 > = the_module::exposed::collection::Vec::new(); + use the_module::exposed::*; + let _v : Vec< u32 > = collection::Vec::new(); + +} \ No newline at end of file diff --git a/module/core/collection_tools/tests/inc/vec.rs b/module/core/collection_tools/tests/inc/vec.rs index ff9480659f..5bf78631ba 100644 --- a/module/core/collection_tools/tests/inc/vec.rs +++ b/module/core/collection_tools/tests/inc/vec.rs @@ -1,6 +1,7 @@ use super::*; #[ test ] +#[ cfg( any( feature = "use_alloc", not( feature = "no_std" ) ) ) ] fn reexport() { @@ -12,7 +13,8 @@ fn reexport() let got = vec1.last().unwrap().clone(); assert_eq!( got, 2 ); - let mut vec2 : the_module::DynList< i32 > = the_module::DynList::new(); + use std::vec::Vec as DynList; + let mut vec2 : DynList< i32 > = DynList::new(); vec2.push( 1 ); vec2.push( 2 ); let got = vec2.first().unwrap().clone(); @@ -84,7 +86,7 @@ fn iters() impl IntoIterator for MyContainer { type Item = i32; - type IntoIter = the_module::vec::IntoIter< i32 >; + type IntoIter = the_module::vector::IntoIter< i32 >; // qqq : should work -- works fn into_iter( self ) -> Self::IntoIter @@ -96,7 +98,7 @@ fn iters() impl< 'a > IntoIterator for &'a MyContainer { type Item = &'a i32; - type IntoIter = the_module::vec::Iter< 'a, i32 >; + type IntoIter = the_module::vector::Iter< 'a, i32 >; fn into_iter( self ) -> Self::IntoIter { @@ -107,7 +109,7 @@ fn iters() impl< 'a > IntoIterator for &'a mut MyContainer { type Item = &'a mut i32; - type IntoIter = the_module::vec::IterMut< 'a, i32 >; + type IntoIter = the_module::vector::IterMut< 'a, i32 >; fn into_iter( self ) -> Self::IntoIter { diff --git a/module/core/collection_tools/tests/smoke_test.rs b/module/core/collection_tools/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/core/collection_tools/tests/smoke_test.rs +++ b/module/core/collection_tools/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/core/collection_tools/tests/tests.rs b/module/core/collection_tools/tests/tests.rs index a36c5debec..ec7c6d3063 100644 --- a/module/core/collection_tools/tests/tests.rs +++ b/module/core/collection_tools/tests/tests.rs @@ -1,10 +1,12 @@ -// usual tests +//! All tests. + +#![ allow( unused_imports ) ] #[ path="../../../../module/step/meta/src/module/aggregating.rs" ] mod aggregating; -#[ allow( unused_imports ) ] -use test_tools::exposed::*; +// #[ allow( unused_imports ) ] +// use test_tools::exposed::*; #[ allow( unused_imports ) ] use ::collection_tools as the_module; diff --git a/module/core/component_model/Cargo.toml b/module/core/component_model/Cargo.toml new file mode 100644 index 0000000000..00086831d7 --- /dev/null +++ b/module/core/component_model/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "component_model" +version = "0.1.0" +edition = "2021" +authors = [ + "Kostiantyn Wandalen ", +] +license = "MIT" +readme = "Readme.md" +documentation = "https://docs.rs/component_model" +repository = "https://github.com/Wandalen/wTools/tree/master/module/core/component_model" +homepage = "https://github.com/Wandalen/wTools/tree/master/module/core/component_model" +description = """ +Type-based data assignment and extraction between structs. +""" +categories = [ "algorithms", "development-tools" ] +keywords = [ "fundamental", "general-purpose" ] + +[lints] +workspace = true + +[package.metadata.docs.rs] +features = [ "full" ] +all-features = false + +[features] +default = [ "enabled" ] +full = [ "enabled" ] +enabled = [] + +[dependencies] + +[dev-dependencies] +test_tools = { workspace = true } diff --git a/module/core/component_model/License b/module/core/component_model/License new file mode 100644 index 0000000000..72c80c1308 --- /dev/null +++ b/module/core/component_model/License @@ -0,0 +1,22 @@ +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 +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/core/component_model/Readme.md b/module/core/component_model/Readme.md new file mode 100644 index 0000000000..514e282342 --- /dev/null +++ b/module/core/component_model/Readme.md @@ -0,0 +1,6 @@ + + +# Module :: component_model +[![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/Modulecomponent_modelPush.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/Modulecomponent_modelPush.yml) [![docs.rs](https://img.shields.io/docsrs/component_model?color=e3e8f0&logo=docs.rs)](https://docs.rs/component_model) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + +Type-based data assignment and extraction between structs. diff --git a/module/core/component_model/src/lib.rs b/module/core/component_model/src/lib.rs new file mode 100644 index 0000000000..8736456366 --- /dev/null +++ b/module/core/component_model/src/lib.rs @@ -0,0 +1,10 @@ + +#![ 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 = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] + +/// Function description. +#[ cfg( feature = "enabled" ) ] +pub fn f1() +{ +} diff --git a/module/core/component_model/tests/inc/basic_test.rs b/module/core/component_model/tests/inc/basic_test.rs new file mode 100644 index 0000000000..60c9a81cfb --- /dev/null +++ b/module/core/component_model/tests/inc/basic_test.rs @@ -0,0 +1,7 @@ +#[ allow( unused_imports ) ] +use super::*; + +#[ test ] +fn basic() +{ +} diff --git a/module/core/component_model/tests/inc/mod.rs b/module/core/component_model/tests/inc/mod.rs new file mode 100644 index 0000000000..7c40be710f --- /dev/null +++ b/module/core/component_model/tests/inc/mod.rs @@ -0,0 +1,4 @@ +use super::*; +use test_tools::exposed::*; + +mod basic_test; diff --git a/module/core/component_model/tests/smoke_test.rs b/module/core/component_model/tests/smoke_test.rs new file mode 100644 index 0000000000..c9b1b4daae --- /dev/null +++ b/module/core/component_model/tests/smoke_test.rs @@ -0,0 +1,13 @@ +//! Smoke testing of the package. + +#[ 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/core/component_model/tests/tests.rs b/module/core/component_model/tests/tests.rs new file mode 100644 index 0000000000..80dff0bc59 --- /dev/null +++ b/module/core/component_model/tests/tests.rs @@ -0,0 +1,8 @@ +//! All tests +#![ allow( unused_imports ) ] + +include!( "../../../../module/step/meta/src/module/terminal.rs" ); + +use component_model as the_module; +#[ cfg( feature = "enabled" ) ] +mod inc; diff --git a/module/core/component_model_meta/Cargo.toml b/module/core/component_model_meta/Cargo.toml new file mode 100644 index 0000000000..d573e33390 --- /dev/null +++ b/module/core/component_model_meta/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "component_model_meta" +version = "0.1.0" +edition = "2021" +authors = [ + "Kostiantyn Wandalen ", +] +license = "MIT" +readme = "Readme.md" +documentation = "https://docs.rs/component_model_meta" +repository = "https://github.com/Wandalen/wTools/tree/master/module/core/component_model_meta" +homepage = "https://github.com/Wandalen/wTools/tree/master/module/core/component_model_meta" +description = """ +Type-based data assignment and extraction between structs. +""" +categories = [ "algorithms", "development-tools" ] +keywords = [ "fundamental", "general-purpose" ] + +[lints] +workspace = true + +[package.metadata.docs.rs] +features = [ "full" ] +all-features = false + +[lib] +proc-macro = true + +[features] +default = [ "enabled" ] +full = [ "enabled" ] +enabled = [] + +[dependencies] + +[dev-dependencies] +test_tools = { workspace = true } diff --git a/module/core/component_model_meta/License b/module/core/component_model_meta/License new file mode 100644 index 0000000000..72c80c1308 --- /dev/null +++ b/module/core/component_model_meta/License @@ -0,0 +1,22 @@ +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 +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/core/component_model_meta/Readme.md b/module/core/component_model_meta/Readme.md new file mode 100644 index 0000000000..ede25b4b2c --- /dev/null +++ b/module/core/component_model_meta/Readme.md @@ -0,0 +1,6 @@ + + +# Module :: component_model_meta +[![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/Modulecomponent_model_metaPush.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/Modulecomponent_model_metaPush.yml) [![docs.rs](https://img.shields.io/docsrs/component_model_meta?color=e3e8f0&logo=docs.rs)](https://docs.rs/component_model_meta) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + +Type-based data assignment and extraction between structs. diff --git a/module/core/component_model_meta/src/lib.rs b/module/core/component_model_meta/src/lib.rs new file mode 100644 index 0000000000..195bb403ff --- /dev/null +++ b/module/core/component_model_meta/src/lib.rs @@ -0,0 +1,4 @@ + +#![ 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 = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] diff --git a/module/core/component_model_meta/tests/smoke_test.rs b/module/core/component_model_meta/tests/smoke_test.rs new file mode 100644 index 0000000000..c9b1b4daae --- /dev/null +++ b/module/core/component_model_meta/tests/smoke_test.rs @@ -0,0 +1,13 @@ +//! Smoke testing of the package. + +#[ 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/core/component_model_types/Cargo.toml b/module/core/component_model_types/Cargo.toml new file mode 100644 index 0000000000..9ca005b87b --- /dev/null +++ b/module/core/component_model_types/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "component_model_types" +version = "0.1.0" +edition = "2021" +authors = [ + "Kostiantyn Wandalen ", +] +license = "MIT" +readme = "Readme.md" +documentation = "https://docs.rs/component_model_types" +repository = "https://github.com/Wandalen/wTools/tree/master/module/core/component_model_types" +homepage = "https://github.com/Wandalen/wTools/tree/master/module/core/component_model_types" +description = """ +Type-based data assignment and extraction between structs. +""" +categories = [ "algorithms", "development-tools" ] +keywords = [ "fundamental", "general-purpose" ] + +[lints] +workspace = true + +[package.metadata.docs.rs] +features = [ "full" ] +all-features = false + +[features] +default = [ "enabled" ] +full = [ "enabled" ] +enabled = [] + +[dependencies] + +[dev-dependencies] +test_tools = { workspace = true } diff --git a/module/core/component_model_types/License b/module/core/component_model_types/License new file mode 100644 index 0000000000..72c80c1308 --- /dev/null +++ b/module/core/component_model_types/License @@ -0,0 +1,22 @@ +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 +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/core/component_model_types/Readme.md b/module/core/component_model_types/Readme.md new file mode 100644 index 0000000000..f985ae2b7b --- /dev/null +++ b/module/core/component_model_types/Readme.md @@ -0,0 +1,6 @@ + + +# Module :: component_model_types +[![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/Modulecomponent_model_typesPush.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/Modulecomponent_model_typesPush.yml) [![docs.rs](https://img.shields.io/docsrs/component_model_types?color=e3e8f0&logo=docs.rs)](https://docs.rs/component_model_types) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + +Type-based data assignment and extraction between structs. diff --git a/module/core/component_model_types/src/lib.rs b/module/core/component_model_types/src/lib.rs new file mode 100644 index 0000000000..8736456366 --- /dev/null +++ b/module/core/component_model_types/src/lib.rs @@ -0,0 +1,10 @@ + +#![ 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 = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] + +/// Function description. +#[ cfg( feature = "enabled" ) ] +pub fn f1() +{ +} diff --git a/module/core/component_model_types/tests/inc/basic_test.rs b/module/core/component_model_types/tests/inc/basic_test.rs new file mode 100644 index 0000000000..60c9a81cfb --- /dev/null +++ b/module/core/component_model_types/tests/inc/basic_test.rs @@ -0,0 +1,7 @@ +#[ allow( unused_imports ) ] +use super::*; + +#[ test ] +fn basic() +{ +} diff --git a/module/core/component_model_types/tests/inc/mod.rs b/module/core/component_model_types/tests/inc/mod.rs new file mode 100644 index 0000000000..7c40be710f --- /dev/null +++ b/module/core/component_model_types/tests/inc/mod.rs @@ -0,0 +1,4 @@ +use super::*; +use test_tools::exposed::*; + +mod basic_test; diff --git a/module/core/component_model_types/tests/smoke_test.rs b/module/core/component_model_types/tests/smoke_test.rs new file mode 100644 index 0000000000..c9b1b4daae --- /dev/null +++ b/module/core/component_model_types/tests/smoke_test.rs @@ -0,0 +1,13 @@ +//! Smoke testing of the package. + +#[ 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/core/component_model_types/tests/tests.rs b/module/core/component_model_types/tests/tests.rs new file mode 100644 index 0000000000..6a8c07dcf9 --- /dev/null +++ b/module/core/component_model_types/tests/tests.rs @@ -0,0 +1,8 @@ +//! All tests +#![ allow( unused_imports ) ] + +include!( "../../../../module/step/meta/src/module/terminal.rs" ); + +use component_model_types as the_module; +#[ cfg( feature = "enabled" ) ] +mod inc; diff --git a/module/core/data_type/Cargo.toml b/module/core/data_type/Cargo.toml index 0328fc7e83..91971e6b8e 100644 --- a/module/core/data_type/Cargo.toml +++ b/module/core/data_type/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "data_type" -version = "0.9.0" +version = "0.13.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -32,7 +32,6 @@ default = [ # "use_std", "enabled", "dt_either", - "dt_prelude", "dt_interval", "dt_collection", # "dt_make", @@ -43,7 +42,6 @@ full = [ # "use_std", "enabled", "dt_either", - "dt_prelude", "dt_interval", "dt_collection", # "dt_make", @@ -54,43 +52,18 @@ no_std = [] use_alloc = [ "no_std" ] enabled = [] -# dt_prelude = [ "collection_tools/reexports" ] -dt_prelude = [] # rid of maybe? dt_interval = [ "interval_adapter/enabled" ] dt_collection = [ "collection_tools/enabled" ] dt_either = [ "either" ] # qqq : for Anton : integrate all features of collection_tools into data_type and reuse tests -# dt_type_constructor = [ "type_constructor/enabled" ] -# dt_make = [ "type_constructor/make" ] -# dt_vectorized_from = [ "type_constructor/vectorized_from" ] - -# = entries - -# [lib] -# name = "data_type" -# path = "src/dt/data_type_lib.rs" - -# [[test]] -# name = "data_type_test" -# path = "tests/dt/data_type_tests.rs" -# -# [[test]] -# name = "data_type_smoke_test" -# path = "tests/_integration_test/smoke_test.rs" -# -# [[example]] -# name = "data_type_trivial" -# path = "examples/data_type_trivial/src/main.rs" - [dependencies] ## external either = { version = "~1.6", optional = true } ## internal -# type_constructor = { workspace = true } interval_adapter = { workspace = true } collection_tools = { workspace = true } diff --git a/module/core/data_type/License b/module/core/data_type/License index 6d5ef8559f..72c80c1308 100644 --- a/module/core/data_type/License +++ b/module/core/data_type/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/core/data_type/Readme.md index 62c1031498..a9ad7698f7 100644 --- a/module/core/data_type/Readme.md +++ b/module/core/data_type/Readme.md @@ -1,8 +1,8 @@ -# Module :: data_type +# Module :: `data_type` - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_data_type_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_data_type_push.yml) [![docs.rs](https://img.shields.io/docsrs/data_type?color=e3e8f0&logo=docs.rs)](https://docs.rs/data_type) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fdata_type%2Fexamples%2Fdata_type_trivial.rs,RUN_POSTFIX=--example%20data_type_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_data_type_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_data_type_push.yml) [![docs.rs](https://img.shields.io/docsrs/data_type?color=e3e8f0&logo=docs.rs)](https://docs.rs/data_type) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fdata_type%2Fexamples%2Fdata_type_trivial.rs,RUN_POSTFIX=--example%20module%2Fcore%2Fdata_type%2Fexamples%2Fdata_type_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Collection of primal data types. @@ -30,7 +30,7 @@ Macro [types](https://docs.rs/type_constructor/latest/type_constructor/types/mac ### Basic Use Case :: make - variadic constructor -Implement traits [From_0], [From1] up to MakeN to provide the interface to construct your structure with a different set of arguments. +Implement traits \[`From_0`\], \[`From1`\] up to `MakeN` to provide the interface to construct your structure with a different set of arguments. In this example structure, Struct1 could be constructed either without arguments, with a single argument, or with two arguments. - Constructor without arguments fills fields with zero. - Constructor with a single argument sets both fields to the value of the argument. diff --git a/module/core/data_type/src/dt.rs b/module/core/data_type/src/dt.rs index 69c9e80518..91b3babd3d 100644 --- a/module/core/data_type/src/dt.rs +++ b/module/core/data_type/src/dt.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { } @@ -11,6 +11,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use orphan::*; @@ -20,6 +21,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; @@ -29,6 +31,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] diff --git a/module/core/data_type/src/lib.rs b/module/core/data_type/src/lib.rs index 5f16a02ecb..c365abca4b 100644 --- a/module/core/data_type/src/lib.rs +++ b/module/core/data_type/src/lib.rs @@ -11,7 +11,6 @@ pub mod dt; /// Namespace with dependencies. - #[ cfg( feature = "enabled" ) ] pub mod dependency { @@ -33,6 +32,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use orphan::*; @@ -45,6 +45,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; @@ -54,6 +55,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] @@ -123,13 +125,13 @@ pub mod prelude pub use crate::dependency::collection_tools::prelude::*; // #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] - #[ cfg( feature = "dt_prelude" ) ] - #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use core:: - { - fmt, - }; + // #[ cfg( feature = "dt_prelude" ) ] + // #[ doc( inline ) ] + // #[ allow( unused_imports ) ] + // pub use core:: + // { + // fmt, + // }; } diff --git a/module/core/data_type/tests/smoke_test.rs b/module/core/data_type/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/core/data_type/tests/smoke_test.rs +++ b/module/core/data_type/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/core/derive_tools/Cargo.toml b/module/core/derive_tools/Cargo.toml index 3d8f47c0cc..bc4be998b8 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.27.0" +version = "0.35.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -203,7 +203,7 @@ parse-display = { version = "~0.8.2", optional = true, default-features = false ## internal derive_tools_meta = { workspace = true, optional = true, features = [] } variadic_from = { workspace = true, optional = true, features = [] } -clone_dyn = { workspace = true, optional = true, features = [ "clone_dyn_types", "clone_dyn_meta" ] } +clone_dyn = { workspace = true, optional = true, features = [ "clone_dyn_types", "derive_clone_dyn" ] } [dev-dependencies] diff --git a/module/core/derive_tools/License b/module/core/derive_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/core/derive_tools/License +++ b/module/core/derive_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/core/derive_tools/Readme.md index 746b6e4ec7..a45cb3a745 100644 --- a/module/core/derive_tools/Readme.md +++ b/module/core/derive_tools/Readme.md @@ -1,8 +1,8 @@ -# Module :: derive_tools +# Module :: `derive_tools` - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_derive_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_derive_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/derive_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/derive_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fderive_tools%2Fexamples%2Fderive_tools_trivial.rs,RUN_POSTFIX=--example%20derive_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_derive_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_derive_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/derive_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/derive_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fderive_tools%2Fexamples%2Fderive_tools_trivial.rs,RUN_POSTFIX=--example%20module%2Fcore%2Fderive_tools%2Fexamples%2Fderive_tools_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) ### Basic use-case diff --git a/module/core/derive_tools/src/lib.rs b/module/core/derive_tools/src/lib.rs index 7c3ddfdecf..f845b0c942 100644 --- a/module/core/derive_tools/src/lib.rs +++ b/module/core/derive_tools/src/lib.rs @@ -31,7 +31,7 @@ // #[ cfg( feature = "enabled" ) ] // pub mod wtools; -#[ cfg( all( feature = "derive_more" ) ) ] +#[ cfg( feature = "derive_more" ) ] #[ allow( unused_imports ) ] mod derive_more { @@ -71,7 +71,6 @@ mod derive_more pub use variadic_from as variadic; /// Namespace with dependencies. - #[ allow( unused_imports ) ] #[ cfg( feature = "enabled" ) ] pub mod dependency @@ -110,6 +109,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use orphan::*; @@ -123,6 +123,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; @@ -133,17 +134,19 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use prelude::*; - #[ cfg( all( feature = "derive_more" ) ) ] + #[ cfg( feature = "derive_more" ) ] #[ doc( inline ) ] pub use super::derive_more::*; #[ cfg( feature = "derive_strum" ) ] #[ doc( inline ) ] pub use ::strum::*; + // qqq : xxx : name all #[ cfg( any( feature = "derive_variadic_from", feature = "type_variadic_from" ) ) ] #[ doc( inline ) ] @@ -201,3 +204,44 @@ pub mod prelude pub use ::variadic_from::prelude::*; } + +// xxx : minimize dependendencies +// Adding aho-corasick v1.1.3 +// Adding cfg_aliases v0.1.1 (latest: v0.2.1) +// Adding clone_dyn v0.24.0 +// Adding clone_dyn_meta v0.24.0 +// Adding clone_dyn_types v0.23.0 +// Adding collection_tools v0.12.0 +// Adding const_format v0.2.33 +// Adding const_format_proc_macros v0.2.33 +// Adding convert_case v0.6.0 +// Adding derive_more v1.0.0 +// Adding derive_more-impl v1.0.0 +// Adding derive_tools v0.28.0 +// Adding derive_tools_meta v0.27.0 +// Adding either v1.13.0 +// Adding former_types v2.8.0 +// Adding heck v0.4.1 (latest: v0.5.0) +// Adding interval_adapter v0.24.0 +// Adding iter_tools v0.21.0 +// Adding itertools v0.11.0 (latest: v0.13.0) +// Adding macro_tools v0.40.0 +// Adding parse-display v0.8.2 (latest: v0.10.0) +// Adding parse-display-derive v0.8.2 (latest: v0.10.0) +// Adding phf v0.10.1 (latest: v0.11.2) +// Adding phf_generator v0.10.0 (latest: v0.11.2) +// Adding phf_macros v0.10.0 (latest: v0.11.2) +// Adding phf_shared v0.10.0 (latest: v0.11.2) +// Adding proc-macro-hack v0.5.20+deprecated +// Adding regex v1.10.6 +// Adding regex-automata v0.4.7 +// Adding regex-syntax v0.7.5 (latest: v0.8.4) +// Adding regex-syntax v0.8.4 +// Adding rustversion v1.0.17 +// Adding structmeta v0.2.0 (latest: v0.3.0) +// Adding structmeta-derive v0.2.0 (latest: v0.3.0) +// Adding strum v0.25.0 (latest: v0.26.3) +// Adding strum_macros v0.25.3 (latest: v0.26.4) +// Adding unicode-segmentation v1.11.0 +// Adding unicode-xid v0.2.5 +// Adding variadic_from v0.23.0 \ No newline at end of file diff --git a/module/core/derive_tools/tests/inc/new/basic_manual_test.rs b/module/core/derive_tools/tests/inc/new/basic_manual_test.rs index 993956eefa..c7f40395c6 100644 --- a/module/core/derive_tools/tests/inc/new/basic_manual_test.rs +++ b/module/core/derive_tools/tests/inc/new/basic_manual_test.rs @@ -1,15 +1,20 @@ use super::*; -#[ derive( Debug, Clone, Copy, PartialEq ) ] -pub struct IsTransparent( bool ); - -impl IsTransparent +mod mod1 { - #[ inline( always ) ] - fn new( src : bool ) -> Self + + #[ derive( Debug, Clone, Copy, PartialEq ) ] + pub struct Struct1( pub bool ); + + impl Struct1 { - Self( src ) + #[ inline( always ) ] + pub fn new( src : bool ) -> Self + { + Self( src ) + } } + } include!( "./only_test/basic.rs" ); diff --git a/module/core/derive_tools/tests/inc/new/basic_test.rs b/module/core/derive_tools/tests/inc/new/basic_test.rs index 6cea8c5a49..c96850d3de 100644 --- a/module/core/derive_tools/tests/inc/new/basic_test.rs +++ b/module/core/derive_tools/tests/inc/new/basic_test.rs @@ -1,6 +1,10 @@ use super::*; -#[ derive( Debug, Clone, Copy, PartialEq, the_module::New ) ] -pub struct IsTransparent( bool ); +mod mod1 +{ + use super::*; + #[ derive( Debug, Clone, Copy, PartialEq, the_module::New ) ] + pub struct Struct1( pub bool ); +} include!( "./only_test/basic.rs" ); diff --git a/module/core/derive_tools/tests/inc/new/multiple_named_manual_test.rs b/module/core/derive_tools/tests/inc/new/multiple_named_manual_test.rs index 839456b1a0..45a7007502 100644 --- a/module/core/derive_tools/tests/inc/new/multiple_named_manual_test.rs +++ b/module/core/derive_tools/tests/inc/new/multiple_named_manual_test.rs @@ -1,19 +1,24 @@ use super::*; -#[ derive( Debug, PartialEq, Eq ) ] -struct StructNamedFields +mod mod1 { - a : i32, - b : bool, -} -impl StructNamedFields -{ - #[ inline( always ) ] - fn new( a : i32, b : bool ) -> Self + #[ derive( Debug, PartialEq, Eq ) ] + pub struct Struct1 + { + pub a : i32, + pub b : bool, + } + + impl Struct1 { - Self{ a, b } + #[ inline( always ) ] + pub fn new( a : i32, b : bool ) -> Self + { + Self{ a, b } + } } + } include!( "./only_test/multiple_named.rs" ); diff --git a/module/core/derive_tools/tests/inc/new/multiple_named_test.rs b/module/core/derive_tools/tests/inc/new/multiple_named_test.rs index c3988146df..3e148771eb 100644 --- a/module/core/derive_tools/tests/inc/new/multiple_named_test.rs +++ b/module/core/derive_tools/tests/inc/new/multiple_named_test.rs @@ -1,11 +1,17 @@ use super::*; -#[ derive( Debug, PartialEq, Eq, the_module::New ) ] -// #[ debug ] -struct StructNamedFields +mod mod1 { - a : i32, - b : bool, + use super::*; + + #[ derive( Debug, PartialEq, Eq, the_module::New ) ] + // #[ debug ] + pub struct Struct1 + { + pub a : i32, + pub b : bool, + } + } include!( "./only_test/multiple_named.rs" ); diff --git a/module/core/derive_tools/tests/inc/new/multiple_unnamed_manual_test.rs b/module/core/derive_tools/tests/inc/new/multiple_unnamed_manual_test.rs index 08ee277851..bed9e79851 100644 --- a/module/core/derive_tools/tests/inc/new/multiple_unnamed_manual_test.rs +++ b/module/core/derive_tools/tests/inc/new/multiple_unnamed_manual_test.rs @@ -1,15 +1,20 @@ use super::*; -#[ derive( Debug, PartialEq, Eq ) ] -struct StructWithManyFields( i32, bool ); - -impl StructWithManyFields +mod mod1 { - #[ inline( always ) ] - fn new( src1 : i32, src2 : bool ) -> Self + + #[ derive( Debug, PartialEq, Eq ) ] + pub struct Struct1( pub i32, pub bool ); + + impl Struct1 { - Self( src1, src2 ) + #[ inline( always ) ] + pub fn new( src1 : i32, src2 : bool ) -> Self + { + Self( src1, src2 ) + } } + } include!( "./only_test/multiple_unnamed.rs" ); diff --git a/module/core/derive_tools/tests/inc/new/multiple_unnamed_test.rs b/module/core/derive_tools/tests/inc/new/multiple_unnamed_test.rs index 6a8f882287..8df3f37489 100644 --- a/module/core/derive_tools/tests/inc/new/multiple_unnamed_test.rs +++ b/module/core/derive_tools/tests/inc/new/multiple_unnamed_test.rs @@ -1,6 +1,12 @@ use super::*; -#[ derive( Debug, PartialEq, Eq, the_module::New ) ] -struct StructWithManyFields( i32, bool ); +mod mod1 +{ + use super::*; + + #[ derive( Debug, PartialEq, Eq, the_module::New ) ] + pub struct Struct1( pub i32, pub bool ); + +} include!( "./only_test/multiple_unnamed.rs" ); diff --git a/module/core/derive_tools/tests/inc/new/named_manual_test.rs b/module/core/derive_tools/tests/inc/new/named_manual_test.rs index 679afeec66..56f656a1c9 100644 --- a/module/core/derive_tools/tests/inc/new/named_manual_test.rs +++ b/module/core/derive_tools/tests/inc/new/named_manual_test.rs @@ -1,18 +1,23 @@ use super::*; -#[ derive( Debug, PartialEq, Eq ) ] -struct MyStruct +mod mod1 { - a : i32, -} -impl MyStruct -{ - #[ inline( always ) ] - fn new( src : i32 ) -> Self + #[ derive( Debug, PartialEq, Eq ) ] + pub struct Struct1 + { + pub a : i32, + } + + impl Struct1 { - Self{ a : src } + #[ inline( always ) ] + pub fn new( src : i32 ) -> Self + { + Self{ a : src } + } } + } include!( "./only_test/named.rs" ); diff --git a/module/core/derive_tools/tests/inc/new/named_test.rs b/module/core/derive_tools/tests/inc/new/named_test.rs index 1df964f76f..66d8fd8ac0 100644 --- a/module/core/derive_tools/tests/inc/new/named_test.rs +++ b/module/core/derive_tools/tests/inc/new/named_test.rs @@ -1,9 +1,15 @@ use super::*; -#[ derive( Debug, PartialEq, Eq, the_module::New ) ] -struct MyStruct +mod mod1 { - a : i32, + use super::*; + + #[ derive( Debug, PartialEq, Eq, the_module::New ) ] + pub struct Struct1 + { + pub a : i32, + } + } include!( "./only_test/named.rs" ); diff --git a/module/core/derive_tools/tests/inc/new/only_test/basic.rs b/module/core/derive_tools/tests/inc/new/only_test/basic.rs index 7dadc915f2..cfaf3127df 100644 --- a/module/core/derive_tools/tests/inc/new/only_test/basic.rs +++ b/module/core/derive_tools/tests/inc/new/only_test/basic.rs @@ -2,12 +2,13 @@ #[ test ] fn from_test() { + use mod1::Struct1; - let got = IsTransparent::new( true ); - let exp = IsTransparent( true ); + let got = Struct1::new( true ); + let exp = Struct1( true ); a_id!( got, exp ); - let got = IsTransparent::new( false ); - let exp = IsTransparent( false ); + let got = Struct1::new( false ); + let exp = Struct1( false ); a_id!( got, exp ); } diff --git a/module/core/derive_tools/tests/inc/new/only_test/multiple_named.rs b/module/core/derive_tools/tests/inc/new/only_test/multiple_named.rs index eebdbba992..adf93b4c93 100644 --- a/module/core/derive_tools/tests/inc/new/only_test/multiple_named.rs +++ b/module/core/derive_tools/tests/inc/new/only_test/multiple_named.rs @@ -1,7 +1,9 @@ #[ test ] fn from_named() { - let got : StructNamedFields = StructNamedFields::new( 10, true ); - let exp = StructNamedFields{ a : 10 , b : true }; + use mod1::Struct1; + + let got : Struct1 = Struct1::new( 10, true ); + let exp = Struct1{ a : 10 , b : true }; a_id!( got, exp ); } diff --git a/module/core/derive_tools/tests/inc/new/only_test/multiple_unnamed.rs b/module/core/derive_tools/tests/inc/new/only_test/multiple_unnamed.rs index 94abf3c133..f8d960c898 100644 --- a/module/core/derive_tools/tests/inc/new/only_test/multiple_unnamed.rs +++ b/module/core/derive_tools/tests/inc/new/only_test/multiple_unnamed.rs @@ -1,7 +1,9 @@ #[ test ] fn from_named() { - let got : StructWithManyFields = StructWithManyFields::new( 10, true ); - let exp = StructWithManyFields( 10 , true ); + use mod1::Struct1; + + let got : Struct1 = Struct1::new( 10, true ); + let exp = Struct1( 10 , true ); a_id!( got, exp ); } diff --git a/module/core/derive_tools/tests/inc/new/only_test/named.rs b/module/core/derive_tools/tests/inc/new/only_test/named.rs index b654961735..71804413ce 100644 --- a/module/core/derive_tools/tests/inc/new/only_test/named.rs +++ b/module/core/derive_tools/tests/inc/new/only_test/named.rs @@ -1,7 +1,9 @@ #[ test ] fn from_named() { - let got : MyStruct = MyStruct::new( 13 ); - let exp = MyStruct { a : 13 }; + use mod1::Struct1; + + let got : Struct1 = Struct1::new( 13 ); + let exp = Struct1 { a : 13 }; a_id!( got, exp ); } diff --git a/module/core/derive_tools/tests/inc/new/only_test/unit.rs b/module/core/derive_tools/tests/inc/new/only_test/unit.rs index 279908a05d..9366152172 100644 --- a/module/core/derive_tools/tests/inc/new/only_test/unit.rs +++ b/module/core/derive_tools/tests/inc/new/only_test/unit.rs @@ -1,7 +1,9 @@ #[ test ] fn from_named() { - let got : UnitStruct = UnitStruct::new(); - let exp = UnitStruct; + use mod1::Struct1; + + let got : Struct1 = Struct1::new(); + let exp = Struct1; a_id!( got, exp ); } diff --git a/module/core/derive_tools/tests/inc/new/unit_manual_test.rs b/module/core/derive_tools/tests/inc/new/unit_manual_test.rs index d5fc60e6c5..2d04912112 100644 --- a/module/core/derive_tools/tests/inc/new/unit_manual_test.rs +++ b/module/core/derive_tools/tests/inc/new/unit_manual_test.rs @@ -1,15 +1,20 @@ use super::*; -#[ derive( Debug, Clone, Copy, PartialEq ) ] -struct UnitStruct; - -impl UnitStruct +mod mod1 { - #[ inline( always ) ] - fn new() -> Self + + #[ derive( Debug, Clone, Copy, PartialEq ) ] + pub struct Struct1; + + impl Struct1 { - Self + #[ inline( always ) ] + pub fn new() -> Self + { + Self + } } + } include!( "./only_test/unit.rs" ); diff --git a/module/core/derive_tools/tests/inc/new/unit_test.rs b/module/core/derive_tools/tests/inc/new/unit_test.rs index 606df185d8..4e40c31a0e 100644 --- a/module/core/derive_tools/tests/inc/new/unit_test.rs +++ b/module/core/derive_tools/tests/inc/new/unit_test.rs @@ -1,6 +1,12 @@ use super::*; -#[ derive( Debug, Clone, Copy, PartialEq, the_module::New ) ] -struct UnitStruct; +mod mod1 +{ + use super::*; + + #[ derive( Debug, Clone, Copy, PartialEq, the_module::New ) ] + pub struct Struct1; + +} include!( "./only_test/unit.rs" ); 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/tests/smoke_test.rs b/module/core/derive_tools/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/core/derive_tools/tests/smoke_test.rs +++ b/module/core/derive_tools/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/core/derive_tools_meta/Cargo.toml b/module/core/derive_tools_meta/Cargo.toml index 3ab4e3998c..dda38632f7 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.26.0" +version = "0.34.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/derive_tools_meta/License b/module/core/derive_tools_meta/License index 6d5ef8559f..72c80c1308 100644 --- a/module/core/derive_tools_meta/License +++ b/module/core/derive_tools_meta/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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_meta/Readme.md b/module/core/derive_tools_meta/Readme.md index 53f7fba9f0..91790856f2 100644 --- a/module/core/derive_tools_meta/Readme.md +++ b/module/core/derive_tools_meta/Readme.md @@ -1,5 +1,5 @@ -# Module :: derive_tools_meta +# Module :: `derive_tools_meta` [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_derive_tools_meta_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_derive_tools_meta_push.yml) [![docs.rs](https://img.shields.io/docsrs/derive_tools_meta?color=e3e8f0&logo=docs.rs)](https://docs.rs/derive_tools_meta) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) diff --git a/module/core/derive_tools_meta/src/derive/as_ref.rs b/module/core/derive_tools_meta/src/derive/as_ref.rs index dba4eacacf..7a02d29b9b 100644 --- a/module/core/derive_tools_meta/src/derive/as_ref.rs +++ b/module/core/derive_tools_meta/src/derive/as_ref.rs @@ -1,4 +1,5 @@ +#[ allow( clippy::wildcard_imports ) ] use super::*; use macro_tools::{ attr, diag, item_struct, Result }; diff --git a/module/core/derive_tools_meta/src/derive/deref.rs b/module/core/derive_tools_meta/src/derive/deref.rs index ac2217c1c8..ad5489bd03 100644 --- a/module/core/derive_tools_meta/src/derive/deref.rs +++ b/module/core/derive_tools_meta/src/derive/deref.rs @@ -1,3 +1,4 @@ +#[ allow( clippy::wildcard_imports ) ] use super::*; use macro_tools::{ attr, diag, generic_params, Result, struct_like::StructLike }; @@ -11,7 +12,7 @@ pub fn deref( input : proc_macro::TokenStream ) -> Result< proc_macro2::TokenStr let item_name = &parsed.ident(); let ( _generics_with_defaults, generics_impl, generics_ty, generics_where ) - = generic_params::decompose( &parsed.generics() ); + = generic_params::decompose( parsed.generics() ); let result = match parsed { @@ -84,6 +85,7 @@ pub fn deref( input : proc_macro::TokenStream ) -> Result< proc_macro2::TokenStr /// } /// ``` /// +#[ allow( clippy::unnecessary_wraps ) ] fn generate_unit ( item_name : &syn::Ident, @@ -322,9 +324,9 @@ fn generate_enum None => return generate_unit ( item_name, - &generics_impl, - &generics_ty, - &generics_where, + generics_impl, + generics_ty, + generics_where, ), }; @@ -343,18 +345,18 @@ fn generate_enum generate_unit ( item_name, - &generics_impl, - &generics_ty, - &generics_where, + generics_impl, + generics_ty, + generics_where, ), syn::Fields::Unnamed( ref item ) => generate_enum_tuple_variants ( item_name, - &generics_impl, - &generics_ty, - &generics_where, + generics_impl, + generics_ty, + generics_where, &idents, item, ), @@ -363,9 +365,9 @@ fn generate_enum generate_enum_named_variants ( item_name, - &generics_impl, - &generics_ty, - &generics_where, + generics_impl, + generics_ty, + generics_where, &idents, item, ), diff --git a/module/core/derive_tools_meta/src/derive/from.rs b/module/core/derive_tools_meta/src/derive/from.rs index 585df90183..099b3f0770 100644 --- a/module/core/derive_tools_meta/src/derive/from.rs +++ b/module/core/derive_tools_meta/src/derive/from.rs @@ -1,3 +1,4 @@ +#[ allow( clippy::wildcard_imports ) ] use super::*; use macro_tools:: { @@ -10,8 +11,10 @@ use macro_tools:: }; mod field_attributes; +#[ allow( clippy::wildcard_imports ) ] use field_attributes::*; mod item_attributes; +#[ allow( clippy::wildcard_imports ) ] use item_attributes::*; // @@ -27,15 +30,15 @@ pub fn from( input : proc_macro::TokenStream ) -> Result< proc_macro2::TokenStre let item_name = &parsed.ident(); let ( _generics_with_defaults, generics_impl, generics_ty, generics_where ) - = generic_params::decompose( &parsed.generics() ); + = generic_params::decompose( parsed.generics() ); let result = match parsed { StructLike::Unit( ref item ) | StructLike::Struct( ref item ) => { - let mut field_types = item_struct::field_types( &item ); - let field_names = item_struct::field_names( &item ); + let mut field_types = item_struct::field_types( item ); + let field_names = item_struct::field_names( item ); match ( field_types.len(), field_names ) { @@ -55,7 +58,7 @@ pub fn from( input : proc_macro::TokenStream ) -> Result< proc_macro2::TokenStre &generics_ty, &generics_where, field_names.next().unwrap(), - &field_types.next().unwrap(), + field_types.next().unwrap(), ), ( 1, None ) => generate_single_field @@ -64,7 +67,7 @@ pub fn from( input : proc_macro::TokenStream ) -> Result< proc_macro2::TokenStre &generics_impl, &generics_ty, &generics_where, - &field_types.next().unwrap(), + field_types.next().unwrap(), ), ( _, Some( field_names ) ) => generate_multiple_fields_named @@ -252,7 +255,7 @@ fn generate_single_field_named } // qqq : document, add example of generated code -- done -/// Generates `From`` implementation for structs with a single named field +/// Generates `From` implementation for structs with a single named field /// /// # Example of generated code /// @@ -441,6 +444,7 @@ fn generate_multiple_fields< 'a > } // qqq : document, add example of generated code +#[ allow ( clippy::format_in_format_args ) ] fn variant_generate ( item_name : &syn::Ident, @@ -462,7 +466,7 @@ fn variant_generate return Ok( qt!{} ) } - if fields.len() <= 0 + if fields.is_empty() { return Ok( qt!{} ) } @@ -494,7 +498,7 @@ fn variant_generate { let debug = format! ( - r#" + r" #[ automatically_derived ] impl< {0} > From< {args} > for {item_name}< {1} > where @@ -506,16 +510,16 @@ where Self::{variant_name}( {use_src} ) }} }} - "#, + ", format!( "{}", qt!{ #generics_impl } ), format!( "{}", qt!{ #generics_ty } ), format!( "{}", qt!{ #generics_where } ), ); let about = format! ( -r#"derive : From +r"derive : From item : {item_name} -field : {variant_name}"#, +field : {variant_name}", ); diag::report_print( about, original_input, debug ); } diff --git a/module/core/derive_tools_meta/src/derive/from/field_attributes.rs b/module/core/derive_tools_meta/src/derive/from/field_attributes.rs index 5aeb72bd56..bd528d3c28 100644 --- a/module/core/derive_tools_meta/src/derive/from/field_attributes.rs +++ b/module/core/derive_tools_meta/src/derive/from/field_attributes.rs @@ -1,3 +1,4 @@ +#[ allow( clippy::wildcard_imports ) ] use super::*; use macro_tools:: { @@ -13,7 +14,6 @@ use former_types::Assign; /// /// Attributes of a field / variant /// - /// Represents the attributes of a struct. Aggregates all its attributes. #[ derive( Debug, Default ) ] pub struct FieldAttributes @@ -25,6 +25,7 @@ pub struct FieldAttributes impl FieldAttributes { + #[ allow( clippy::single_match ) ] pub fn from_attrs< 'a >( attrs : impl Iterator< Item = &'a syn::Attribute > ) -> Result< Self > { let mut result = Self::default(); @@ -50,7 +51,7 @@ impl FieldAttributes { let key_ident = attr.path().get_ident().ok_or_else( || error( attr ) )?; - let key_str = format!( "{}", key_ident ); + let key_str = format!( "{key_ident}" ); // attributes does not have to be known // if attr::is_standard( &key_str ) @@ -61,7 +62,7 @@ impl FieldAttributes match key_str.as_ref() { FieldAttributeConfig::KEYWORD => result.assign( FieldAttributeConfig::from_meta( attr )? ), - "debug" => {}, + // "debug" => {}, _ => {}, // _ => return Err( error( attr ) ), } @@ -95,17 +96,18 @@ impl AttributeComponent for FieldAttributeConfig { const KEYWORD : &'static str = "from"; + #[ allow( clippy::match_wildcard_for_single_variants ) ] fn from_meta( attr : &syn::Attribute ) -> Result< Self > { match attr.meta { syn::Meta::List( ref meta_list ) => { - return syn::parse2::< FieldAttributeConfig >( meta_list.tokens.clone() ); + syn::parse2::< FieldAttributeConfig >( meta_list.tokens.clone() ) }, syn::Meta::Path( ref _path ) => { - return Ok( Default::default() ) + Ok( FieldAttributeConfig::default() ) }, _ => return_syn_err!( attr, "Expects an attribute of format `#[ from( on ) ]`. \nGot: {}", qt!{ #attr } ), } @@ -178,10 +180,10 @@ impl syn::parse::Parse for FieldAttributeConfig syn_err! ( ident, - r#"Expects an attribute of format '#[ from( on ) ]' + r"Expects an attribute of format '#[ from( on ) ]' {known} But got: '{}' -"#, +", qt!{ #ident } ) }; diff --git a/module/core/derive_tools_meta/src/derive/from/item_attributes.rs b/module/core/derive_tools_meta/src/derive/from/item_attributes.rs index f60b4fbbe4..132fde24a0 100644 --- a/module/core/derive_tools_meta/src/derive/from/item_attributes.rs +++ b/module/core/derive_tools_meta/src/derive/from/item_attributes.rs @@ -1,3 +1,4 @@ +#[ allow( clippy::wildcard_imports ) ] use super::*; use macro_tools:: { @@ -11,7 +12,6 @@ use former_types::Assign; /// /// Attributes of the whole tiem /// - /// Represents the attributes of a struct. Aggregates all its attributes. #[ derive( Debug, Default ) ] pub struct ItemAttributes @@ -23,6 +23,7 @@ pub struct ItemAttributes impl ItemAttributes { + #[ allow( clippy::single_match ) ] pub fn from_attrs< 'a >( attrs : impl Iterator< Item = &'a syn::Attribute > ) -> Result< Self > { let mut result = Self::default(); @@ -48,7 +49,7 @@ impl ItemAttributes { let key_ident = attr.path().get_ident().ok_or_else( || error( attr ) )?; - let key_str = format!( "{}", key_ident ); + let key_str = format!( "{key_ident}" ); // attributes does not have to be known // if attr::is_standard( &key_str ) @@ -59,7 +60,7 @@ impl ItemAttributes match key_str.as_ref() { ItemAttributeConfig::KEYWORD => result.assign( ItemAttributeConfig::from_meta( attr )? ), - "debug" => {} + // "debug" => {} _ => {}, // _ => return Err( error( attr ) ), // attributes does not have to be known @@ -92,17 +93,18 @@ impl AttributeComponent for ItemAttributeConfig { const KEYWORD : &'static str = "from"; + #[ allow( clippy::match_wildcard_for_single_variants ) ] fn from_meta( attr : &syn::Attribute ) -> Result< Self > { match attr.meta { syn::Meta::List( ref meta_list ) => { - return syn::parse2::< ItemAttributeConfig >( meta_list.tokens.clone() ); + syn::parse2::< ItemAttributeConfig >( meta_list.tokens.clone() ) }, syn::Meta::Path( ref _path ) => { - return Ok( Default::default() ) + Ok( ItemAttributeConfig::default() ) }, _ => return_syn_err!( attr, "Expects an attribute of format `#[ from( on ) ]`. \nGot: {}", qt!{ #attr } ), } @@ -162,10 +164,10 @@ impl syn::parse::Parse for ItemAttributeConfig syn_err! ( ident, - r#"Expects an attribute of format '#[ from( off ) ]' + r"Expects an attribute of format '#[ from( off ) ]' {known} But got: '{}' -"#, +", qt!{ #ident } ) }; diff --git a/module/core/derive_tools_meta/src/derive/new.rs b/module/core/derive_tools_meta/src/derive/new.rs index 2f0341c159..5e274c3eb1 100644 --- a/module/core/derive_tools_meta/src/derive/new.rs +++ b/module/core/derive_tools_meta/src/derive/new.rs @@ -145,7 +145,7 @@ fn generate_unit #generics_where { #[ inline( always ) ] - fn new() -> Self + pub fn new() -> Self { Self } @@ -175,8 +175,8 @@ fn generate_single_field_named #generics_where { #[ inline( always ) ] - // fn new( src : i32 ) -> Self - fn new( src : #field_type ) -> Self + // pub fn new( src : i32 ) -> Self + pub fn new( src : #field_type ) -> Self { // Self { a : src } Self { #field_name: src } @@ -207,8 +207,8 @@ fn generate_single_field #generics_where { #[ inline( always ) ] - // fn new( src : bool ) -> Self - fn new( src : #field_type ) -> Self + // pub fn new( src : bool ) -> Self + pub fn new( src : #field_type ) -> Self { // Self( src ) Self( src ) @@ -248,8 +248,8 @@ fn generate_multiple_fields_named< 'a > #generics_where { #[ inline( always ) ] - // fn new( src : ( i32, bool ) ) -> Self - fn new( #( #val_type ),* ) -> Self + // pub fn new( src : ( i32, bool ) ) -> Self + pub fn new( #( #val_type ),* ) -> Self { // StructNamedFields{ a : src.0, b : src.1 } #item_name { #( #field_names ),* } @@ -287,8 +287,8 @@ fn generate_multiple_fields< 'a > #generics_where { #[ inline( always ) ] - // fn new( src : (i32, bool) ) -> Self - fn new( src : ( #( #field_types ),* ) ) -> Self + // pub fn new( src : (i32, bool) ) -> Self + pub fn new( src : ( #( #field_types ),* ) ) -> Self { // StructWithManyFields( src.0, src.1 ) #item_name( #( #params ),* ) @@ -359,7 +359,7 @@ where {2} {{ #[ inline ] - fn new( src : {args} ) -> Self + pub fn new( src : {args} ) -> Self {{ Self::{variant_name}( {use_src} ) }} @@ -388,7 +388,7 @@ field : {variant_name}"#, #generics_where { #[ inline ] - fn new( src : #args ) -> Self + pub fn new( src : #args ) -> Self { Self::#variant_name( #use_src ) } diff --git a/module/core/derive_tools_meta/src/lib.rs b/module/core/derive_tools_meta/src/lib.rs index 2b323bbdc0..c3c6657026 100644 --- a/module/core/derive_tools_meta/src/lib.rs +++ b/module/core/derive_tools_meta/src/lib.rs @@ -79,7 +79,6 @@ mod derive; /// /// The macro facilitates the conversion without additional boilerplate code. /// - #[ cfg( feature = "enabled" ) ] #[ cfg( feature = "derive_from" ) ] #[ proc_macro_derive @@ -134,7 +133,6 @@ pub fn from( input : proc_macro::TokenStream ) -> proc_macro::TokenStream /// /// The macro facilitates the conversion without additional boilerplate code. /// - #[ cfg( feature = "enabled" ) ] #[ cfg( feature = "derive_new" ) ] #[ proc_macro_derive @@ -229,7 +227,6 @@ pub fn new( input : proc_macro::TokenStream ) -> proc_macro::TokenStream /// } /// } /// ``` - #[ cfg( feature = "enabled" ) ] #[ cfg( feature = "derive_inner_from" ) ] #[ proc_macro_derive( InnerFrom, attributes( debug ) ) ] @@ -270,7 +267,6 @@ pub fn inner_from( input : proc_macro::TokenStream ) -> proc_macro::TokenStream /// } /// } /// ``` - #[ cfg( feature = "enabled" ) ] #[ cfg( feature = "derive_deref" ) ] #[ proc_macro_derive( Deref, attributes( debug ) ) ] @@ -330,7 +326,6 @@ pub fn deref( input : proc_macro::TokenStream ) -> proc_macro::TokenStream /// } /// /// ``` - #[ cfg( feature = "enabled" ) ] #[ cfg( feature = "derive_deref_mut" ) ] #[ proc_macro_derive( DerefMut, attributes( debug ) ) ] @@ -345,7 +340,7 @@ pub fn deref_mut( input : proc_macro::TokenStream ) -> proc_macro::TokenStream } /// -/// Derive macro to implement AsRef when-ever it's possible to do automatically. +/// Derive macro to implement `AsRef` when-ever it's possible to do automatically. /// /// ### Sample :: struct instead of macro. /// @@ -369,7 +364,6 @@ pub fn deref_mut( input : proc_macro::TokenStream ) -> proc_macro::TokenStream /// } /// } /// ``` - #[ cfg( feature = "enabled" ) ] #[ cfg( feature = "derive_as_ref" ) ] #[ proc_macro_derive( AsRef, attributes( debug ) ) ] @@ -409,7 +403,6 @@ pub fn as_ref( input : proc_macro::TokenStream ) -> proc_macro::TokenStream /// } /// /// ``` - #[ cfg( feature = "enabled" ) ] #[ cfg( feature = "derive_as_mut" ) ] #[ proc_macro_derive( AsMut, attributes( debug ) ) ] @@ -514,7 +507,6 @@ pub fn as_mut( input : proc_macro::TokenStream ) -> proc_macro::TokenStream /// } /// ``` /// - #[ cfg( feature = "enabled" ) ] #[ cfg( feature = "derive_variadic_from" ) ] #[ proc_macro_derive( VariadicFrom, attributes( debug ) ) ] @@ -619,7 +611,6 @@ pub fn derive_not( input : proc_macro::TokenStream ) -> proc_macro::TokenStream /// /// The macro facilitates the addition of the `PhantomData` field without additional boilerplate code. /// - #[ cfg( feature = "enabled" ) ] #[ cfg ( feature = "derive_phantom" ) ] #[ proc_macro_attribute ] @@ -675,7 +666,6 @@ pub fn phantom( _attr: proc_macro::TokenStream, input : proc_macro::TokenStream /// }; /// ``` /// - #[ cfg( feature = "enabled" ) ] #[ cfg( feature = "derive_index" ) ] #[ proc_macro_derive @@ -745,7 +735,6 @@ pub fn derive_index( input : proc_macro::TokenStream ) -> proc_macro::TokenStrea /// }; /// ``` /// - #[ cfg( feature = "enabled" ) ] #[ cfg( feature = "derive_index_mut" ) ] #[ proc_macro_derive diff --git a/module/core/diagnostics_tools/Cargo.toml b/module/core/diagnostics_tools/Cargo.toml index c2b7d7e994..6103e7fb9c 100644 --- a/module/core/diagnostics_tools/Cargo.toml +++ b/module/core/diagnostics_tools/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "diagnostics_tools" -version = "0.8.0" +version = "0.10.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -43,12 +43,12 @@ no_std = [] use_alloc = [ "no_std" ] enabled = [] -diagnostics_runtime_assertions = [ "pretty_assertions" ] # run-time assertions +diagnostics_runtime_assertions = [ "dep:pretty_assertions" ] # run-time assertions diagnostics_compiletime_assertions = [] # compile-time assertions diagnostics_memory_layout = [] # [dependencies] -pretty_assertions = { version = "~1.4.0", optional = true } +pretty_assertions = { workspace = true, optional = true } [dev-dependencies] test_tools = { workspace = true } diff --git a/module/core/diagnostics_tools/License b/module/core/diagnostics_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/core/diagnostics_tools/License +++ b/module/core/diagnostics_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/core/diagnostics_tools/Readme.md index d41d9b75a5..94cd00fc4a 100644 --- a/module/core/diagnostics_tools/Readme.md +++ b/module/core/diagnostics_tools/Readme.md @@ -2,7 +2,7 @@ # Module :: diagnostics_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_diagnostics_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_diagnostics_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/diagnostics_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/diagnostics_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fdiagnostics_tools%2Fexamples%2Fdiagnostics_tools_trivial.rs,RUN_POSTFIX=--example%20diagnostics_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_diagnostics_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_diagnostics_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/diagnostics_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/diagnostics_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fdiagnostics_tools%2Fexamples%2Fdiagnostics_tools_trivial.rs,RUN_POSTFIX=--example%20module%2Fcore%2Fdiagnostics_tools%2Fexamples%2Fdiagnostics_tools_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Diagnostics tools. diff --git a/module/core/diagnostics_tools/src/diag/rta.rs b/module/core/diagnostics_tools/src/diag/rta.rs index 27f8d991ec..4bd27b3bba 100644 --- a/module/core/diagnostics_tools/src/diag/rta.rs +++ b/module/core/diagnostics_tools/src/diag/rta.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { @@ -289,4 +289,3 @@ pub mod prelude }; } - diff --git a/module/core/diagnostics_tools/src/lib.rs b/module/core/diagnostics_tools/src/lib.rs index a3415c710e..8ed4ccb486 100644 --- a/module/core/diagnostics_tools/src/lib.rs +++ b/module/core/diagnostics_tools/src/lib.rs @@ -31,7 +31,6 @@ pub mod own #[ doc( inline ) ] pub use orphan::*; #[ doc( inline ) ] - #[ allow( unused_imports ) ] pub use super::diag::orphan::*; } @@ -54,7 +53,6 @@ pub mod exposed #[ doc( inline ) ] pub use prelude::*; #[ doc( inline ) ] - #[ allow( unused_imports ) ] pub use super::diag::exposed::*; } @@ -65,6 +63,5 @@ pub mod prelude { use super::*; #[ doc( inline ) ] - #[ allow( unused_imports ) ] pub use super::diag::prelude::*; } diff --git a/module/core/diagnostics_tools/tests/diagnostics_tests.rs b/module/core/diagnostics_tools/tests/all_tests.rs similarity index 61% rename from module/core/diagnostics_tools/tests/diagnostics_tests.rs rename to module/core/diagnostics_tools/tests/all_tests.rs index 46138a3acb..cce190c203 100644 --- a/module/core/diagnostics_tools/tests/diagnostics_tests.rs +++ b/module/core/diagnostics_tools/tests/all_tests.rs @@ -1,3 +1,5 @@ +//! All tests. + // #![ deny( rust_2018_idioms ) ] // #![ deny( missing_debug_implementations ) ] // #![ deny( missing_docs ) ] @@ -5,11 +7,9 @@ // #![ cfg_attr( feature = "type_name_of_val", feature( type_name_of_val ) ) ] // #![ feature( trace_macros ) ] -#[ allow( unused_imports ) ] -use diagnostics_tools as the_module; -#[ allow( unused_imports ) ] -use test_tools::exposed::*; -// #[ path="../../../../module/step/meta/src/module/terminal.rs" ] -// mod terminal; +#![ allow( unused_imports ) ] +#[ path="../../../../module/step/meta/src/module/terminal.rs" ] +mod terminal; +use diagnostics_tools as the_module; mod inc; diff --git a/module/core/diagnostics_tools/tests/inc/layout_test.rs b/module/core/diagnostics_tools/tests/inc/layout_test.rs index 37cd393f46..c0b92f743f 100644 --- a/module/core/diagnostics_tools/tests/inc/layout_test.rs +++ b/module/core/diagnostics_tools/tests/inc/layout_test.rs @@ -64,10 +64,9 @@ tests_impls! } -#[ path = "../../../../step/meta/src/module/aggregating.rs" ] -mod aggregating; - -use crate::only_for_terminal_module; +// #[ path = "../../../../step/meta/src/module/aggregating.rs" ] +// mod aggregating; +// use crate::only_for_terminal_module; only_for_terminal_module! { diff --git a/module/core/diagnostics_tools/tests/inc/mod.rs b/module/core/diagnostics_tools/tests/inc/mod.rs index 68dc070886..4016c1dc8a 100644 --- a/module/core/diagnostics_tools/tests/inc/mod.rs +++ b/module/core/diagnostics_tools/tests/inc/mod.rs @@ -1,4 +1,5 @@ use super::*; +use test_tools::exposed::*; #[ cfg( any( feature = "diagnostics_runtime_assertions", feature = "diagnostics_runtime_assertions" ) ) ] mod cta_test; diff --git a/module/core/diagnostics_tools/tests/smoke_test.rs b/module/core/diagnostics_tools/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/core/diagnostics_tools/tests/smoke_test.rs +++ b/module/core/diagnostics_tools/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/core/error_tools/Cargo.toml b/module/core/error_tools/Cargo.toml index 1ea9fe9535..670b1fd2b5 100644 --- a/module/core/error_tools/Cargo.toml +++ b/module/core/error_tools/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "error_tools" -version = "0.16.0" +version = "0.20.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -48,8 +48,9 @@ error_untyped = [ "anyhow" ] # = entry [dependencies] -anyhow = { version = "~1.0", optional = true } -thiserror = { version = "~1.0", optional = true } +anyhow = { workspace = true, optional = true } +thiserror = { workspace = true, optional = true } [dev-dependencies] test_tools = { workspace = true } +# xxx : qqq : review \ No newline at end of file diff --git a/module/core/error_tools/License b/module/core/error_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/core/error_tools/License +++ b/module/core/error_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/core/error_tools/Readme.md index 727ed9d8b7..3fabec16b9 100644 --- a/module/core/error_tools/Readme.md +++ b/module/core/error_tools/Readme.md @@ -1,8 +1,8 @@ -# Module :: error_tools +# Module :: `error_tools` - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_error_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_error_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/error_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/error_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Ferror_tools%2Fexamples%2Ferror_tools_trivial.rs,RUN_POSTFIX=--example%20error_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_error_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_error_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/error_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/error_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Ferror_tools%2Fexamples%2Ferror_tools_trivial.rs,RUN_POSTFIX=--example%20module%2Fcore%2Ferror_tools%2Fexamples%2Ferror_tools_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Basic exceptions handling mechanism. diff --git a/module/core/error_tools/examples/error_tools_trivial.rs b/module/core/error_tools/examples/error_tools_trivial.rs index cc6fc29f24..e6ddd65432 100644 --- a/module/core/error_tools/examples/error_tools_trivial.rs +++ b/module/core/error_tools/examples/error_tools_trivial.rs @@ -17,5 +17,5 @@ fn main() fn f1() -> error_tools::untyped::Result< () > { let _read = std::fs::read_to_string( "Cargo.toml" )?; - Err( error_tools::BasicError::new( "Some error" ).into() ) + Err( error_tools::untyped::format_err!( "Some error" ) ) } diff --git a/module/core/error_tools/src/error.rs b/module/core/error_tools/src/error.rs deleted file mode 100644 index 730f9c477c..0000000000 --- a/module/core/error_tools/src/error.rs +++ /dev/null @@ -1,265 +0,0 @@ -/// Internal namespace. -mod private -{ - pub use std::error::Error as ErrorTrait; - - /// This trait allows adding extra context or information to an error, creating a tuple of the additional - /// context and the original error. This is particularly useful for error handling when you want to include - /// more details in the error without losing the original error value. - /// - /// The `ErrWith` trait provides methods to wrap an error with additional context, either by using a closure - /// that generates the context or by directly providing the context. - /// - /// ``` - pub trait ErrWith< ReportErr, ReportOk, E > - { - /// Takes a closure `f` that returns a value of type `ReportErr`, and uses it to wrap an error of type `(ReportErr, E)` - /// in the context of a `Result` of type `ReportOk`. - /// - /// This method allows you to add additional context to an error by providing a closure that generates the context. - /// - /// # Arguments - /// - /// * `f` - A closure that returns the additional context of type `ReportErr`. - /// - /// # Returns - /// - /// A `Result` of type `ReportOk` if the original result is `Ok`, or a tuple `(ReportErr, E)` containing the additional - /// context and the original error if the original result is `Err`. - /// - /// # Example - /// - /// ```rust - /// use error_tools::ErrWith; - /// let result : Result< (), std::io::Error > = Err( std::io::Error::new( std::io::ErrorKind::Other, "an error occurred" ) ); - /// let result_with_context : Result< (), ( &str, std::io::Error ) > = result.err_with( || "additional context" ); - /// ``` - fn err_with< F >( self, f : F ) -> std::result::Result< ReportOk, ( ReportErr, E ) > - where - F : FnOnce() -> ReportErr; - - /// Takes a reference to a `ReportErr` value and uses it to wrap an error of type `(ReportErr, E)` - /// in the context of a `Result` of type `ReportOk`. - /// - /// This method allows you to add additional context to an error by providing a reference to the context. - /// - /// # Arguments - /// - /// * `report` - A reference to the additional context of type `ReportErr`. - /// - /// # Returns - /// - /// A `Result` of type `ReportOk` if the original result is `Ok`, or a tuple `(ReportErr, E)` containing the additional - /// context and the original error if the original result is `Err`. - /// - /// # Example - /// - /// ```rust - /// use error_tools::ErrWith; - /// let result : Result< (), std::io::Error > = Err( std::io::Error::new( std::io::ErrorKind::Other, "an error occurred" ) ); - /// let report = "additional context"; - /// let result_with_report : Result< (), ( &str, std::io::Error ) > = result.err_with_report( &report ); - /// ``` - fn err_with_report( self, report : &ReportErr ) -> std::result::Result< ReportOk, ( ReportErr, E ) > - where - ReportErr : Clone; - - } - - impl< ReportErr, ReportOk, E, IntoError > ErrWith< ReportErr, ReportOk, E > - for std::result::Result< ReportOk, IntoError > - where - IntoError : Into< E >, - { - - fn err_with< F >( self, f : F ) -> std::result::Result< ReportOk, ( ReportErr, E ) > - where - F : FnOnce() -> ReportErr, - { - self.map_err( | e | ( f(), e.into() ) ) - } - - #[ inline( always ) ] - fn err_with_report( self, report : &ReportErr ) -> std::result::Result< ReportOk, ( ReportErr, E ) > - where - ReportErr : Clone, - Self : Sized, - { - self.map_err( | e | ( report.clone(), e.into() ) ) - } - - } - - /// A type alias for a `Result` that contains an error which is a tuple of a report and an original error. - /// - /// This is useful when you want to report additional information along with an error. The `ResultWithReport` type - /// helps in defining such results more concisely. - pub type ResultWithReport< Report, Error > = Result< Report, ( Report, Error ) >; - - /// - /// Macro to generate an error descriptor. - /// - /// ### Basic use-case. - /// ```rust - /// # use error_tools::{ BasicError, err }; - /// fn f1() -> BasicError - /// { - /// return err!( "No attr" ); - /// } - /// ``` - /// - - #[ macro_export ] - macro_rules! err - { - - ( $msg : expr ) => - { - $crate::BasicError::new( $msg ).into() - }; - ( $msg : expr, $( $arg : expr ),+ $(,)? ) => - { - $crate::BasicError::new( format!( $msg, $( $arg ),+ ) ).into() - }; - - } - - /// - /// Macro to return an Err( error ) generating error descriptor. - /// - /// ### Basic use-case. - /// ```rust - /// # use error_tools::{ BasicError, return_err }; - /// fn f1() -> Result< (), BasicError > - /// { - /// return_err!( "No attr" ); - /// } - /// ``` - /// - - #[ macro_export ] - macro_rules! return_err - { - - ( $msg : expr ) => - { - return Result::Err( $crate::err!( $msg ) ) - }; - ( $msg : expr, $( $arg : expr ),+ $(,)? ) => - { - return Result::Err( $crate::err!( $msg, $( $arg ),+ ) ) - }; - - } - - // zzz : review - - /// baic implementation of generic BasicError - - #[ derive( core::fmt::Debug, core::clone::Clone, core::cmp::PartialEq, core::cmp::Eq ) ] - pub struct BasicError - { - msg : String, - } - - impl BasicError - { - /// Constructor expecting message with description. - pub fn new< Msg : Into< String > >( msg : Msg ) -> BasicError - { - BasicError { msg : msg.into() } - } - /// Message with description getter. - pub fn msg( &self ) -> &String - { - &self.msg - } - } - - impl core::fmt::Display for BasicError - { - fn fmt(&self, f: &mut core::fmt::Formatter< '_ >) -> core::fmt::Result - { - write!( f, "{}", self.msg ) - } - } - - impl ErrorTrait for BasicError - { - fn description( &self ) -> &str - { - &self.msg - } - } - - impl< T > From< BasicError > for Result< T, BasicError > - { - /// Returns the argument unchanged. - #[ inline( always ) ] - fn from( src : BasicError ) -> Self - { - Result::Err( src ) - } - } - - pub use err; - pub use return_err; - - // qqq : write standard mod interface without using mod_interface /* aaa : Dmytro : added to each library file */ -} - -#[ 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::*; -} - -/// Shared with parent 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 private:: - { - ErrWith, - ResultWithReport, - }; - - #[ doc( inline ) ] - pub use prelude::*; -} - -/// Prelude to use essentials: `use my_module::prelude::*`. -#[ allow( unused_imports ) ] -pub mod prelude -{ - use super::*; - - #[ doc( inline ) ] - pub use private:: - { - err, - return_err, - ErrorTrait, - BasicError, - }; - -} diff --git a/module/core/error_tools/src/assert.rs b/module/core/error_tools/src/error/assert.rs similarity index 74% rename from module/core/error_tools/src/assert.rs rename to module/core/error_tools/src/error/assert.rs index 50c72b0bdf..8a8145e755 100644 --- a/module/core/error_tools/src/assert.rs +++ b/module/core/error_tools/src/error/assert.rs @@ -1,10 +1,9 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { /// - /// Macro asserts that two expressions are identical to each other. Unlike std::assert_eq it is removed from a release build. + /// Macro asserts that two expressions are identical to each other. Unlike `std::assert_eq` it is removed from a release build. /// - #[ macro_export ] macro_rules! debug_assert_id { @@ -58,8 +57,7 @@ mod private // }}; } - /// Macro asserts that two expressions are identical to each other. Unlike std::assert_eq it is removed from a release build. Alias of debug_assert_id. - + /// Macro asserts that two expressions are identical to each other. Unlike `std::assert_eq` it is removed from a release build. Alias of `debug_assert_id`. #[ macro_export ] macro_rules! debug_assert_identical { @@ -70,8 +68,7 @@ mod private }; } - /// Macro asserts that two expressions are not identical to each other. Unlike std::assert_eq it is removed from a release build. - + /// Macro asserts that two expressions are not identical to each other. Unlike `std::assert_eq` it is removed from a release build. #[ macro_export ] macro_rules! debug_assert_ni { @@ -83,8 +80,7 @@ mod private }; } - /// Macro asserts that two expressions are not identical to each other. Unlike std::assert_eq it is removed from a release build. - + /// Macro asserts that two expressions are not identical to each other. Unlike `std::assert_eq` it is removed from a release build. #[ macro_export ] macro_rules! debug_assert_not_identical { @@ -108,9 +104,13 @@ mod private // }; // } + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] pub use debug_assert_id; + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] pub use debug_assert_identical; + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] pub use debug_assert_ni; + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] pub use debug_assert_not_identical; } @@ -118,21 +118,26 @@ mod private #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] pub use orphan::*; } #[ doc( inline ) ] #[ allow( unused_imports ) ] +#[ allow( clippy::pub_use ) ] pub use own::*; /// Shared with parent namespace of the module #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] pub use exposed::*; } @@ -140,8 +145,10 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] pub use prelude::*; } @@ -149,9 +156,14 @@ pub mod exposed #[ allow( unused_imports ) ] pub mod prelude { + #[ allow( clippy::wildcard_imports ) ] use super::*; + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] pub use private::debug_assert_id; + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] pub use private::debug_assert_identical; + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] pub use private::debug_assert_ni; + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] pub use private::debug_assert_not_identical; } diff --git a/module/core/error_tools/src/error/mod.rs b/module/core/error_tools/src/error/mod.rs new file mode 100644 index 0000000000..46d48b7c35 --- /dev/null +++ b/module/core/error_tools/src/error/mod.rs @@ -0,0 +1,373 @@ +/// Define a private namespace for all its items. +mod private +{ + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use core::error::Error as ErrorTrait; + + /// This trait allows adding extra context or information to an error, creating a tuple of the additional + /// context and the original error. This is particularly useful for error handling when you want to include + /// more details in the error without losing the original error value. + /// + /// The `ErrWith` trait provides methods to wrap an error with additional context, either by using a closure + /// that generates the context or by directly providing the context. + /// + pub trait ErrWith< ReportErr, ReportOk, E > + { + /// Takes a closure `f` that returns a value of type `ReportErr`, and uses it to wrap an error of type `(ReportErr, E)` + /// in the context of a `Result` of type `ReportOk`. + /// + /// This method allows you to add additional context to an error by providing a closure that generates the context. + /// + /// # Arguments + /// + /// * `f` - A closure that returns the additional context of type `ReportErr`. + /// + /// # Returns + /// + /// A `Result` of type `ReportOk` if the original result is `Ok`, or a tuple `(ReportErr, E)` containing the additional + /// context and the original error if the original result is `Err`. + /// + /// # Errors + /// + /// qqq: errors + /// + /// # Example + /// + /// ```rust + /// use error_tools::ErrWith; + /// let result : Result< (), std::io::Error > = Err( std::io::Error::new( std::io::ErrorKind::Other, "an error occurred" ) ); + /// let result_with_context : Result< (), ( &str, std::io::Error ) > = result.err_with( || "additional context" ); + /// ``` + fn err_with< F >( self, f : F ) -> core::result::Result< ReportOk, ( ReportErr, E ) > + where + F : FnOnce() -> ReportErr; + + /// Takes a reference to a `ReportErr` value and uses it to wrap an error of type `(ReportErr, E)` + /// in the context of a `Result` of type `ReportOk`. + /// + /// This method allows you to add additional context to an error by providing a reference to the context. + /// + /// # Arguments + /// + /// * `report` - A reference to the additional context of type `ReportErr`. + /// + /// # Returns + /// + /// A `Result` of type `ReportOk` if the original result is `Ok`, or a tuple `(ReportErr, E)` containing the additional + /// context and the original error if the original result is `Err`. + /// + /// # Errors + /// + /// qqq: Errors + /// + /// # Example + /// + /// ```rust + /// use error_tools::ErrWith; + /// let result : Result< (), std::io::Error > = Err( std::io::Error::new( std::io::ErrorKind::Other, "an error occurred" ) ); + /// let report = "additional context"; + /// let result_with_report : Result< (), ( &str, std::io::Error ) > = result.err_with_report( &report ); + /// ``` + fn err_with_report( self, report : &ReportErr ) -> core::result::Result< ReportOk, ( ReportErr, E ) > + where + ReportErr : Clone; + + } + + impl< ReportErr, ReportOk, E, IntoError > ErrWith< ReportErr, ReportOk, E > + for core::result::Result< ReportOk, IntoError > + where + IntoError : Into< E >, + { + + #[ allow( clippy::implicit_return, clippy::min_ident_chars ) ] + #[ inline ] + fn err_with< F >( self, f : F ) -> core::result::Result< ReportOk, ( ReportErr, E ) > + where + F : FnOnce() -> ReportErr, + { + self.map_err( | error | ( f(), error.into() ) ) + } + + #[ inline( always ) ] + #[ allow( clippy::implicit_return ) ] + fn err_with_report( self, report : &ReportErr ) -> core::result::Result< ReportOk, ( ReportErr, E ) > + where + ReportErr : Clone, + Self : Sized, + { + self.map_err( | error | ( report.clone(), error.into() ) ) + } + + } + + /// A type alias for a `Result` that contains an error which is a tuple of a report and an original error. + /// + /// This is useful when you want to report additional information along with an error. The `ResultWithReport` type + /// helps in defining such results more concisely. + pub type ResultWithReport< Report, Error > = Result< Report, ( Report, Error ) >; + +// /// +// /// Macro to generate an error descriptor. +// /// +// /// ### Basic use-case. +// /// ```rust +// /// # use error_tools::{ BasicError, err }; +// /// fn f1() -> BasicError +// /// { +// /// return err!( "No attr" ); +// /// } +// /// ``` +// /// +// +// #[ macro_export ] +// macro_rules! err +// { +// +// ( $msg : expr ) => +// { +// $crate::BasicError::new( $msg ).into() +// }; +// ( $msg : expr, $( $arg : expr ),+ $(,)? ) => +// { +// $crate::BasicError::new( format!( $msg, $( $arg ),+ ) ).into() +// }; +// +// } +// +// /// +// /// Macro to return an Err( error ) generating error descriptor. +// /// +// /// ### Basic use-case. +// /// ```rust +// /// # use error_tools::{ BasicError, return_err }; +// /// fn f1() -> Result< (), BasicError > +// /// { +// /// return_err!( "No attr" ); +// /// } +// /// ``` +// /// +// +// #[ macro_export ] +// macro_rules! return_err +// { +// +// ( $msg : expr ) => +// { +// return Result::Err( $crate::err!( $msg ) ) +// }; +// ( $msg : expr, $( $arg : expr ),+ $(,)? ) => +// { +// return Result::Err( $crate::err!( $msg, $( $arg ),+ ) ) +// }; +// +// } +// +// // zzz : review +// // xxx : rid of +// +// /// baic implementation of generic BasicError +// +// #[ derive( core::fmt::Debug, core::clone::Clone, core::cmp::PartialEq, core::cmp::Eq ) ] +// pub struct BasicError +// { +// msg : String, +// } +// +// impl BasicError +// { +// /// Constructor expecting message with description. +// pub fn new< Msg : Into< String > >( msg : Msg ) -> BasicError +// { +// BasicError { msg : msg.into() } +// } +// /// Message with description getter. +// pub fn msg( &self ) -> &String +// { +// &self.msg +// } +// } +// +// impl core::fmt::Display for BasicError +// { +// fn fmt(&self, f: &mut core::fmt::Formatter< '_ >) -> core::fmt::Result +// { +// write!( f, "{}", self.msg ) +// } +// } +// +// impl ErrorTrait for BasicError +// { +// fn description( &self ) -> &str +// { +// &self.msg +// } +// } +// +// impl< T > From< BasicError > for Result< T, BasicError > +// { +// /// Returns the argument unchanged. +// #[ inline( always ) ] +// fn from( src : BasicError ) -> Self +// { +// Result::Err( src ) +// } +// } +// +// pub use err; +// pub use return_err; + + // qqq : write standard mod interface without using mod_interface /* aaa : Dmytro : added to each library file */ +} + +/// Assertions. +#[ cfg( feature = "enabled" ) ] +pub mod assert; + +#[ cfg( feature = "enabled" ) ] +#[ cfg( feature = "error_typed" ) ] +/// Typed exceptions handling mechanism. +pub mod typed; + +#[ cfg( feature = "enabled" ) ] +#[ cfg( feature = "error_untyped" ) ] +/// Untyped exceptions handling mechanism. +pub mod untyped; + +#[ cfg( feature = "enabled" ) ] +#[ doc( inline ) ] +#[ allow( unused_imports ) ] +#[ allow( clippy::pub_use ) ] +pub use own::*; + +/// Own namespace of the module. +#[ cfg( feature = "enabled" ) ] +#[ allow( unused_imports ) ] +pub mod own +{ + #[ allow( clippy::wildcard_imports ) ] + use super::*; + + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use orphan::*; + + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use assert::orphan::*; + + #[ cfg( feature = "error_untyped" ) ] + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use untyped::orphan::*; + + #[ cfg( feature = "error_typed" ) ] + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use typed::orphan::*; + + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use private:: + { + // err, + // return_err, + ErrorTrait, + // BasicError, + }; + + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use super::assert; + #[ cfg( feature = "error_typed" ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use super::typed; + #[ cfg( feature = "error_untyped" ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use super::untyped; + +} + +/// Shared with parent namespace of the module +#[ cfg( feature = "enabled" ) ] +#[ allow( unused_imports ) ] +pub mod orphan +{ + #[ allow( clippy::wildcard_imports ) ] + use super::*; + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use exposed::*; +} + +/// Exposed namespace of the module. +#[ cfg( feature = "enabled" ) ] +#[ allow( unused_imports ) ] +pub mod exposed +{ + #[ allow( clippy::wildcard_imports ) ] + use super::*; + + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use prelude::*; + + // Expose itself. + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use super::super::error; + + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use private:: + { + ErrWith, + ResultWithReport, + }; + + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use assert::exposed::*; + + #[ cfg( feature = "error_untyped" ) ] + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use untyped::exposed::*; + + #[ cfg( feature = "error_typed" ) ] + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use typed::exposed::*; + +} + +/// Prelude to use essentials: `use my_module::prelude::*`. +#[ cfg( feature = "enabled" ) ] +#[ allow( unused_imports ) ] +pub mod prelude +{ + #[ allow( clippy::wildcard_imports ) ] + use super::*; + + // #[ doc( inline ) ] + // pub use private:: + // { + // // err, + // // return_err, + // ErrorTrait, + // // BasicError, + // }; + + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use assert::prelude::*; + + #[ cfg( feature = "error_untyped" ) ] + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use untyped::prelude::*; + + #[ cfg( feature = "error_typed" ) ] + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use typed::prelude::*; + +} diff --git a/module/core/error_tools/src/error/typed.rs b/module/core/error_tools/src/error/typed.rs new file mode 100644 index 0000000000..0845523e35 --- /dev/null +++ b/module/core/error_tools/src/error/typed.rs @@ -0,0 +1,73 @@ +/// Define a private namespace for all its items. +mod private +{ + +} + +#[ doc( inline ) ] +#[ allow( unused_imports ) ] +#[ allow( clippy::pub_use ) ] +pub use own::*; + +/// Own namespace of the module. +#[ allow( unused_imports ) ] +pub mod own +{ + #[ allow( clippy::wildcard_imports ) ] + use super::*; + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use orphan::*; +} + +/// Shared with parent namespace of the module +#[ allow( unused_imports ) ] +pub mod orphan +{ + #[ allow( clippy::wildcard_imports ) ] + use super::*; + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use super::super::typed; + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use super::super::typed as for_lib; + + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use exposed::*; + + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + #[ allow( clippy::pub_use ) ] + pub use ::thiserror:: + { + Error, + }; + +} + +/// Exposed namespace of the module. +#[ allow( unused_imports ) ] +pub mod exposed +{ + #[ allow( clippy::wildcard_imports ) ] + use super::*; + + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use prelude::*; + +} + +/// Prelude to use essentials: `use my_module::prelude::*`. +#[ allow( unused_imports ) ] +pub mod prelude +{ + #[ allow( clippy::wildcard_imports ) ] + use super::*; + + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + #[ allow( clippy::pub_use ) ] + pub use thiserror; + +} \ No newline at end of file diff --git a/module/core/error_tools/src/error/untyped.rs b/module/core/error_tools/src/error/untyped.rs new file mode 100644 index 0000000000..8a57019dc0 --- /dev/null +++ b/module/core/error_tools/src/error/untyped.rs @@ -0,0 +1,84 @@ +/// Define a private namespace for all its items. +mod private +{ + +} + +#[ doc( inline ) ] +#[ allow( unused_imports ) ] +#[ allow( clippy::pub_use ) ] +pub use own::*; + +/// Own namespace of the module. +#[ allow( unused_imports ) ] +pub mod own +{ + #[ allow( clippy::wildcard_imports ) ] + use super::*; + + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use orphan::*; + + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use ::anyhow:: + { + Chain, + Context, + Error, + Ok, + Result, + format_err, + bail as return_err, + ensure, + bail, + }; + +} + +/// Shared with parent namespace of the module +#[ allow( unused_imports ) ] +pub mod orphan +{ + #[ allow( clippy::wildcard_imports ) ] + use super::*; + + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use super::super::untyped; + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use super::super::untyped as for_app; + + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use exposed::*; + + // #[ doc( inline ) ] + // pub use ::anyhow:: + // { + // format_err, + // ensure, + // bail, + // }; + +} + +/// Exposed namespace of the module. +#[ allow( unused_imports ) ] +pub mod exposed +{ + #[ allow( clippy::wildcard_imports ) ] + use super::*; + + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use prelude::*; + +} + +/// Prelude to use essentials: `use my_module::prelude::*`. +#[ allow( unused_imports ) ] +pub mod prelude +{ + use super::*; +} \ No newline at end of file diff --git a/module/core/error_tools/src/lib.rs b/module/core/error_tools/src/lib.rs index 30a25af03b..dbf07d7fb2 100644 --- a/module/core/error_tools/src/lib.rs +++ b/module/core/error_tools/src/lib.rs @@ -3,12 +3,10 @@ #![ 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/error_tools/latest/error_tools/" ) ] #![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] +#![ allow( clippy::mod_module_files ) ] -/// Assertions. -#[ cfg( feature = "enabled" ) ] -pub mod assert; - -/// Alias for std::error::BasicError. +/// Alias for `std::error::BasicError`. +#[ allow( clippy::pub_use ) ] #[ cfg( feature = "enabled" ) ] #[ cfg( not( feature = "no_std" ) ) ] pub mod error; @@ -19,132 +17,86 @@ pub mod dependency { #[ doc( inline ) ] - #[ allow( unused_imports ) ] #[ cfg( feature = "error_typed" ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] pub use ::thiserror; #[ doc( inline ) ] - #[ allow( unused_imports ) ] #[ cfg( feature = "error_untyped" ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] pub use ::anyhow; } #[ cfg( feature = "enabled" ) ] -#[ cfg( feature = "error_typed" ) ] -/// Typed exceptions handling mechanism. -pub mod typed; - -#[ cfg( feature = "enabled" ) ] -#[ cfg( feature = "error_untyped" ) ] -/// Untyped exceptions handling mechanism. -pub mod untyped; - -#[ cfg( feature = "enabled" ) ] +#[ cfg( not( feature = "no_std" ) ) ] #[ doc( inline ) ] #[ allow( unused_imports ) ] +#[ allow( clippy::pub_use ) ] pub use own::*; /// Own namespace of the module. #[ cfg( feature = "enabled" ) ] +#[ cfg( not( feature = "no_std" ) ) ] #[ allow( unused_imports ) ] pub mod own { use super::*; - #[ allow( unused_imports ) ] - use super::*; #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use assert::orphan::*; - - #[ cfg( not( feature = "no_std" ) ) ] - #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use error::orphan::*; - - #[ cfg( feature = "error_untyped" ) ] - #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use untyped::orphan::*; - - #[ cfg( feature = "error_typed" ) ] - #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use typed::orphan::*; + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use error::own::*; } /// Shared with parent namespace of the module #[ cfg( feature = "enabled" ) ] +#[ cfg( not( feature = "no_std" ) ) ] #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] pub use exposed::*; + #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] + pub use error::orphan::*; + } /// Exposed namespace of the module. #[ cfg( feature = "enabled" ) ] +#[ cfg( not( feature = "no_std" ) ) ] #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] - #[ allow( unused_imports ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] pub use prelude::*; #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use assert::exposed::*; - - #[ cfg( not( feature = "no_std" ) ) ] - #[ doc( inline ) ] - #[ allow( unused_imports ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] pub use error::exposed::*; - #[ cfg( feature = "error_untyped" ) ] - #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use untyped::exposed::*; - - #[ cfg( feature = "error_typed" ) ] - #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use typed::exposed::*; - } /// Prelude to use essentials: `use my_module::prelude::*`. #[ cfg( feature = "enabled" ) ] +#[ cfg( not( feature = "no_std" ) ) ] #[ allow( unused_imports ) ] pub mod prelude { - use super::*; - #[ allow( unused_imports ) ] - use super::*; + use super::error; #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use assert::prelude::*; - - #[ cfg( not( feature = "no_std" ) ) ] - #[ doc( inline ) ] - #[ allow( unused_imports ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] pub use error::prelude::*; - #[ cfg( feature = "error_untyped" ) ] - #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use untyped::prelude::*; - - #[ cfg( feature = "error_typed" ) ] - #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use typed::prelude::*; - } diff --git a/module/core/error_tools/src/result.rs b/module/core/error_tools/src/result.rs deleted file mode 100644 index ea5a3c3b48..0000000000 --- a/module/core/error_tools/src/result.rs +++ /dev/null @@ -1,43 +0,0 @@ -// /// Internal namespace. -// mod private -// { -// use crate::error::BasicError; -// -// /// Type alias for Result with BasicError. -// pub type Result< T, E = BasicError > = std::result::Result< T, E >; -// } -// -// /// Own namespace of the module. -// pub mod own -// { -// #[ doc( inline ) ] -// #[ allow( unused_imports ) ] -// pub use orphan::*; -// } -// -// #[ doc( inline ) ] -// #[ allow( unused_imports ) ] -// pub use own::*; -// -// /// Shared with parent namespace of the module -// pub mod orphan -// { -// #[ doc( inline ) ] -// #[ allow( unused_imports ) ] -// pub use exposed::*; -// } -// -// /// Exposed namespace of the module. -// pub mod exposed -// { -// #[ doc( inline ) ] -// #[ allow( unused_imports ) ] -// pub use prelude::*; -// } -// -// /// Prelude to use essentials: `use my_module::prelude::*`. -// pub mod prelude -// { -// pub use private::Result; -// } -// diff --git a/module/core/error_tools/tests/inc/basic_test.rs b/module/core/error_tools/tests/inc/basic_test.rs index 61462b17f9..32cb4c4bba 100644 --- a/module/core/error_tools/tests/inc/basic_test.rs +++ b/module/core/error_tools/tests/inc/basic_test.rs @@ -1,5 +1,5 @@ #![ allow( deprecated ) ] -#![ allow( unused_imports ) ] +// #![ allow( unused_imports ) ] use super::*; // @@ -7,117 +7,116 @@ use super::*; #[ cfg( not( feature = "no_std" ) ) ] tests_impls! { - fn basic() - { - use std::error::Error; - - // test.case( "basic" ); - - let err1 = the_module::BasicError::new( "Some error" ); - a_id!( err1.to_string(), "Some error" ); - a_id!( err1.description(), "Some error" ); - a_id!( err1.msg(), "Some error" ); - a_id!( format!( "err1 : {}", err1 ), "err1 : Some error" ); - - // test.case( "compare" ); - - let err1 = the_module::BasicError::new( "Some error" ); - let err2 = the_module::BasicError::new( "Some error" ); - a_id!( err1, err2 ); - a_id!( err1.description(), err2.description() ); - - // test.case( "clone" ); - - let err1 = the_module::BasicError::new( "Some error" ); - let err2 = err1.clone(); - a_id!( err1, err2 ); - a_id!( err1.description(), err2.description() ); - } - - // - - fn use1() - { - use std::error::Error as ErrorTrait; - use the_module::BasicError as Error; - - // test.case( "basic" ); - - let err1 = Error::new( "Some error" ); - a_id!( err1.to_string(), "Some error" ); - a_id!( err1.description(), "Some error" ); - a_id!( err1.msg(), "Some error" ); - a_id!( format!( "err1 : {}", err1 ), "err1 : Some error" ); - } - - // - - fn use2() - { - use the_module::{ BasicError, ErrorTrait }; - - // test.case( "basic" ); - - let err1 = BasicError::new( "Some error" ); - a_id!( err1.to_string(), "Some error" ); - a_id!( err1.description(), "Some error" ); - a_id!( err1.msg(), "Some error" ); - a_id!( format!( "err1 : {}", err1 ), "err1 : Some error" ); - } - - // - - fn use3() - { - use std::error::Error; - - // test.case( "basic" ); - - let err1 = the_module::BasicError::new( "Some error" ); - a_id!( err1.to_string(), "Some error" ); - a_id!( err1.description(), "Some error" ); - a_id!( err1.msg(), "Some error" ); - a_id!( format!( "err1 : {}", err1 ), "err1 : Some error" ); - } - - // - - fn err_basic() - { - // test.case( "basic" ); - let err : the_module::BasicError = the_module::err!( "abc" ); - a_id!( err.to_string(), "abc" ); - - // test.case( "with args" ); - let err : the_module::BasicError = the_module::err!( "abc{}{}", "def", "ghi" ); - a_id!( err.to_string(), "abcdefghi" ); - } +// fn basic() +// { +// use std::error::Error; +// +// // test.case( "basic" ); +// +// let err1 = the_module::BasicError::new( "Some error" ); +// a_id!( err1.to_string(), "Some error" ); +// a_id!( err1.description(), "Some error" ); +// a_id!( err1.msg(), "Some error" ); +// a_id!( format!( "err1 : {}", err1 ), "err1 : Some error" ); +// +// // test.case( "compare" ); +// +// let err1 = the_module::BasicError::new( "Some error" ); +// let err2 = the_module::BasicError::new( "Some error" ); +// a_id!( err1, err2 ); +// a_id!( err1.description(), err2.description() ); +// +// // test.case( "clone" ); +// +// let err1 = the_module::BasicError::new( "Some error" ); +// let err2 = err1.clone(); +// a_id!( err1, err2 ); +// a_id!( err1.description(), err2.description() ); +// } // - fn sample() - { - #[ cfg( not( feature = "no_std" ) ) ] - fn f1() -> the_module::untyped::Result< () > - { - let _read = std::fs::read_to_string( "Cargo.toml" )?; - Err( the_module::BasicError::new( "Some error" ).into() ) - // the_module::BasicError::new( "Some error" ).into() - // zzz : make it working maybe - } - - #[ cfg( not( feature = "no_std" ) ) ] - { - let err = f1(); - println!( "{err:#?}" ); - // < Err( - // < BasicError { - // < msg: "Some error", - // < }, - // < ) - } - } - +// fn use1() +// { +// use std::error::Error as ErrorTrait; +// use the_module::BasicError as Error; +// +// // test.case( "basic" ); +// +// let err1 = Error::new( "Some error" ); +// a_id!( err1.to_string(), "Some error" ); +// a_id!( err1.description(), "Some error" ); +// a_id!( err1.msg(), "Some error" ); +// a_id!( format!( "err1 : {}", err1 ), "err1 : Some error" ); +// } +// +// // +// +// fn use2() +// { +// use the_module::{ BasicError, ErrorTrait }; +// +// // test.case( "basic" ); +// +// let err1 = BasicError::new( "Some error" ); +// a_id!( err1.to_string(), "Some error" ); +// a_id!( err1.description(), "Some error" ); +// a_id!( err1.msg(), "Some error" ); +// a_id!( format!( "err1 : {}", err1 ), "err1 : Some error" ); +// } +// +// // +// +// fn use3() +// { +// use std::error::Error; +// +// // test.case( "basic" ); +// +// let err1 = the_module::BasicError::new( "Some error" ); +// a_id!( err1.to_string(), "Some error" ); +// a_id!( err1.description(), "Some error" ); +// a_id!( err1.msg(), "Some error" ); +// a_id!( format!( "err1 : {}", err1 ), "err1 : Some error" ); +// } +// +// // +// +// fn err_basic() +// { +// // test.case( "basic" ); +// let err : the_module::BasicError = the_module::err!( "abc" ); +// a_id!( err.to_string(), "abc" ); +// +// // test.case( "with args" ); +// let err : the_module::BasicError = the_module::err!( "abc{}{}", "def", "ghi" ); +// a_id!( err.to_string(), "abcdefghi" ); +// } +// +// // +// +// fn sample() +// { +// #[ cfg( not( feature = "no_std" ) ) ] +// fn f1() -> the_module::untyped::Result< () > +// { +// let _read = std::fs::read_to_string( "Cargo.toml" )?; +// Err( the_module::BasicError::new( "Some error" ).into() ) +// // the_module::BasicError::new( "Some error" ).into() +// // zzz : make it working maybe +// } +// +// #[ cfg( not( feature = "no_std" ) ) ] +// { +// let err = f1(); +// println!( "{err:#?}" ); +// // < Err( +// // < BasicError { +// // < msg: "Some error", +// // < }, +// // < ) +// } +// } } @@ -126,10 +125,10 @@ tests_impls! #[ cfg( not( feature = "no_std" ) ) ] tests_index! { - basic, - use1, - use2, - use3, - err_basic, - sample, + // basic, + // use1, + // use2, + // use3, + // err_basic, + // sample, } diff --git a/module/core/error_tools/tests/inc/err_with_test.rs b/module/core/error_tools/tests/inc/err_with_test.rs index 7b3a65516f..3e50ac7d08 100644 --- a/module/core/error_tools/tests/inc/err_with_test.rs +++ b/module/core/error_tools/tests/inc/err_with_test.rs @@ -20,7 +20,7 @@ fn err_with() fn err_with_report() { - use error_tools::ErrWith; + use the_module::ErrWith; let result : Result< (), std::io::Error > = Err( std::io::Error::new( std::io::ErrorKind::Other, "an error occurred" ) ); let report = "additional context"; let got : Result< (), ( &str, std::io::Error ) > = result.err_with_report( &report ); diff --git a/module/core/error_tools/tests/inc/mod.rs b/module/core/error_tools/tests/inc/mod.rs index 256c6e20bd..5e78c0b4c1 100644 --- a/module/core/error_tools/tests/inc/mod.rs +++ b/module/core/error_tools/tests/inc/mod.rs @@ -1,8 +1,12 @@ #[ allow( unused_imports ) ] use super::*; -mod assert_test; +use test_tools::exposed::*; + mod basic_test; +mod namespace_test; + +mod assert_test; #[ cfg( not( feature = "no_std" ) ) ] mod err_with_test; mod untyped_test; diff --git a/module/core/error_tools/tests/inc/namespace_test.rs b/module/core/error_tools/tests/inc/namespace_test.rs new file mode 100644 index 0000000000..92e96b0610 --- /dev/null +++ b/module/core/error_tools/tests/inc/namespace_test.rs @@ -0,0 +1,12 @@ +use super::*; + +#[ test ] +fn exposed_main_namespace() +{ + + the_module::error::debug_assert_id!( 1, 1 ); + the_module::exposed::error::debug_assert_id!( 1, 1 ); + use the_module::exposed::*; + error::debug_assert_id!( 1, 1 ); + +} \ No newline at end of file diff --git a/module/core/error_tools/tests/inc/untyped_test.rs b/module/core/error_tools/tests/inc/untyped_test.rs index f1db8e77a9..6e92b663e7 100644 --- a/module/core/error_tools/tests/inc/untyped_test.rs +++ b/module/core/error_tools/tests/inc/untyped_test.rs @@ -10,8 +10,8 @@ tests_impls! { // test.case( "from parse usize error" ); - let err = the_module::untyped::format_err!( "err" ); - a_id!( the_module::untyped::Error::is::< &str >( &err ), true ); + let err = the_module::error::untyped::format_err!( "err" ); + a_id!( the_module::error::untyped::Error::is::< &str >( &err ), true ); a_id!( err.is::< &str >(), true ); a_id!( err.to_string(), "err" ); } diff --git a/module/core/error_tools/tests/smoke_test.rs b/module/core/error_tools/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/core/error_tools/tests/smoke_test.rs +++ b/module/core/error_tools/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/core/error_tools/tests/tests.rs b/module/core/error_tools/tests/tests.rs index 0374c10521..4feacbc8fb 100644 --- a/module/core/error_tools/tests/tests.rs +++ b/module/core/error_tools/tests/tests.rs @@ -1,7 +1,8 @@ +//! All tests. + +#![ allow( unused_imports ) ] -#[ allow( unused_imports ) ] use error_tools as the_module; -#[ allow( unused_imports ) ] -use test_tools::exposed::*; +// use test_tools::exposed::*; mod inc; diff --git a/module/core/for_each/Cargo.toml b/module/core/for_each/Cargo.toml index 877ccbe184..2e43d14153 100644 --- a/module/core/for_each/Cargo.toml +++ b/module/core/for_each/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "for_each" -version = "0.8.0" +version = "0.10.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/for_each/License b/module/core/for_each/License index 6d5ef8559f..72c80c1308 100644 --- a/module/core/for_each/License +++ b/module/core/for_each/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/core/for_each/Readme.md index eb0d2e3d5e..ed57cd123b 100644 --- a/module/core/for_each/Readme.md +++ b/module/core/for_each/Readme.md @@ -2,7 +2,7 @@ # Module :: for_each - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_for_each_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_for_each_push.yml) [![docs.rs](https://img.shields.io/docsrs/for_each?color=e3e8f0&logo=docs.rs)](https://docs.rs/for_each) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Ffor_each%2Fexamples%2Ffor_each_trivial.rs,RUN_POSTFIX=--example%20for_each_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_for_each_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_for_each_push.yml) [![docs.rs](https://img.shields.io/docsrs/for_each?color=e3e8f0&logo=docs.rs)](https://docs.rs/for_each) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Ffor_each%2Fexamples%2Ffor_each_trivial.rs,RUN_POSTFIX=--example%20module%2Fcore%2Ffor_each%2Fexamples%2Ffor_each_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Apply a macro for each element of a list. diff --git a/module/core/for_each/src/lib.rs b/module/core/for_each/src/lib.rs index b106a5110b..00825e5b96 100644 --- a/module/core/for_each/src/lib.rs +++ b/module/core/for_each/src/lib.rs @@ -4,7 +4,7 @@ #![ doc( html_root_url = "https://docs.rs/for_each/latest/for_each/" ) ] #![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] -/// Internal namespace. +/// Define a private namespace for all its items. #[ cfg( feature = "enabled" ) ] mod private { diff --git a/module/core/for_each/tests/smoke_test.rs b/module/core/for_each/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/core/for_each/tests/smoke_test.rs +++ b/module/core/for_each/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/core/format_tools/Cargo.toml b/module/core/format_tools/Cargo.toml index 8cd8b79c01..b581ce66ed 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.2.0" +version = "0.5.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/format_tools/License b/module/core/format_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/core/format_tools/License +++ b/module/core/format_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/core/format_tools/Readme.md index 19543ef6f4..e298c5a5db 100644 --- a/module/core/format_tools/Readme.md +++ b/module/core/format_tools/Readme.md @@ -1,7 +1,7 @@ # Module :: format_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_reflect_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_reflect_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/format_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/format_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Freflect_tools%2Fexamples%2Freflect_tools_trivial.rs,RUN_POSTFIX=--example%20reflect_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_format_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_format_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/format_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/format_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fformat_tools%2Fexamples%2Fformat_tools_trivial.rs,RUN_POSTFIX=--example%20module%2Fcore%2Fformat_tools%2Fexamples%2Fformat_tools_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Collection of mechanisms for formatting and serialization into string. @@ -15,66 +15,70 @@ Using the `to_string_with_fallback` macro to convert values to strings with a pr ```rust fn main() { - // Import necessary traits and the macro from the `format_tools` crate. - use core::fmt; - use format_tools:: + #[ cfg( feature = "enabled" ) ] { - WithDebug, - WithDisplay, - to_string_with_fallback, - }; - // Define a struct that implements both Debug and Display traits. - struct Both; + // Import necessary traits and the macro from the `format_tools` crate. + use core::fmt; + use format_tools:: + { + WithDebug, + WithDisplay, + to_string_with_fallback, + }; - // Implement the Debug trait for the Both struct. - impl fmt::Debug for Both - { - fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result + // Define a struct that implements both Debug and Display traits. + struct Both; + + // Implement the Debug trait for the Both struct. + impl fmt::Debug for Both { - write!( f, "This is debug" ) + fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result + { + write!( f, "This is debug" ) + } } - } - // Implement the Display trait for the Both struct. - impl fmt::Display for Both - { - fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result + // Implement the Display trait for the Both struct. + impl fmt::Display for Both { - write!( f, "This is display" ) + fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result + { + write!( f, "This is display" ) + } } - } - // Define a struct that implements only the Debug trait. - struct OnlyDebug; + // Define a struct that implements only the Debug trait. + struct OnlyDebug; - // Implement the Debug trait for the OnlyDebug struct. - impl fmt::Debug for OnlyDebug - { - fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result + // Implement the Debug trait for the OnlyDebug struct. + impl fmt::Debug for OnlyDebug { - write!( f, "This is debug" ) + fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result + { + write!( f, "This is debug" ) + } } - } - - // Example usage: Using Both which implements both Debug and Display. - let src = Both; - // Convert the struct to a string using `to_string_with_fallback` macro. - // The primary formatting method WithDisplay is used. - let got = to_string_with_fallback!( WithDisplay, WithDebug, &src ); - let exp = "This is display".to_string(); - // Assert that the result matches the expected value. - assert_eq!( got, exp ); - - // Example usage: Using OnlyDebug which implements only Debug. - let src = OnlyDebug; - // Convert the struct to a string using `to_string_with_fallback` macro. - // The primary formatting method WithDisplay is not available, so the fallback WithDebug is used. - let got = to_string_with_fallback!( WithDisplay, WithDebug, &src ); - let exp = "This is debug".to_string(); - // Assert that the result matches the expected value. - assert_eq!( got, exp ); + // Example usage: Using Both which implements both Debug and Display. + let src = Both; + // Convert the struct to a string using `to_string_with_fallback` macro. + // The primary formatting method WithDisplay is used. + let got = to_string_with_fallback!( WithDisplay, WithDebug, &src ); + let exp = "This is display".to_string(); + // Assert that the result matches the expected value. + assert_eq!( got, exp ); + + // Example usage: Using OnlyDebug which implements only Debug. + let src = OnlyDebug; + // Convert the struct to a string using `to_string_with_fallback` macro. + // The primary formatting method WithDisplay is not available, so the fallback WithDebug is used. + let got = to_string_with_fallback!( WithDisplay, WithDebug, &src ); + let exp = "This is debug".to_string(); + // Assert that the result matches the expected value. + assert_eq!( got, exp ); + + } } ``` diff --git a/module/core/format_tools/examples/format_tools_trivial.rs b/module/core/format_tools/examples/format_tools_trivial.rs index 6683ab3f4a..bd7c7208c9 100644 --- a/module/core/format_tools/examples/format_tools_trivial.rs +++ b/module/core/format_tools/examples/format_tools_trivial.rs @@ -5,64 +5,68 @@ fn main() { - // Import necessary traits and the macro from the `format_tools` crate. - use core::fmt; - use format_tools:: + #[ cfg( feature = "enabled" ) ] { - WithDebug, - WithDisplay, - to_string_with_fallback, - }; - // Define a struct that implements both Debug and Display traits. - struct Both; + // Import necessary traits and the macro from the `format_tools` crate. + use core::fmt; + use format_tools:: + { + WithDebug, + WithDisplay, + to_string_with_fallback, + }; - // Implement the Debug trait for the Both struct. - impl fmt::Debug for Both - { - fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result + // Define a struct that implements both Debug and Display traits. + struct Both; + + // Implement the Debug trait for the Both struct. + impl fmt::Debug for Both { - write!( f, "This is debug" ) + fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result + { + write!( f, "This is debug" ) + } } - } - // Implement the Display trait for the Both struct. - impl fmt::Display for Both - { - fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result + // Implement the Display trait for the Both struct. + impl fmt::Display for Both { - write!( f, "This is display" ) + fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result + { + write!( f, "This is display" ) + } } - } - // Define a struct that implements only the Debug trait. - struct OnlyDebug; + // Define a struct that implements only the Debug trait. + struct OnlyDebug; - // Implement the Debug trait for the OnlyDebug struct. - impl fmt::Debug for OnlyDebug - { - fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result + // Implement the Debug trait for the OnlyDebug struct. + impl fmt::Debug for OnlyDebug { - write!( f, "This is debug" ) + fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result + { + write!( f, "This is debug" ) + } } - } - // Example usage: Using Both which implements both Debug and Display. - let src = Both; - // Convert the struct to a string using `to_string_with_fallback` macro. - // The primary formatting method WithDisplay is used. - let got = to_string_with_fallback!( WithDisplay, WithDebug, &src ); - let exp = "This is display".to_string(); - // Assert that the result matches the expected value. - assert_eq!( got, exp ); - - // Example usage: Using OnlyDebug which implements only Debug. - let src = OnlyDebug; - // Convert the struct to a string using `to_string_with_fallback` macro. - // The primary formatting method WithDisplay is not available, so the fallback WithDebug is used. - let got = to_string_with_fallback!( WithDisplay, WithDebug, &src ); - let exp = "This is debug".to_string(); - // Assert that the result matches the expected value. - assert_eq!( got, exp ); + // Example usage: Using Both which implements both Debug and Display. + let src = Both; + // Convert the struct to a string using `to_string_with_fallback` macro. + // The primary formatting method WithDisplay is used. + let got = to_string_with_fallback!( WithDisplay, WithDebug, &src ); + let exp = "This is display".to_string(); + // Assert that the result matches the expected value. + assert_eq!( got, exp ); + // Example usage: Using OnlyDebug which implements only Debug. + let src = OnlyDebug; + // Convert the struct to a string using `to_string_with_fallback` macro. + // The primary formatting method WithDisplay is not available, so the fallback WithDebug is used. + let got = to_string_with_fallback!( WithDisplay, WithDebug, &src ); + let exp = "This is debug".to_string(); + // Assert that the result matches the expected value. + assert_eq!( got, exp ); + + } } \ No newline at end of file diff --git a/module/core/format_tools/src/format.rs b/module/core/format_tools/src/format.rs index 4e55517dcc..6200a4f5d8 100644 --- a/module/core/format_tools/src/format.rs +++ b/module/core/format_tools/src/format.rs @@ -2,7 +2,7 @@ //! Collection of mechanisms for formatting and serialization into string. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { @@ -17,8 +17,8 @@ mod private macro_rules! _field_with_key { ( + $path : expr, $key : ident, - $src : expr, $how : ty, $fallback1 : ty, $fallback2 : ty @@ -28,9 +28,10 @@ mod private {{ ( ::core::stringify!( $key ), - $crate::OptionalCow::< '_, str, $how >::from + // $crate::OptionalCow::< '_, str, $how >::from + Option::Some ( - $crate::to_string_with_fallback!( $how, $fallback1, $fallback2, $src ) + $crate::to_string_with_fallback!( $how, $fallback1, $fallback2, $path ) ), ) }}; @@ -47,32 +48,39 @@ mod private macro_rules! _field { - ( & $path:ident.$( $key:ident )+, $how : ty, $fallback1 : ty, $fallback2 : ty $(,)? ) => + // dst.push( field!( &self.id ) ); + ( ( & $pre:ident.$( $key:tt )+ ), $how : ty, $fallback1 : ty, $fallback2 : ty $(,)? ) => {{ - $crate::_field!( # ( & $path . ) ( $( $key )+ ) ( $how, $fallback1, $fallback2 ) ) + $crate::_field!( # ( & $pre . ) ( $( $key )+ ) ( $how, $fallback1, $fallback2 ) ) }}; - ( $path:ident.$( $key:ident )+, $how : ty, $fallback1 : ty, $fallback2 : ty $(,)? ) => + // dst.push( field!( self.id ) ); + ( ( $pre:ident.$( $key:tt )+ ), $how : ty, $fallback1 : ty, $fallback2 : ty $(,)? ) => {{ - $crate::_field!( # ( $path . ) ( $( $key )+ ) ( $how, $fallback1, $fallback2 ) ) + $crate::_field!( # ( $pre . ) ( $( $key )+ ) ( $how, $fallback1, $fallback2 ) ) }}; - ( & $key:ident, $how : ty, $fallback1 : ty, $fallback2 : ty $(,)? ) => + // dst.push( field!( &tools ) ); + ( ( & $key:ident ), $how : ty, $fallback1 : ty, $fallback2 : ty $(,)? ) => {{ - $crate::_field!( # () ( $key ) ( $how, $fallback1, $fallback2 ) ) + $crate::_field!( # () ( & $key ) ( $how, $fallback1, $fallback2 ) ) }}; - ( $key:ident, $how : ty, $fallback1 : ty, $fallback2 : ty $(,)? ) => + // dst.push( field!( tools ) ); + ( ( $key:ident ), $how : ty, $fallback1 : ty, $fallback2 : ty $(,)? ) => {{ $crate::_field!( # () ( $key ) ( $how, $fallback1, $fallback2 ) ) }}; // private + // ( a.b. ) + // ( c.d ) + // ( $crate::WithRef, $crate::WithDebug, $crate::WithDebug ) ( # ( $( $prefix:tt )* ) - ( $prekey:ident.$( $field:ident )+ ) + ( $prekey:ident.$( $field:tt )+ ) ( $how : ty, $fallback1 : ty, $fallback2 : ty ) ) => @@ -80,6 +88,23 @@ mod private $crate::_field!( # ( $( $prefix )* $prekey . ) ( $( $field )+ ) ( $how, $fallback1, $fallback2 ) ) }}; + // ( a.b. ) + // ( 0.d ) + // ( $crate::WithRef, $crate::WithDebug, $crate::WithDebug ) + ( + # + ( $( $prefix:tt )* ) + ( $prekey:tt.$( $field:tt )+ ) + ( $how : ty, $fallback1 : ty, $fallback2 : ty ) + ) + => + {{ + $crate::_field!( # ( $( $prefix )* $prekey . ) ( $( $field )+ ) ( $how, $fallback1, $fallback2 ) ) + }}; + + // ( a.b.c. ) + // ( d ) + // ( $crate::WithRef, $crate::WithDebug, $crate::WithDebug ) ( # ( $( $prefix:tt )* ) @@ -91,6 +116,9 @@ mod private $crate::_field!( # # ( $( $prefix )* ) ( $key ) ( $how, $fallback1, $fallback2 ) ) }}; + // ( a.b.c ) + // ( d ) + // ( $crate::WithRef, $crate::WithDebug, $crate::WithDebug ) ( # # ( $( $prefix:tt )* ) @@ -99,7 +127,8 @@ mod private ) => {{ - $crate::_field_with_key!( $key, $( $prefix )* $key, $how, $fallback1, $fallback2 ) + // _field_with_key!( id, &self. id, $crate::WithRef, $crate::WithDisplay, $crate::WithDebugMultiline ) + $crate::_field_with_key!( $( $prefix )* $key, $key, $how, $fallback1, $fallback2 ) }}; } @@ -129,7 +158,7 @@ mod private ) => {{ - $crate::_field_with_key!( $key, $src, $crate::WithRef, $crate::WithDisplay, $crate::WithDebugMultiline ) + $crate::_field_with_key!( $src, $key, $crate::WithRef, $crate::WithDisplay, $crate::WithDebugMultiline ) }}; } @@ -144,7 +173,7 @@ mod private ( $( $t:tt )+ ) => {{ - $crate::_field!( $( $t )+, $crate::WithRef, $crate::WithDisplay, $crate::WithDebugMultiline ) + $crate::_field!( ( $( $t )+ ), $crate::WithRef, $crate::WithDisplay, $crate::WithDebugMultiline ) }} } @@ -178,7 +207,7 @@ mod private ) => {{ - $crate::_field_with_key!( $key, $src, $crate::WithRef, $crate::WithDisplay, $crate::WithDebug ) + $crate::_field_with_key!( $src, $key, $crate::WithRef, $crate::WithDisplay, $crate::WithDebug ) }}; } @@ -193,7 +222,7 @@ mod private ( $( $t:tt )+ ) => {{ - $crate::_field!( $( $t )+, $crate::WithRef, $crate::WithDisplay, $crate::WithDebug ) + $crate::_field!( ( $( $t )+ ), $crate::WithRef, $crate::WithDisplay, $crate::WithDebug ) }} } @@ -226,7 +255,7 @@ mod private ) => {{ - $crate::_field_with_key!( $key, $src, $crate::WithRef, $crate::WithDebug, $crate::WithDebug ) + $crate::_field_with_key!( $src, $key, $crate::WithRef, $crate::WithDebug, $crate::WithDebug ) }}; } @@ -240,7 +269,7 @@ mod private ( $( $t:tt )+ ) => {{ - $crate::_field!( $( $t )+, $crate::WithRef, $crate::WithDebug, $crate::WithDebug ) + $crate::_field!( ( $( $t )+ ), $crate::WithRef, $crate::WithDebug, $crate::WithDebug ) }} } @@ -260,6 +289,12 @@ pub mod string; pub mod table; pub mod to_string; pub mod to_string_with_fallback; +pub mod text_wrap; + +/// A strucutre for diagnostic and demonstration purpose. +#[ doc( hidden ) ] +#[ cfg( debug_assertions ) ] +pub mod test_object_without_impl; #[ doc( inline ) ] #[ allow( unused_imports ) ] @@ -283,6 +318,7 @@ pub mod own table::orphan::*, to_string::orphan::*, to_string_with_fallback::orphan::*, + text_wrap::orphan::*, }; } @@ -304,6 +340,14 @@ pub mod orphan ref_or_debug, }; + #[ doc( hidden ) ] + #[ cfg( debug_assertions ) ] + pub use test_object_without_impl:: + { + TestObjectWithoutImpl, + test_objects_gen, + }; + } /// Exposed namespace of the module. @@ -327,6 +371,7 @@ pub mod exposed table::exposed::*, to_string::exposed::*, to_string_with_fallback::exposed::*, + text_wrap::exposed::*, }; } @@ -349,6 +394,7 @@ pub mod prelude table::prelude::*, to_string::prelude::*, to_string_with_fallback::prelude::*, + text_wrap::prelude::*, }; } diff --git a/module/core/format_tools/src/format/as_table.rs b/module/core/format_tools/src/format/as_table.rs index 4e70ba233d..d269556525 100644 --- a/module/core/format_tools/src/format/as_table.rs +++ b/module/core/format_tools/src/format/as_table.rs @@ -2,7 +2,7 @@ //! Nice print's wrapper. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { @@ -21,29 +21,29 @@ mod private /// #[ repr( transparent ) ] #[ derive( Clone, Copy ) ] - pub struct AsTable< 'table, Table, RowKey, Row, CellKey, CellRepr > + pub struct AsTable< 'table, Table, RowKey, Row, CellKey> ( &'table Table, ::core::marker::PhantomData <( &'table (), - fn() -> ( &'table RowKey, Row, &'table CellKey, CellRepr ), + fn() -> ( &'table RowKey, Row, &'table CellKey ), )>, ) where RowKey : table::RowKey, - Row : Cells< CellKey, CellRepr >, + Row : Cells< CellKey >, CellKey : table::CellKey + ?Sized, - CellRepr : table::CellRepr + // CellRepr : table::CellRepr ; - impl< 'table, Table, RowKey, Row, CellKey, CellRepr > - AsTable< 'table, Table, RowKey, Row, CellKey, CellRepr > + impl< 'table, Table, RowKey, Row, CellKey> + AsTable< 'table, Table, RowKey, Row, CellKey> where RowKey : table::RowKey, - Row : Cells< CellKey, CellRepr >, + Row : Cells< CellKey >, CellKey : table::CellKey + ?Sized, - CellRepr : table::CellRepr, + // CellRepr : table::CellRepr, { /// Just a constructor. pub fn new( src : &'table Table ) -> Self @@ -52,13 +52,13 @@ mod private } } - impl< 'table, Table, RowKey, Row, CellKey, CellRepr > AsRef< Table > - for AsTable< 'table, Table, RowKey, Row, CellKey, CellRepr > + impl< 'table, Table, RowKey, Row, CellKey> AsRef< Table > + for AsTable< 'table, Table, RowKey, Row, CellKey> where RowKey : table::RowKey, - Row : Cells< CellKey, CellRepr >, + Row : Cells< CellKey >, CellKey : table::CellKey + ?Sized, - CellRepr : table::CellRepr, + // CellRepr : table::CellRepr, { fn as_ref( &self ) -> &Table { @@ -66,13 +66,13 @@ mod private } } - impl< 'table, Table, RowKey, Row, CellKey, CellRepr > Deref - for AsTable< 'table, Table, RowKey, Row, CellKey, CellRepr > + impl< 'table, Table, RowKey, Row, CellKey> Deref + for AsTable< 'table, Table, RowKey, Row, CellKey> where RowKey : table::RowKey, - Row : Cells< CellKey, CellRepr >, + Row : Cells< CellKey >, CellKey : table::CellKey + ?Sized, - CellRepr : table::CellRepr, + // CellRepr : table::CellRepr, { type Target = Table; @@ -82,13 +82,13 @@ mod private } } - impl< 'table, Table, RowKey, Row, CellKey, CellRepr > From< &'table Table > - for AsTable< 'table, Table, RowKey, Row, CellKey, CellRepr > + impl< 'table, Table, RowKey, Row, CellKey> From< &'table Table > + for AsTable< 'table, Table, RowKey, Row, CellKey> where RowKey : table::RowKey, - Row : Cells< CellKey, CellRepr >, + Row : Cells< CellKey >, CellKey : table::CellKey + ?Sized, - CellRepr : table::CellRepr, + // CellRepr : table::CellRepr, { fn from( table : &'table Table ) -> Self { @@ -96,14 +96,14 @@ mod private } } - impl< 'table, Table, RowKey, Row, CellKey, CellRepr > fmt::Debug - for AsTable< 'table, Table, RowKey, Row, CellKey, CellRepr > + impl< 'table, Table, RowKey, Row, CellKey> fmt::Debug + for AsTable< 'table, Table, RowKey, Row, CellKey> where Table : fmt::Debug, RowKey : table::RowKey, - Row : Cells< CellKey, CellRepr >, + Row : Cells< CellKey >, CellKey : table::CellKey + ?Sized, - CellRepr : table::CellRepr, + // CellRepr : table::CellRepr, { fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result { @@ -130,25 +130,25 @@ mod private type RowKey : table::RowKey; /// The type representing a row, must implement `Cells`. - type Row : Cells< Self::CellKey, Self::CellRepr >; + type Row : Cells< Self::CellKey >; /// The type used to identify cells within a row, must implement `Key` and can be unsized. type CellKey : table::CellKey + ?Sized; - /// The type representing the content of a cell, must implement `CellRepr`. - type CellRepr : table::CellRepr; + // /// The type representing the content of a cell, must implement `CellRepr`. + // type // CellRepr : table::CellRepr; /// Converts the data reference into an `AsTable` reference. - fn as_table( &self ) -> AsTable< '_, Self::Table, Self::RowKey, Self::Row, Self::CellKey, Self::CellRepr >; + fn as_table( &self ) -> AsTable< '_, Self::Table, Self::RowKey, Self::Row, Self::CellKey >; } - impl< 'table, Table, RowKey, Row, CellKey, CellRepr > IntoAsTable - for AsTable< 'table, Table, RowKey, Row, CellKey, CellRepr > + impl< 'table, Table, RowKey, Row, CellKey> IntoAsTable + for AsTable< 'table, Table, RowKey, Row, CellKey> where RowKey : table::RowKey, - Row : Cells< CellKey, CellRepr >, + Row : Cells< CellKey >, CellKey : table::CellKey + ?Sized, - CellRepr : table::CellRepr, + // CellRepr : table::CellRepr, Self : Copy, { @@ -156,9 +156,9 @@ mod private type RowKey = RowKey; type Row = Row; type CellKey = CellKey; - type CellRepr = CellRepr; + // type CellRepr = CellRepr; - fn as_table( &self ) -> AsTable< '_, Self::Table, Self::RowKey, Self::Row, Self::CellKey, Self::CellRepr > + fn as_table( &self ) -> AsTable< '_, Self::Table, Self::RowKey, Self::Row, Self::CellKey > { *self } @@ -168,9 +168,9 @@ mod private // impl< Row > IntoAsTable // for Vec< Row > // where -// Row : Cells< Self::CellKey, Self::CellRepr >, +// Row : Cells< Self::CellKey >, // // CellKey : table::CellKey + ?Sized, -// // CellRepr : table::CellRepr, +// // // CellRepr : table::CellRepr, // { // // type Table = Self; @@ -179,14 +179,14 @@ mod private // type CellKey = str; // type CellRepr = WithRef; // -// fn as_table( &self ) -> AsTable< '_, Self::Table, Self::RowKey, Self::Row, Self::CellKey, Self::CellRepr > +// fn as_table( &self ) -> AsTable< '_, Self::Table, Self::RowKey, Self::Row, Self::CellKey > // { // AsTable::from( self ) // } // // } - // pub struct AsTable< 'table, Table, RowKey, Row, CellKey, CellRepr > + // pub struct AsTable< 'table, Table, RowKey, Row, CellKey> } diff --git a/module/core/format_tools/src/format/filter.rs b/module/core/format_tools/src/format/filter.rs index 191522e138..1551721570 100644 --- a/module/core/format_tools/src/format/filter.rs +++ b/module/core/format_tools/src/format/filter.rs @@ -2,7 +2,7 @@ //! Print data as table. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { diff --git a/module/core/format_tools/src/format/md_math.rs b/module/core/format_tools/src/format/md_math.rs index a53625fc21..9aa70022d0 100644 --- a/module/core/format_tools/src/format/md_math.rs +++ b/module/core/format_tools/src/format/md_math.rs @@ -3,7 +3,9 @@ //! Provides functionality for converting multidimensional indices into flat offsets, //! useful for operations involving multidimensional arrays or grids. -/// Internal namespace. +// xxx : use crate mdmath + +/// Define a private namespace for all its items. mod private { use core:: @@ -29,7 +31,20 @@ mod private /// # Returns /// /// A value of type `T` representing the flat offset. - fn md_offset( & self, md_index : [ T ; 3 ] ) -> T; + fn md_offset( & self, md_index : Self ) -> T; + } + + impl< T > MdOffset< T > for [ T ; 2 ] + where + T : Mul< T, Output = T > + Add< T, Output = T > + PartialOrd + Copy + fmt::Debug, + { + fn md_offset( & self, md_index : [ T ; 2 ] ) -> T + { + debug_assert!( md_index[ 0 ] < self[ 0 ], "md_index : {md_index:?} | md_size : {self:?}" ); + debug_assert!( md_index[ 1 ] < self[ 1 ], "md_index : {md_index:?} | md_size : {self:?}" ); + let m1 = self[ 0 ]; + md_index[ 0 ] + m1 * md_index[ 1 ] + } } impl< T > MdOffset< T > for [ T ; 3 ] diff --git a/module/core/format_tools/src/format/output_format.rs b/module/core/format_tools/src/format/output_format.rs index 511d12db79..971b413ec5 100644 --- a/module/core/format_tools/src/format/output_format.rs +++ b/module/core/format_tools/src/format/output_format.rs @@ -1,6 +1,6 @@ //! Customizable format of printing table. //! -//! # Example of ordinary format +//! # Example of table format //! //! ```text //! sid | sname | gap @@ -28,20 +28,19 @@ //! ``` //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { + use std::borrow::Cow; + use crate::*; use print:: { InputExtract, Context, }; - use core:: - { - fmt, - }; + use core::fmt; //= @@ -74,14 +73,45 @@ mod private #[ inline( always ) ] fn default() -> Self { - super::ordinary::Ordinary::instance() + super::table::Table::instance() } } + /// Print table, which is constructed with vectors and `Cow`s, with the + /// specified output formatter. + /// + /// This function is useful when you do not want to use `AsTable`, or implement `Fields`, and + /// other traits, but you just have string slices in vectors. + /// + /// `rows` should not contain header of the table, it will be automatically added if `has_header` + /// is true. + pub fn vector_table_write< 'data, 'context > + ( + column_names : Vec< Cow< 'data, str > >, + has_header : bool, + rows : Vec< Vec< Cow< 'data, str > > >, + c : &mut Context< 'context >, + ) -> fmt::Result + { + InputExtract::extract_from_raw_table + ( + column_names, + has_header, + rows, + c.printer.filter_col, + c.printer.filter_row, + | x | + { + c.printer.output_format.extract_write( x, c ) + } + ) + } + } -mod ordinary; +mod table; mod records; +mod keys; #[ allow( unused_imports ) ] pub use own::*; @@ -97,14 +127,13 @@ pub mod own #[ doc( inline ) ] pub use { - ordinary::Ordinary, + table::Table, records::Records, + keys::Keys, }; #[ doc( inline ) ] - pub use private:: - { - }; + pub use private::vector_table_write; } @@ -125,10 +154,7 @@ pub mod exposed pub use super::super::output_format; #[ doc( inline ) ] - pub use private:: - { - TableOutputFormat, - }; + pub use private::TableOutputFormat; } diff --git a/module/core/format_tools/src/format/output_format/keys.rs b/module/core/format_tools/src/format/output_format/keys.rs new file mode 100644 index 0000000000..55ee27b023 --- /dev/null +++ b/module/core/format_tools/src/format/output_format/keys.rs @@ -0,0 +1,107 @@ +//! Implement keys list output format. +//! +//! # Example +//! +//! ```text +//! ``` +//! + +use crate::*; +use print:: +{ + InputExtract, + Context, +}; +use core:: +{ + fmt, +}; +use std::sync::OnceLock; + +/// A struct representing the list of keys output format. +#[derive( Debug )] +pub struct Keys +{ + // /// Prefix added to each row. + // pub table_prefix : String, + // /// Postfix added to each row. + // pub table_postfix : String, + // /// Separator used between rows. + // pub table_separator : String, + // /// Prefix added to each row. + // pub row_prefix : String, + // /// Postfix added to each row. + // pub row_postfix : String, + // /// Separator used between rows. + // pub row_separator : String, + // /// Prefix added to each cell. + // pub cell_prefix : String, + // /// Postfix added to each cell. + // pub cell_postfix : String, + // /// Separator used between table columns. + // pub cell_separator : String, +} + +impl Keys +{ + /// Returns a reference to a static instance of `Keys`. + pub fn instance() -> &'static dyn TableOutputFormat + { + static INSTANCE : OnceLock< Keys > = OnceLock::new(); + INSTANCE.get_or_init( || Keys::default() ) + } +} + +impl Default for Keys +{ + fn default() -> Self + { + + // let cell_prefix = "".to_string(); + // let cell_postfix = "".to_string(); + // let cell_separator = " │ ".to_string(); + // let row_prefix = "│ ".to_string(); + // let row_postfix = " │".to_string(); + // let row_separator = "\n".to_string(); + // let table_prefix = "".to_string(); + // let table_postfix = "".to_string(); + // let table_separator = "\n".to_string(); + + Self + { + // table_prefix, + // table_postfix, + // table_separator, + // row_prefix, + // row_postfix, + // row_separator, + // cell_prefix, + // cell_postfix, + // cell_separator, + } + } +} + +impl TableOutputFormat for Keys +{ + + fn extract_write< 'buf, 'data >( + &self, + x : &InputExtract< 'data >, + c : &mut Context< 'buf >, + ) -> fmt::Result + { + + // dbg!( &x ); + + for col in &x.col_descriptors + { + write!( c.buf, " - {}\n", col.label )?; + } + + write!( c.buf, " {} fields\n", x.col_descriptors.len() )?; + + Ok(()) + } + +} diff --git a/module/core/format_tools/src/format/output_format/ordinary.rs b/module/core/format_tools/src/format/output_format/ordinary.rs deleted file mode 100644 index 23606111f3..0000000000 --- a/module/core/format_tools/src/format/output_format/ordinary.rs +++ /dev/null @@ -1,275 +0,0 @@ -//! Implement classic table output format. -//! -//! # Example -//! -//! ```text -//! sid | sname | gap -//! -----+-------+----- -//! 3 | Alice | 5 -//! 6 | Joe | 1 -//! 10 | Boris | 5 -//! ``` - -use crate::*; -use print:: -{ - InputExtract, - Context, -}; -use core:: -{ - fmt, -}; -use std::sync::OnceLock; - -/// A struct representing the classic table output format. -/// -/// `Ordinary` provides a standard implementation for table formatting, -/// supporting a classic style with default settings. -/// -/// # Example -/// -/// ```text -/// sid | sname | gap -/// -----+-------+----- -/// 3 | Alice | 5 -/// 6 | Joe | 1 -/// 10 | Boris | 5 -/// ``` -#[ derive( Debug ) ] -pub struct Ordinary -{ - /// Delimitting header with grid line or not. - pub delimitting_header : bool, - /// Prefix added to each cell. - pub cell_prefix : String, - /// Postfix added to each cell. - pub cell_postfix : String, - /// Separator used between table columns. - pub cell_separator : String, - /// Prefix added to each row. - pub row_prefix : String, - /// Postfix added to each row. - pub row_postfix : String, - /// Separator used between rows. - pub row_separator : String, - /// Horizontal line character. - pub h : char, - /// Vertical line character. - pub v : char, - /// Left T-junction character. - pub t_l : char, - /// Right T-junction character. - pub t_r : char, - /// Top T-junction character. - pub t_t : char, - /// Bottom T-junction character. - pub t_b : char, - /// Cross junction character. - pub cross : char, - /// Top-left corner character. - pub corner_lt : char, - /// Top-right corner character. - pub corner_rt : char, - /// Bottom-left corner character. - pub corner_lb : char, - /// Bottom-right corner character. - pub corner_rb : char, -} - -impl Default for Ordinary -{ - fn default() -> Self - { - - let delimitting_header = true; - - let cell_prefix = "".to_string(); - let cell_postfix = "".to_string(); - let cell_separator = " │ ".to_string(); - let row_prefix = "│ ".to_string(); - let row_postfix = " │".to_string(); - let row_separator = "\n".to_string(); - - let h = '─'; - let v = '|'; - let t_l = '├'; - let t_r = '┤'; - let t_t = '┬'; - let t_b = '┴'; - let cross = '┼'; - let corner_lt = '┌'; - let corner_rt = '┐'; - let corner_lb = '└'; - let corner_rb = '┘'; - - Self - { - delimitting_header, - cell_prefix, - cell_postfix, - cell_separator, - row_prefix, - row_postfix, - row_separator, - h, - v, - t_l, - t_r, - t_t, - t_b, - cross, - corner_lt, - corner_rt, - corner_lb, - corner_rb, - } - } -} - -impl Default for &'static Ordinary -{ - fn default() -> Self - { - // qqq : find a better solution - static STYLES : OnceLock< Ordinary > = OnceLock::new(); - STYLES.get_or_init( || - { - Ordinary::default() - }) - } -} - -impl Ordinary -{ - - /// Returns a reference to a static instance of `Ordinary`. - /// - /// This method provides access to a single shared instance of `Ordinary`, - /// ensuring efficient reuse of the classic table output format. - pub fn instance() -> & 'static dyn TableOutputFormat - { - - static INSTANCE : OnceLock< Ordinary > = OnceLock::new(); - INSTANCE.get_or_init( || - { - Self::default() - }) - - } -} - -impl TableOutputFormat for Ordinary -{ - fn extract_write< 'buf, 'data >( &self, x : &InputExtract< 'data >, c : &mut Context< 'buf > ) -> fmt::Result - { - use md_math::MdOffset; - - let cell_prefix = &self.cell_prefix; - let cell_postfix = &self.cell_postfix; - let cell_separator = &self.cell_separator; - let row_prefix = &self.row_prefix; - let row_postfix = &self.row_postfix; - let row_separator = &self.row_separator; - let h = self.h.to_string(); - - let mut delimitting_header = self.delimitting_header; - let row_width = if delimitting_header - { - let mut grid_width = x.mcells_vis[ 0 ] * ( cell_prefix.chars().count() + cell_postfix.chars().count() ); - grid_width += row_prefix.chars().count() + row_postfix.chars().count(); - if x.mcells_vis[ 0 ] > 0 - { - grid_width += ( x.mcells_vis[ 0 ] - 1 ) * ( cell_separator.chars().count() ); - } - x.mchars[ 0 ] + grid_width - } - else - { - 0 - }; - let mut prev_typ : Option< LineType > = None; - - // dbg!( x.row_descriptors.len() ); - - for ( irow, row ) in x.row_descriptors.iter().enumerate() - { - let height = row.height; - - if delimitting_header - { - if let Some( prev_typ ) = prev_typ - { - if prev_typ == LineType::Header && row.typ == LineType::Regular - { - write!( c.buf, "{}", row_separator )?; - write!( c.buf, "{}", h.repeat( row_width ) )?; - delimitting_header = false - } - } - if row.vis - { - prev_typ = Some( row.typ ); - } - } - - if !row.vis - { - continue; - } - - // dbg!( row.height ); - - for islice in 0..height - { - - if irow > 0 - { - write!( c.buf, "{}", row_separator )?; - } - - write!( c.buf, "{}", row_prefix )?; - - for icol in 0 .. x.col_descriptors.len() - { - let col = &x.col_descriptors[ icol ]; - let cell_width = x.data[ irow ][ icol ].1[0]; - let width = col.width; - let md_index = [ islice, icol, irow as usize ]; - let slice = x.slices[ x.slices_dim.md_offset( md_index ) ]; - - // println!( "md_index : {md_index:?} | md_offset : {} | slice : {slice}", x.slices_dim.md_offset( md_index ) ); - - if icol > 0 - { - write!( c.buf, "{}", cell_separator )?; - } - - write!( c.buf, "{}", cell_prefix )?; - - // println!( "icol : {icol} | irow : {irow} | width : {width} | cell_width : {cell_width}" ); - let lspaces = ( width - cell_width ) / 2; - let rspaces = ( width - cell_width + 1 ) / 2 + cell_width - slice.len(); - // println!( "icol : {icol} | irow : {irow} | width : {width} | cell_width : {cell_width} | lspaces : {lspaces} | rspaces : {rspaces}" ); - - if lspaces > 0 - { - write!( c.buf, "{: 0 - { - write!( c.buf, "{:>width$}", " ", width = rspaces )?; - } - - write!( c.buf, "{}", cell_postfix )?; - } - - write!( c.buf, "{}", row_postfix )?; - } - - } - - Ok(()) - } -} diff --git a/module/core/format_tools/src/format/output_format/records.rs b/module/core/format_tools/src/format/output_format/records.rs index 263177edb8..3be07a9e83 100644 --- a/module/core/format_tools/src/format/output_format/records.rs +++ b/module/core/format_tools/src/format/output_format/records.rs @@ -22,16 +22,13 @@ //! use crate::*; -use md_math::MdOffset; use print:: { InputExtract, Context, }; -use core:: -{ - fmt, -}; +use std::borrow::Cow; +use core::fmt; use std::sync::OnceLock; /// A struct representing the list of records( rows ) output format. @@ -39,7 +36,51 @@ use std::sync::OnceLock; /// `Records` provides an implementation for table formatting that outputs /// each row as a separate table with 2 columns, first is name of column in the original data and second is cell value itself. #[derive( Debug )] -pub struct Records; +pub struct Records +{ + /// Prefix added to each row. + pub table_prefix : String, + /// Postfix added to each row. + pub table_postfix : String, + /// Separator used between rows. + pub table_separator : String, + /// Prefix added to each row. + pub row_prefix : String, + /// Postfix added to each row. + pub row_postfix : String, + /// Separator used between rows. + pub row_separator : String, + /// Prefix added to each cell. + pub cell_prefix : String, + /// Postfix added to each cell. + pub cell_postfix : String, + /// Separator used between table columns. + pub cell_separator : String, + /// Limit table width. If the value is zero, then no limitation. + pub max_width: usize, + // /// Horizontal line character. + // pub h : char, + // /// Vertical line character. + // pub v : char, + // /// Left T-junction character. + // pub t_l : char, + // /// Right T-junction character. + // pub t_r : char, + // /// Top T-junction character. + // pub t_t : char, + // /// Bottom T-junction character. + // pub t_b : char, + // /// Cross junction character. + // pub cross : char, + // /// Top-left corner character. + // pub corner_lt : char, + // /// Top-right corner character. + // pub corner_rt : char, + // /// Bottom-left corner character. + // pub corner_lb : char, + // /// Bottom-right corner character. + // pub corner_rb : char, +} impl Records { @@ -47,7 +88,26 @@ impl Records pub fn instance() -> & 'static dyn TableOutputFormat { static INSTANCE : OnceLock< Records > = OnceLock::new(); - INSTANCE.get_or_init( || Records ) + INSTANCE.get_or_init( || Records::default() ) + } + + /// Calculate how much space is minimally needed in order to generate an output with this output formatter. + /// It will be impossible to render tables smaller than the result of `min_width()`. + /// + /// This function is similar to `output_format::Table::min_width`, but it does not contain a + /// `column_count` as it always equal to 2, and it aslo uses the `output_format::Records` + /// style parameters. + pub fn min_width + ( + &self, + ) -> usize + { + // 2 is used here, because `Records` displays 2 columns: keys and values. + self.row_prefix.chars().count() + + self.row_postfix.chars().count() + + 2 * ( self.cell_postfix.chars().count() + self.cell_prefix.chars().count() ) + + self.cell_separator.chars().count() + + 2 } } @@ -55,36 +115,149 @@ impl Default for Records { fn default() -> Self { + + let cell_prefix = "".to_string(); + let cell_postfix = "".to_string(); + let cell_separator = " │ ".to_string(); + let row_prefix = "│ ".to_string(); + let row_postfix = " │".to_string(); + let row_separator = "\n".to_string(); + let table_prefix = "".to_string(); + let table_postfix = "".to_string(); + let table_separator = "\n".to_string(); + + let max_width = 0; + + // let h = '─'; + // let v = '|'; + // let t_l = '├'; + // let t_r = '┤'; + // let t_t = '┬'; + // let t_b = '┴'; + // let cross = '┼'; + // let corner_lt = '┌'; + // let corner_rt = '┐'; + // let corner_lb = '└'; + // let corner_rb = '┘'; + Self { + table_prefix, + table_postfix, + table_separator, + row_prefix, + row_postfix, + row_separator, + cell_prefix, + cell_postfix, + cell_separator, + max_width, + // h, + // v, + // t_l, + // t_r, + // t_t, + // t_b, + // cross, + // corner_lt, + // corner_rt, + // corner_lb, + // corner_rb, } } } impl TableOutputFormat for Records { - fn extract_write< 'buf, 'data > - ( + + fn extract_write< 'buf, 'data >( & self, x : & InputExtract< 'data >, c : & mut Context< 'buf >, ) -> fmt::Result { - for ( i, row ) in x.row_descriptors.iter().enumerate() + use format::text_wrap::{ text_wrap, width_calculate }; + + if self.max_width != 0 && self.max_width < self.min_width() + { + return Err( fmt::Error ); + } + + // 2 because there are only 2 columns: key and value. + let columns_max_width = if self.max_width == 0 { 0 } else { self.max_width - self.min_width() + 2 }; + + let keys : Vec< ( Cow< 'data, str >, [ usize; 2 ] ) > = x.header().collect(); + let keys_width = width_calculate( &keys ); + + write!( c.buf, "{}", self.table_prefix )?; + + let mut printed_tables_count = 0; + + for ( itable_descriptor, table_descriptor ) in x.row_descriptors.iter().enumerate() { - if !row.vis + if !table_descriptor.vis || ( x.has_header && itable_descriptor == 0 ) { continue; } - writeln!( c.buf, "-[ RECORD {} ]", i + 1 )?; - for ( icol, col ) in x.col_descriptors.iter().enumerate() + + if printed_tables_count > 0 + { + write!( c.buf, "{}", self.table_separator )?; + } + + printed_tables_count += 1; + + writeln!( c.buf, " = {}", table_descriptor.irow )?; + + let values = &x.data[ itable_descriptor ]; + let values_width = width_calculate( &values ); + + let table_for_wrapping : Vec< Vec< ( Cow< 'data, str >, [ usize; 2] ) > > = + keys.iter().enumerate().map( | ( ikey, key ) | + { + vec![ key.clone(), values[ ikey ].clone() ] + }).collect(); + + let wrapped_text = text_wrap + ( + table_for_wrapping.iter(), + &[ keys_width, values_width ], + columns_max_width, + keys_width + values_width, + ); + + for ( irow, cols ) in wrapped_text.data.into_iter().enumerate() { - // let cell_width = x.data[ i ][ icol ].1[ 0 ]; - let md_index = [ 0, icol, i ]; - let slice = x.slices[ x.slices_dim.md_offset( md_index ) ]; - writeln!( c.buf, "{} | {}", col.width, slice )?; + if irow != 0 + { + write!( c.buf, "{}", self.row_separator )?; + } + + let key = &cols[ 0 ]; + let value = &cols[ 1 ]; + + let key_width = wrapped_text.column_widthes[ 0 ]; + let value_width = wrapped_text.column_widthes[ 1 ]; + + write!( c.buf, "{}", self.row_prefix )?; + + write!( c.buf, "{}", self.cell_prefix )?; + write!( c.buf, "{: Self + { + + let delimitting_header = true; + + let cell_prefix = "".to_string(); + let cell_postfix = "".to_string(); + let cell_separator = " │ ".to_string(); + let row_prefix = "│ ".to_string(); + let row_postfix = " │".to_string(); + let row_separator = "\n".to_string(); + + let h = '─'; + let v = '|'; + let t_l = '├'; + let t_r = '┤'; + let t_t = '┬'; + let t_b = '┴'; + let cross = '┼'; + let corner_lt = '┌'; + let corner_rt = '┐'; + let corner_lb = '└'; + let corner_rb = '┘'; + let max_width = 0; + + Self + { + delimitting_header, + cell_prefix, + cell_postfix, + cell_separator, + row_prefix, + row_postfix, + row_separator, + h, + v, + t_l, + t_r, + t_t, + t_b, + cross, + corner_lt, + corner_rt, + corner_lb, + corner_rb, + max_width + } + } +} + +impl Default for &'static Table +{ + fn default() -> Self + { + // qqq : find a better solution + static STYLES : OnceLock< Table > = OnceLock::new(); + STYLES.get_or_init( || + { + Table::default() + }) + } +} + +impl Table +{ + + /// Returns a reference to a static instance of `Table`. + /// + /// This method provides access to a single shared instance of `Table`, + /// ensuring efficient reuse of the classic table output format. + pub fn instance() -> & 'static dyn TableOutputFormat + { + + static INSTANCE : OnceLock< Table > = OnceLock::new(); + INSTANCE.get_or_init( || + { + Self::default() + }) + + } + + /// Calculate how much space is minimally needed in order to generate a table output with the specified + /// number of columns. It will be impossible to render table smaller than the result of + /// `min_width()`. + /// + /// This function is similar to `output_format::Records::min_width`, but it contains a `column_count` + /// parameter, and it aslo uses the `output_format::Table` style parameters. + pub fn min_width + ( + &self, + column_count : usize, + ) -> usize + { + self.row_prefix.chars().count() + + self.row_postfix.chars().count() + + column_count * ( self.cell_postfix.chars().count() + self.cell_prefix.chars().count() ) + + if column_count == 0 { 0 } else { ( column_count - 1 ) * self.cell_separator.chars().count() } + + column_count + } +} + +impl TableOutputFormat for Table +{ + fn extract_write< 'buf, 'data >( &self, x : &InputExtract< 'data >, c : &mut Context< 'buf > ) -> fmt::Result + { + use format::text_wrap::text_wrap; + + let cell_prefix = &self.cell_prefix; + let cell_postfix = &self.cell_postfix; + let cell_separator = &self.cell_separator; + let row_prefix = &self.row_prefix; + let row_postfix = &self.row_postfix; + let row_separator = &self.row_separator; + let h = self.h.to_string(); + + let column_count = x.col_descriptors.len(); + + if self.max_width != 0 && ( self.min_width( column_count ) > self.max_width ) + { + return Err( fmt::Error ); + } + + let columns_nowrap_width = x.col_descriptors.iter().map( |c| c.width ).sum::(); + let visual_elements_width = self.min_width( column_count ) - column_count; + + let filtered_data = x.row_descriptors.iter().filter_map( | r | + { + if r.vis + { + Some( &x.data[ r.irow ] ) + } + else + { + None + } + }); + + let wrapped_text = text_wrap + ( + filtered_data, + x.col_descriptors.iter().map( | c | c.width ).collect::< Vec< usize > >(), + if self.max_width == 0 { 0 } else { self.max_width - visual_elements_width }, + columns_nowrap_width + ); + + let new_columns_widthes = wrapped_text.column_widthes.iter().sum::(); + let new_row_width = new_columns_widthes + visual_elements_width; + + let mut printed_row_count = 0; + + for row in wrapped_text.data.iter() + { + if printed_row_count == wrapped_text.first_row_height && x.has_header && self.delimitting_header + { + write!( c.buf, "{}", row_separator )?; + write!( c.buf, "{}", h.repeat( new_row_width ) )?; + } + + if printed_row_count > 0 + { + write!( c.buf, "{}", row_separator )?; + } + + printed_row_count += 1; + + write!( c.buf, "{}", row_prefix )?; + + for ( icol, col ) in row.iter().enumerate() + { + let cell_wrapped_width = col.wrap_width; + let column_width = wrapped_text.column_widthes[ icol ]; + let slice_width = col.content.chars().count(); + + if icol > 0 + { + write!( c.buf, "{}", cell_separator )?; + } + + write!( c.buf, "{}", cell_prefix )?; + + let lspaces = column_width.saturating_sub( cell_wrapped_width ) / 2; + let rspaces = ( ( column_width.saturating_sub( cell_wrapped_width ) as f32 / 2 as f32 ) ).round() as usize + cell_wrapped_width.saturating_sub(slice_width); + + if lspaces > 0 + { + write!( c.buf, "{: 0 + { + write!( c.buf, "{:>width$}", " ", width = rspaces )?; + } + + write!( c.buf, "{}", cell_postfix )?; + } + + write!( c.buf, "{}", row_postfix )?; + } + + Ok(()) + } +} \ No newline at end of file diff --git a/module/core/format_tools/src/format/print.rs b/module/core/format_tools/src/format/print.rs index e02746d3dc..f5c63caf2f 100644 --- a/module/core/format_tools/src/format/print.rs +++ b/module/core/format_tools/src/format/print.rs @@ -2,14 +2,14 @@ //! Print data as table. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::*; use std:: { - borrow::Cow, + borrow::{ Cow, Borrow }, collections::HashMap, }; use core:: @@ -41,6 +41,7 @@ mod private /// used to add a consistent end to each row. /// /// ``` + // xxx : enable // #[ derive( Debug, Former ) ] // #[ derive( Debug ) ] @@ -83,7 +84,7 @@ mod private // .field( "row_prefix", & self.row_prefix ) // .field( "row_postfix", & self.row_postfix ) // .field( "row_separator", & self.row_separator ) - // .field( "output_format", & format_args!( "{:?}", self.output_format ) ) // xxx + // .field( "output_format", & format_args!( "{:?}", self.output_format ) ) // .field( "filter_col", & format_args!( "{:?}", self.filter_col ) ) .finish() } @@ -164,7 +165,7 @@ mod private /// A `String` containing the formatted table. fn table_to_string( &'data self ) -> String { - self.table_to_string_with_format( &output_format::Ordinary::default() ) + self.table_to_string_with_format( &output_format::Table::default() ) } /// Converts the table to a string representation specifying printer. @@ -195,15 +196,15 @@ mod private } /// A trait for formatting tables. - impl< 'data, T, RowKey, Row, CellKey, CellRepr > TableFormatter< 'data > - for AsTable< 'data, T, RowKey, Row, CellKey, CellRepr > + impl< 'data, T, RowKey, Row, CellKey> TableFormatter< 'data > + for AsTable< 'data, T, RowKey, Row, CellKey> where - Self : TableRows< CellKey = CellKey, CellRepr = CellRepr, RowKey = RowKey, Row = Row >, + Self : TableRows< CellKey = CellKey, RowKey = RowKey, Row = Row >, Self : TableHeader< CellKey = CellKey >, RowKey : table::RowKey, - Row : Cells< CellKey, CellRepr >, + Row : Cells< CellKey>, CellKey : table::CellKey + ?Sized, - CellRepr : table::CellRepr, + // CellRepr : table::CellRepr, { fn fmt< 'a >( &'data self, c : &mut Context< 'a > ) -> fmt::Result @@ -228,19 +229,29 @@ mod private #[ derive( Debug, Default ) ] pub struct RowDescriptor { + + + /// Index of the row. pub irow : usize, + /// Height of the row. pub height : usize, + /// Type of the line: header or regular. pub typ : LineType, + /// Visibility of the row. pub vis : bool, } /// A struct for extracting and organizing row of table data for formatting. #[ derive( Debug, Default ) ] - pub struct ColDescriptor + pub struct ColDescriptor< 'label > { + /// Index of the column. pub icol : usize, + /// Column width. pub width : usize, + /// Label of the column. + pub label : &'label str, } /// A struct for extracting and organizing table data for formatting. @@ -270,23 +281,14 @@ mod private /// Descriptors for each column, including optional title, width, and index. // width, index - // pub col_descriptors : Vec< ( usize, usize ) >, - pub col_descriptors : Vec< ColDescriptor >, + pub col_descriptors : Vec< ColDescriptor< 'data > >, /// Descriptors for each row, including height. - // height - // pub row_descriptors : Vec< ( usize, ) >, pub row_descriptors : Vec< RowDescriptor >, /// Extracted data for each cell, including string content and size. // string, size, - pub data : Vec< Vec< ( Cow< 'data, str >, [ usize ; 2 ] ) > >, - - /// Dimensions of slices for retrieving data from multi-matrix. - pub slices_dim : [ usize ; 3 ], - - /// Extracted slices or strings for further processing. - pub slices : Vec< &'data str >, + pub data : Vec< Vec< ( Cow< 'data, str >, [ usize ; 2 ] ) > >, // xxx : use maybe flat vector } @@ -295,45 +297,168 @@ mod private impl< 'data > InputExtract< 'data > { + /// Returns an iterator over the row descriptors, skipping the header if present. + /// + /// This function provides an iterator that yields each row descriptor along with its index. + /// If the table has a header, the first row is skipped, ensuring that iteration starts from + /// the first data row. + /// + /// # Returns + /// + /// An iterator over tuples containing: + /// - `usize`: The index of the row. + /// - `&RowDescriptor`: A reference to the row descriptor. + /// + pub fn rows( & self ) -> impl _IteratorTrait< Item = ( usize, &RowDescriptor ) > + { + self.row_descriptors + .iter() + .enumerate() + .skip( if self.has_header { 1 } else { 0 } ) + } + + /// Returns an iterator over the header cells, or a default value if no header is present. + /// + /// This function provides an iterator that yields each cell in the header row. If the table + /// does not have a header, it returns an iterator over default values, which are empty strings + /// with a size of `[0, 1]`. + /// + /// # Returns + /// + /// A boxed iterator yielding tuples containing: + /// - `Cow<'data, str>`: A clone-on-write string representing the cell content. + /// - `[usize; 2]`: An array representing the size of the cell. + /// + pub fn header( & self ) -> Box< dyn Iterator< Item = ( Cow< 'data, str >, [ usize ; 2 ] ) > + '_ > + { + if self.has_header + { + Box::new( self.data[ 0 ].iter().cloned() ) + } + else + { + Box::new( std::iter::repeat( ( Cow::Borrowed( "" ), [ 0, 1 ] ) ).take( self.mcells[ 0 ] ) ) + } + } + + /// Returns a slice from the header, or an empty string if no header is present. + /// + /// # Arguments + /// + /// - `icol`: The column index within the header row. + /// + /// # Returns + /// + /// A string slice representing the header content. + /// + pub fn header_slice( & self, icol : usize ) -> & str + { + if self.has_header + { + self.data[ 0 ][ icol ].0.borrow() + } + else + { + "" + } + } + + /// Extract input data from and collect it in a format consumable by output formatter. - pub fn extract< 't, 'context, Table, RowKey, Row, CellKey, CellRepr > + pub fn extract< 'context, Table, RowKey, Row, CellKey> ( - table : &'t Table, + table : &'data Table, filter_col : &'context ( dyn FilterCol + 'context ), filter_row : &'context ( dyn FilterRow + 'context ), callback : impl for< 'a2 > FnOnce( &'a2 InputExtract< 'a2 > ) -> fmt::Result, ) -> fmt::Result where - 'data : 't, - Table : TableRows< RowKey = RowKey, Row = Row, CellKey = CellKey, CellRepr = CellRepr >, + Table : TableRows< RowKey = RowKey, Row = Row, CellKey = CellKey >, Table : TableHeader< CellKey = CellKey >, RowKey : table::RowKey, - Row : Cells< CellKey, CellRepr > + 'data, + Row : Cells< CellKey > + 'data, + Row : Cells< CellKey > + 'data, CellKey : table::CellKey + ?Sized + 'data, - CellRepr : table::CellRepr, + // CellRepr : table::CellRepr, { - use md_math::MdOffset; + let mut key_to_ikey : HashMap< Cow< 'data, str >, usize > = HashMap::new(); + let mut keys_count = 0; + + let rows = table.rows().map( | r | + { + let mut unsorted : Vec< ( usize, Cow< 'data, str > ) > = r.cells().map( | ( key, c ) | + { + if !key_to_ikey.contains_key( key.borrow() ) + { + key_to_ikey.insert( key.borrow().into(), keys_count ); + keys_count += 1; + } + + ( key_to_ikey[ key.borrow() ], c.unwrap_or( Cow::from( "" ) ) ) + } ).collect(); + + unsorted.sort_by( | ( i1, _ ), ( i2, _ ) | i1.cmp(i2) ); + unsorted.into_iter().map( | ( _, c ) | c).collect() + } ).collect(); + + let has_header = table.header().is_some(); + + let column_names = match table.header() + { + Some( header ) => header.map( | ( k, _ ) | Cow::from( k.borrow() ) ).collect(), + + None => match table.rows().next() + { + Some( r ) => r.cells().map( | ( k, _ ) | Cow::from( k.borrow() ) ).collect(), + None => Vec::new() + } + }; + + Self::extract_from_raw_table + ( + column_names, + has_header, + rows, + filter_col, + filter_row, + callback, + ) + } + + /// Extract input data from a table that is constructed with vectors and `Cow`s and collect + /// it in a format consumable by output formatter. + /// + /// `rows` should not contain header of the table, it will be automatically added if `has_header` + /// is true. + pub fn extract_from_raw_table< 'context > + ( + column_names : Vec< Cow< 'data, str > >, + has_header : bool, + rows : Vec< Vec< Cow< 'data, str > > >, + filter_col : &'context ( dyn FilterCol + 'context ), + filter_row : &'context ( dyn FilterRow + 'context ), + callback : impl for< 'a2 > FnOnce( &'a2 InputExtract< 'a2 > ) -> fmt::Result, + ) -> fmt::Result + { // let mcells = table.mcells(); let mut mcells_vis = [ 0 ; 2 ]; let mut mcells = [ 0 ; 2 ]; let mut mchars = [ 0 ; 2 ]; // key width, index - let mut key_to_ikey : HashMap< &'t CellKey, usize > = HashMap::new(); + let mut key_to_ikey : HashMap< Cow< 'data, str >, usize > = HashMap::new(); - let mut col_descriptors : Vec< ColDescriptor > = Vec::with_capacity( mcells[ 0 ] ); + let mut col_descriptors : Vec< ColDescriptor< '_ > > = Vec::with_capacity( mcells[ 0 ] ); let mut row_descriptors : Vec< RowDescriptor > = Vec::with_capacity( mcells[ 1 ] ); - let mut has_header = false; - let mut data : Vec< Vec< ( Cow< 't, str >, [ usize ; 2 ] ) > > = Vec::new(); - let rows = table.rows(); + let mut data : Vec< Vec< ( Cow< 'data, str >, [ usize ; 2 ] ) > > = Vec::new(); let mut irow : usize = 0; let filter_col_need_args = filter_col.need_args(); // let filter_row_need_args = filter_row.need_args(); - let mut row_add = | row_iter : &'_ mut dyn _IteratorTrait< Item = ( &'t CellKey, Cow< 't, str > ) >, typ : LineType | + let mut row_add = | row_data : Vec< Cow< 'data, str > >, typ : LineType | { irow = row_descriptors.len(); @@ -343,18 +468,21 @@ mod private let mut ncol = 0; let mut ncol_vis = 0; - let fields : Vec< ( Cow< 't, str >, [ usize ; 2 ] ) > = row_iter + let fields : Vec< ( Cow< 'data, str >, [ usize ; 2 ] ) > = row_data + .into_iter() + .enumerate() .filter_map ( - | ( key, val ) | + | ( ikey, val ) | { + let key = &column_names[ ikey ]; let l = col_descriptors.len(); ncol += 1; if filter_col_need_args { - if !filter_col.filter_col( key.borrow() ) + if !filter_col.filter_col( key.as_ref() ) { return None; } @@ -372,17 +500,18 @@ mod private let sz = string::size( &val ); key_to_ikey - .entry( key ) + .entry( key.clone() ) .and_modify( | icol | { let col = &mut col_descriptors[ *icol ]; col.width = col.width.max( sz[ 0 ] ); + col.label = ""; }) .or_insert_with( || { let icol = l; let width = sz[ 0 ]; - let col = ColDescriptor { width, icol }; + let col = ColDescriptor { width, icol, label : "" }; col_descriptors.push( col ); icol }); @@ -410,18 +539,9 @@ mod private // process header first - if let Some( header ) = table.header() + if has_header { - rows.len().checked_add( 1 ).expect( "Table has too many rows" ); - // assert!( header.len() <= usize::MAX, "Header of a table has too many cells" ); - has_header = true; - - let mut row2 = header.map( | ( key, title ) | - { - ( key, Cow::Borrowed( title ) ) - }); - - row_add( &mut row2, LineType::Header ); + row_add( column_names.clone(), LineType::Header ); } // Collect rows @@ -430,30 +550,7 @@ mod private { // assert!( row.cells().len() <= usize::MAX, "Row of a table has too many cells" ); - let mut row2 = row - .cells() - .map - ( - | ( key, val ) | - { - - let val = match val.0 - { - Some( val ) => - { - val - } - None => - { - Cow::Borrowed( "" ) - } - }; - - return ( key, val ); - } - ); - - row_add( &mut row2, LineType::Regular ); + row_add( row, LineType::Regular ); } // calculate size in chars @@ -461,22 +558,6 @@ mod private mchars[ 0 ] = col_descriptors.iter().fold( 0, | acc, col | acc + col.width ); mchars[ 1 ] = row_descriptors.iter().fold( 0, | acc, row | acc + if row.vis { row.height } else { 0 } ); - // cook slices multi-matrix - - let mut slices_dim = [ 1, mcells[ 0 ], mcells[ 1 ] ]; - slices_dim[ 0 ] = row_descriptors - .iter() - .fold( 0, | acc : usize, row | acc.max( row.height ) ) - ; - - let slices_len = slices_dim[ 0 ] * slices_dim[ 1 ] * slices_dim[ 2 ]; - let slices : Vec< &str > = vec![ "" ; slices_len ]; - - // assert_eq!( mcells, mcells, r#"Incorrect multidimensional size of table - // mcells <> mcells - // {mcells:?} <> {mcells:?}"# ); - // println!( "mcells : {mcells:?} | mcells : {mcells:?} | mcells_vis : {mcells_vis:?}" ); - let mut x = InputExtract::< '_ > { mcells, @@ -486,39 +567,16 @@ mod private row_descriptors, data, has_header, - slices_dim, - slices, }; - // extract slices - - let mut slices : Vec< &str > = vec![]; - std::mem::swap( &mut x.slices, &mut slices ); - - let mut irow : isize = -1; - - for row_data in x.data.iter() + if x.data.len() > 0 { - - irow += 1; - for icol in 0 .. x.col_descriptors.len() { - let cell = &row_data[ icol ]; - string::lines( cell.0.as_ref() ) - .enumerate() - .for_each( | ( layer, s ) | - { - let md_index = [ layer, icol, irow as usize ]; - slices[ x.slices_dim.md_offset( md_index ) ] = s; - }) - ; + x.col_descriptors[ icol ].label = x.data[ 0 ][ icol ].0.as_ref(); } - } - std::mem::swap( &mut x.slices, &mut slices ); - return callback( &x ); } @@ -543,6 +601,8 @@ pub mod own Context, Printer, InputExtract, + RowDescriptor, + ColDescriptor, }; } diff --git a/module/core/format_tools/src/format/string.rs b/module/core/format_tools/src/format/string.rs index 511f44c473..8f7032c9d5 100644 --- a/module/core/format_tools/src/format/string.rs +++ b/module/core/format_tools/src/format/string.rs @@ -4,10 +4,9 @@ // xxx : move to crate string_tools -/// Internal namespace. +/// Define a private namespace for all its items. mod private { - // use crate::*; /// Returns the size of the text in `src` as a `[ width, height ]` array. @@ -74,7 +73,7 @@ mod private for line in lines( text ) { height += 1; - let line_length = line.chars().count(); + let line_length = line.as_bytes().len(); if line_length > width { width = line_length; @@ -114,6 +113,47 @@ mod private Lines::new( src.as_ref() ) } + /// Returns an iterator over the lines of a string slice with text wrapping. + /// + /// This function provides an iterator that yields each line of the input string slice. + /// It is based on previous iterator `lines` but it also includes text wrapping that is + /// controlled via `limit_width` argument. If the string contains a trailing new line, + /// then an empty string will be yielded in this iterator. + /// + /// # Arguments + /// + /// * `src` - A reference to a type that can be converted to a string slice. This allows + /// for flexibility in passing various string-like types. + /// + /// * `limit_width` - text wrapping limit. Lines that are longer than this parameter will + // be split into smaller lines. + /// + /// # Returns + /// + /// An iterator of type `LinesWithLimit` that yields each line as a `&str`. + /// + /// # Examples + /// + /// ``` + /// let text = "Hello\nWorld\n"; + /// let mut lines = format_tools::string::lines_with_limit( text, 3 ); + /// assert_eq!( lines.next(), Some( "Hel" ) ); + /// assert_eq!( lines.next(), Some( "lo" ) ); + /// assert_eq!( lines.next(), Some( "Wor" ) ); + /// assert_eq!( lines.next(), Some( "ld" ) ); + /// assert_eq!( lines.next(), Some( "" ) ); + /// assert_eq!( lines.next(), None ); + /// ``` + pub fn lines_with_limit< S : AsRef< str > + ?Sized > + ( + src : & S, + limit_width : usize + ) + -> LinesWithLimit< '_ > + { + LinesWithLimit::new( src.as_ref(), limit_width ) + } + /// An iterator over the lines of a string slice. /// /// This struct implements the `Iterator` trait, allowing you to iterate over the lines @@ -128,6 +168,7 @@ mod private has_trailing_newline : bool, finished : bool, } + impl< 'a > Lines< 'a > { fn new( input : &'a str ) -> Self @@ -172,6 +213,96 @@ mod private } } + /// An iterator over the lines of a string slice with text wrapping. + /// + /// This struct implements the `Iterator` trait, allowing you to iterate over the parts + /// of a string. It uses `Lines` iterator and splits lines if they are longer that the + /// `limit_width` parameter. If the string contains a trailing new line, then an empty + /// string will be yielded in this iterator. + /// + /// If `limit_width` is equal to 0, then no wrapping is applied, and behaviour of this + /// iterator is equals to `Lines` iterator. + #[ derive( Debug ) ] + pub struct LinesWithLimit< 'a > + { + lines : Lines< 'a >, + limit_width : usize, + cur : Option< &'a str >, + } + + impl< 'a > LinesWithLimit< 'a > + { + fn new( input : &'a str, limit_width : usize ) -> Self + { + LinesWithLimit + { + lines : lines( input ), + limit_width, + cur : None, + } + } + } + + impl< 'a > Iterator for LinesWithLimit< 'a > + { + type Item = &'a str; + + fn next( &mut self ) -> Option< Self::Item > + { + loop + { + let s = match self.cur + { + Some( st ) if !st.is_empty() => st, + + _ => + { + let next_line = self.lines.next()?; + if next_line.is_empty() + { + self.cur = None; + return Some( "" ); + } + else + { + self.cur = Some( next_line ); + continue; + } + } + }; + + if self.limit_width == 0 + { + self.cur = None; + return Some( s ); + } + + let mut boundary_byte_index = s.len(); + let mut char_count = 0; + for ( byte_i, _ch ) in s.char_indices() + { + if char_count == self.limit_width + { + boundary_byte_index = byte_i; + break; + } + char_count += 1; + } + + let chunk = &s[ ..boundary_byte_index ]; + let rest = &s[ boundary_byte_index.. ]; + + match rest.is_empty() + { + true => self.cur = None, + false => self.cur = Some( rest ) + }; + + return Some( chunk ); + } + } +} + } #[ allow( unused_imports ) ] @@ -191,6 +322,8 @@ pub mod own size, lines, Lines, + lines_with_limit, + LinesWithLimit, }; } diff --git a/module/core/format_tools/src/format/table.rs b/module/core/format_tools/src/format/table.rs index 9ea6b1aecd..1fab2ab744 100644 --- a/module/core/format_tools/src/format/table.rs +++ b/module/core/format_tools/src/format/table.rs @@ -2,7 +2,7 @@ //! Table interface. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { @@ -12,7 +12,11 @@ mod private // fmt, borrow::Borrow, }; - // use std::borrow::Cow; + use std:: + { + borrow::Cow, + collections::HashMap, + }; use reflect_tools:: { IteratorTrait, @@ -72,26 +76,37 @@ mod private // = - /// Marker trait to tag structures for whcih table trait deducing should be done from trait Fields, which is reflection. + /// Marker trait to tag structures for which table trait deducing should be done from trait Fields, which is reflection. pub trait TableWithFields {} // = /// A trait for iterating over all cells of a row. - pub trait Cells< CellKey, CellRepr > + pub trait Cells< CellKey > where - CellRepr : table::CellRepr, + // CellRepr : table::CellRepr, CellKey : table::CellKey + ?Sized, { /// Returns an iterator over all cells of the row. - fn cells< 'a, 'b >( &'a self ) -> impl IteratorTrait< Item = ( &'b CellKey, OptionalCow< 'b, str, CellRepr > ) > + // fn cells< 'a, 'b >( &'a self ) -> impl IteratorTrait< Item = ( &'b CellKey, OptionalCow< 'b, str > ) > + fn cells< 'a, 'b >( &'a self ) -> impl IteratorTrait< Item = ( &'b CellKey, Option< Cow< 'b, str > > ) > where 'a : 'b, CellKey : 'b, ; } - impl< Row, CellKey, CellRepr > Cells< CellKey, CellRepr > + impl Cells< str > for HashMap< String, String > + { + fn cells< 'a, 'b >( &'a self ) -> impl IteratorTrait< Item = ( &'b str, Option< Cow< 'b, str > > ) > + where + 'a : 'b, + { + self.iter().map( | ( k, v ) | ( k.as_str(), Some( Cow::from( v ) ) ) ) + } + } + + impl< Row, CellKey > Cells< CellKey > for Row where CellKey : table::CellKey + ?Sized, @@ -99,14 +114,17 @@ mod private Row : TableWithFields + Fields < &'ckv CellKey, - OptionalCow< 'ckv, str, CellRepr >, + // OptionalCow< 'ckv, str >, + Option< Cow< 'ckv, str > >, Key< 'ckv > = &'ckv CellKey, - Val< 'ckv > = OptionalCow< 'ckv, str, CellRepr >, + // Val< 'ckv > = OptionalCow< 'ckv, str >, + Val< 'ckv > = Option< Cow< 'ckv, str > >, > + 'ckv, // xxx - CellRepr : table::CellRepr, + // CellRepr : table::CellRepr, { - fn cells< 'a, 'b >( &'a self ) -> impl IteratorTrait< Item = ( &'b CellKey, OptionalCow< 'b, str, CellRepr > ) > + // fn cells< 'a, 'b >( &'a self ) -> impl IteratorTrait< Item = ( &'b CellKey, OptionalCow< 'b, str > ) > + fn cells< 'a, 'b >( &'a self ) -> impl IteratorTrait< Item = ( &'b CellKey, Option< Cow< 'b, str > > ) > where 'a : 'b, CellKey : 'b, @@ -154,15 +172,15 @@ mod private /// /// The type representing a row, which must implement `Cells` /// for the specified `CellKey` and `CellRepr`. - type Row : Cells< Self::CellKey, Self::CellRepr >; + type Row : Cells< Self::CellKey >; /// /// The type used to identify cells within a row, requiring /// implementation of the `Key` trait. type CellKey : table::CellKey + ?Sized; /// - /// The type representing the content of a cell, requiring - /// implementation of the `CellRepr` trait. - type CellRepr : table::CellRepr; + // /// The type representing the content of a cell, requiring + // /// implementation of the `CellRepr` trait. + // type // CellRepr : table::CellRepr; /// Returns an iterator over all rows of the table. fn rows( &self ) -> impl IteratorTrait< Item = &Self::Row >; @@ -171,9 +189,8 @@ mod private // Self::Row : 'a; } - impl< T, RowKey, Row, CellKey, CellRepr > - TableRows<> - for AsTable< '_, T, RowKey, Row, CellKey, CellRepr > + impl< T, RowKey, Row, CellKey > TableRows<> + for AsTable< '_, T, RowKey, Row, CellKey > where for< 'k, 'v > T : Fields @@ -185,14 +202,14 @@ mod private > + 'k + 'v, RowKey : table::RowKey, - Row : TableWithFields + Cells< CellKey, CellRepr >, + Row : Cells< CellKey >, CellKey : table::CellKey + ?Sized, - CellRepr : table::CellRepr, + // CellRepr : table::CellRepr, { type RowKey = RowKey; type Row = Row; type CellKey = CellKey; - type CellRepr = CellRepr; + // type CellRepr = CellRepr; fn rows( &self ) -> impl IteratorTrait< Item = &Self::Row > // fn rows< 'a >( &'a self ) -> impl IteratorTrait< Item = &'a Self::Row > @@ -217,14 +234,14 @@ mod private // fn mcells( &self ) -> [ usize ; 2 ]; // } // -// impl< T, RowKey, Row, CellKey, CellRepr > TableSize -// for AsTable< '_, T, RowKey, Row, CellKey, CellRepr > +// impl< T, RowKey, Row, CellKey > TableSize +// for AsTable< '_, T, RowKey, Row, CellKey > // where -// Self : TableRows< RowKey = RowKey, Row = Row, CellKey = CellKey, CellRepr = CellRepr >, +// Self : TableRows< RowKey = RowKey, Row = Row, CellKey = CellKey >, // RowKey : table::RowKey, -// Row : Cells< CellKey, CellRepr >, +// Row : Cells< CellKey >, // CellKey : table::CellKey + ?Sized, -// CellRepr : table::CellRepr, +// // CellRepr : table::CellRepr, // { // fn mcells( &self ) -> [ usize ; 2 ] // { @@ -256,14 +273,14 @@ mod private fn header( &self ) -> Option< impl IteratorTrait< Item = ( &Self::CellKey, &'_ str ) > >; } - impl< T, RowKey, Row, CellKey, CellRepr > TableHeader - for AsTable< '_, T, RowKey, Row, CellKey, CellRepr > + impl< T, RowKey, Row, CellKey > TableHeader + for AsTable< '_, T, RowKey, Row, CellKey > where - Self : TableRows< RowKey = RowKey, Row = Row, CellKey = CellKey, CellRepr = CellRepr >, + Self : TableRows< RowKey = RowKey, Row = Row, CellKey = CellKey >, RowKey : table::RowKey, - Row : TableWithFields + Cells< CellKey, CellRepr >, + Row : Cells< CellKey >, CellKey : table::CellKey + ?Sized, - CellRepr : table::CellRepr, + // CellRepr : table::CellRepr, { type CellKey = CellKey; diff --git a/module/core/format_tools/src/format/test_object_without_impl.rs b/module/core/format_tools/src/format/test_object_without_impl.rs new file mode 100644 index 0000000000..f61b3fe588 --- /dev/null +++ b/module/core/format_tools/src/format/test_object_without_impl.rs @@ -0,0 +1,155 @@ +//! A strucutre for diagnostic and demonstration purpose. + +// use super::*; + +// use crate:: +// { +// Fields, +// IteratorTrait, +// TableWithFields, +// WithRef, +// OptionalCow, +// }; + +use std:: +{ + collections::HashMap, + hash::Hasher, + hash::Hash, + cmp::Ordering, + // borrow::Cow, +}; + +/// Struct representing a test object with various fields. +#[ derive( Clone, Debug, PartialEq, Eq ) ] +pub struct TestObjectWithoutImpl +{ + pub id : String, + pub created_at : i64, + pub file_ids : Vec< String >, + pub tools : Option< Vec< HashMap< String, String > > >, +} + +// TableWithFields is not implemented for TestObjectWithoutImpl intentionally + +// impl TableWithFields for TestObjectWithoutImpl {} +// +// impl Fields< &'_ str, Option< Cow< '_, str > > > +// for TestObjectWithoutImpl +// { +// type Key< 'k > = &'k str; +// type Val< 'v > = Option< Cow< 'v, str > >; +// +// fn fields( &self ) -> impl IteratorTrait< Item = ( &'_ str, Option< Cow< '_, str > > ) > +// { +// use format_tools::ref_or_display_or_debug_multiline::field; +// // use format_tools::ref_or_display_or_debug::field; +// let mut dst : Vec< ( &'_ str, Option< Cow< '_, str > > ) > = Vec::new(); +// +// dst.push( field!( &self.id ) ); +// dst.push( field!( &self.created_at ) ); +// dst.push( field!( &self.file_ids ) ); +// +// if let Some( tools ) = &self.tools +// { +// dst.push( field!( tools ) ); +// } +// else +// { +// dst.push( ( "tools", Option::None ) ); +// } +// +// dst.into_iter() +// } +// +// } + +impl Hash for TestObjectWithoutImpl +{ + + fn hash< H: Hasher >( &self, state: &mut H ) + { + self.id.hash( state ); + self.created_at.hash( state ); + self.file_ids.hash( state ); + + if let Some( tools ) = &self.tools + { + for tool in tools + { + for ( key, value ) in tool + { + key.hash( state ); + value.hash( state ); + } + } + } + else + { + state.write_u8( 0 ); + } + } + +} + +impl PartialOrd for TestObjectWithoutImpl +{ + + fn partial_cmp( &self, other: &Self ) -> Option< Ordering > + { + Some( self.cmp( other ) ) + } + +} + +impl Ord for TestObjectWithoutImpl +{ + + fn cmp( &self, other: &Self ) -> Ordering + { + self.id + .cmp( &other.id ) + .then_with( | | self.created_at.cmp( &other.created_at ) ) + .then_with( | | self.file_ids.cmp( &other.file_ids ) ) + } + +} + +/// Generate a dynamic array of test objects. +pub fn test_objects_gen() -> Vec< TestObjectWithoutImpl > +{ + + vec! + [ + TestObjectWithoutImpl + { + id : "1".to_string(), + created_at : 1627845583, + file_ids : vec![ "file1".to_string(), "file2".to_string() ], + tools : None + }, + TestObjectWithoutImpl + { + id : "2".to_string(), + created_at : 13, + file_ids : vec![ "file3".to_string(), "file4\nmore details".to_string() ], + tools : Some + ( + vec! + [ + { + let mut map = HashMap::new(); + map.insert( "tool1".to_string(), "value1".to_string() ); + map + }, + { + let mut map = HashMap::new(); + map.insert( "tool2".to_string(), "value2".to_string() ); + map + } + ] + ), + }, + ] + +} diff --git a/module/core/format_tools/src/format/text_wrap.rs b/module/core/format_tools/src/format/text_wrap.rs new file mode 100644 index 0000000000..695ac287cd --- /dev/null +++ b/module/core/format_tools/src/format/text_wrap.rs @@ -0,0 +1,256 @@ +//! +//! Text wrapping function. +//! + +/// Define a private namespace for all its items. +mod private +{ + + use std::borrow::Cow; + + use crate::*; + + /// Struct that represents a wrapped tabular data. It is similar to `InputExtract`, + /// but we cannot use it as it does not wrap the text and it contains wrong column + /// widthes and heights (as they are dependent on wrapping too). + #[ derive( Debug ) ] + pub struct WrappedInputExtract< 'data > + { + /// Tabular data of rows and columns. + /// Note: these cells does not represent the actual information cells in the + /// original table. These cells are wrapped and used only for displaying. This also + /// means that one row in original table can be represented here with one or more + /// rows. + pub data: Vec< Vec< WrappedCell< 'data > > >, + + /// New widthes of columns that include wrapping. + pub column_widthes : Vec< usize >, + + /// Size of the first row of the table. + /// This parameter is used in case header of the table should be displayed. + pub first_row_height : usize, + } + + /// Struct that represents a content of a wrapped cell. + /// It contains the slice of the cell as well as its original width. + /// + /// Parameter `wrap_width` is needed as text in `output_format::Table` is centered. + /// However it is centered according to whole cell size and not the size of wrapped + /// text slice. + /// + /// Example that depicts the importance of `wrap_width` parameter: + /// + /// 1) | [ | 2) | [ | + /// | line1, | | line1, | + /// | line2 | | line2 | + /// | ] | | ] | + /// + /// The first case seems to be properly formatted, while the second case took centering + /// too literally. That is why `wrap_width` is introduced, and additional spaces to the + /// right side should be included by the output formatter. + #[ derive( Debug ) ] + pub struct WrappedCell< 'data > + { + /// Width of the cell. In calculations use this width instead of slice length in order + /// to properly center the text. See example in the doc string of the parent struct. + pub wrap_width : usize, + + /// Actual content of the cell. + pub content : Cow< 'data, str > + } + + /// Wrap table cells. + /// + /// `InputExtract` contains cells with full content, so it represents the logical + /// structure of the table. `WrappedInputExtract` wraps original cells to smaller + /// cells. The resulting data is more low-level and corresponds to the table that + /// will be actually printed to the console (or other output type). + /// + /// `InputExtract` is not directly passed to this function, as it made to be general. + /// Instead you pass table cells in `data` argument and pass a vector of column widthes + /// in `columns_width_list` generated by `InputExtract`. + /// + /// `columns_width_list` is a slice, this is more effective and general than just a `Vec`. + /// In table style, there could be many columns, but in records style there will be + /// always 2 columns - this number is known at compile time, so we can use a slice object. + /// + /// Notice: + /// 1. Data passed to this function should contain only visible rows and columns. + /// It does not perform additional filtering. + /// 2. `data` parameters is **vector of rows of columns** (like and ordinary table). + /// This means that in styles like `Records` where headers and rows turned into columns + /// You have to transpose your data before passing it to this function. + /// + /// Wrapping is controlled by `columns_max_width` and `columns_nowrap_width` parameters. + /// + /// - `columns_max_width` is the size that is allowed to be occupied by columns. + /// It equals to maximum table width minus lengthes of visual elements (prefixes, + /// postfixes, separators, etc.). + /// + /// - `columns_nowrap_width` is the sum of column widthes of cells without wrapping (basically, + /// the sum of widthes of column descriptors in `InputExtract`). + /// + /// The function will perform wrapping and shrink the columns so that they occupy not + /// more than `columns_max_width`. + /// + /// If `columns_max_width` is equal to 0, then no wrapping will be performed. + pub fn text_wrap< 'data > + ( + data : impl Iterator< Item = &'data Vec< ( Cow< 'data, str >, [ usize; 2 ] ) > >, + columns_width_list : impl AsRef< [ usize ] >, + columns_max_width : usize, + columns_nowrap_width : usize, + ) + -> WrappedInputExtract< 'data > + { + let columns_width_list = columns_width_list.as_ref(); + + let mut first_row_height = 0; + let mut new_data = Vec::new(); + let mut column_widthes = Vec::new(); + + if columns_max_width == 0 || columns_max_width >= columns_nowrap_width + { + column_widthes.extend( columns_width_list.iter() ); + } + else + { + let shrink_factor : f32 = ( columns_max_width as f32 ) / ( columns_nowrap_width as f32 ); + + for ( icol, col_width ) in columns_width_list.iter().enumerate() + { + let col_limit_float = ( *col_width as f32 ) * shrink_factor; + let col_limit = col_limit_float.floor() as usize; + + let col_width_to_put = if icol == columns_width_list.len() - 1 + { + columns_max_width - column_widthes.iter().sum::() + } + else + { + col_limit.max(1) + }; + + column_widthes.push( col_width_to_put ); + } + } + + for ( irow, row ) in data.enumerate() + { + let mut wrapped_rows : Vec< Vec< Cow< 'data, str > > > = vec![]; + + for ( icol, col ) in row.iter().enumerate() + { + let col_limit = column_widthes[ icol ]; + let wrapped_col = string::lines_with_limit( col.0.as_ref(), col_limit ).map( Cow::from ).collect(); + wrapped_rows.push( wrapped_col ); + } + + let max_rows = wrapped_rows.iter().map( Vec::len ).max().unwrap_or(0); + + let mut transposed : Vec< Vec< WrappedCell< 'data > > > = Vec::new(); + + if max_rows == 0 + { + transposed.push( vec![] ); + } + + for i in 0..max_rows + { + let mut row_vec : Vec< WrappedCell< 'data > > = Vec::new(); + + for col_lines in &wrapped_rows + { + if col_lines.len() > i + { + let wrap_width = col_lines.iter().map( |c| c.len() ).max().unwrap_or(0); + row_vec.push( WrappedCell { wrap_width , content : col_lines[ i ].clone() } ); + } + else + { + row_vec.push( WrappedCell { wrap_width : 0, content : Cow::from( "" ) } ); + } + } + + transposed.push( row_vec ); + } + + if irow == 0 + { + first_row_height += transposed.len(); + } + + new_data.extend( transposed ); + } + + WrappedInputExtract + { + data: new_data, + first_row_height, + column_widthes + } + } + + /// Calculate width of the column without wrapping. + pub fn width_calculate< 'data > + ( + column : &'data Vec< ( Cow< 'data, str >, [ usize; 2 ] ) > + ) + -> usize + { + column.iter().map( |k| + { + string::lines( k.0.as_ref() ).map( |l| l.chars().count() ).max().unwrap_or( 0 ) + } ).max().unwrap_or( 0 ) + } + +} + +#[ 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 ) ] + pub use + { + }; + + #[ doc( inline ) ] + pub use private:: + { + text_wrap, + width_calculate, + }; + +} + +/// 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::*; + pub use super::super::output_format; +} + +/// Prelude to use essentials: `use my_module::prelude::*`. +#[ allow( unused_imports ) ] +pub mod prelude +{ + use super::*; +} diff --git a/module/core/format_tools/src/format/to_string.rs b/module/core/format_tools/src/format/to_string.rs index 6446e90fa2..8bc9bb538f 100644 --- a/module/core/format_tools/src/format/to_string.rs +++ b/module/core/format_tools/src/format/to_string.rs @@ -2,7 +2,7 @@ //! Flexible ToString augmentation. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { diff --git a/module/core/format_tools/src/format/to_string_with_fallback.rs b/module/core/format_tools/src/format/to_string_with_fallback.rs index e79b827896..fb5966bf38 100644 --- a/module/core/format_tools/src/format/to_string_with_fallback.rs +++ b/module/core/format_tools/src/format/to_string_with_fallback.rs @@ -2,7 +2,7 @@ //! Flexible ToString augmentation. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::*; diff --git a/module/core/format_tools/src/format/to_string_with_fallback/params.rs b/module/core/format_tools/src/format/to_string_with_fallback/params.rs new file mode 100644 index 0000000000..1b901ec99c --- /dev/null +++ b/module/core/format_tools/src/format/to_string_with_fallback/params.rs @@ -0,0 +1,7 @@ +//! +//! Marker type for trait `_ToStringWithFallback` with type parameters. +//! + +/// Marker type for trait `_ToStringWithFallback` with type parameters. +#[ derive( Debug, Default, Clone, Copy ) ] +pub struct ToStringWithFallbackParams< How, Fallback >( ::core::marker::PhantomData< fn() -> ( How, Fallback ) > ); diff --git a/module/core/format_tools/src/format/wrapper.rs b/module/core/format_tools/src/format/wrapper.rs new file mode 100644 index 0000000000..4cd134650f --- /dev/null +++ b/module/core/format_tools/src/format/wrapper.rs @@ -0,0 +1,50 @@ +//! +//! Collection of wrappers. +//! + +/// Internal namespace. +pub( crate ) mod private +{ +} + +mod aref; +mod maybe_as; + +#[ doc( inline ) ] +#[ allow( unused_imports ) ] +pub use protected::*; + +/// Protected namespace of the module. +pub mod protected +{ + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::orphan::*; +} + +/// Orphan namespace of the module. +pub mod orphan +{ + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::exposed::*; +} + +/// Exposed namespace of the module. +pub mod exposed +{ + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super:: + { + aref::IntoRef, + aref::Ref, + maybe_as::IntoMaybeAs, + maybe_as::MaybeAs, + }; +} + +/// Prelude to use essentials: `use my_module::prelude::*`. +pub mod prelude +{ +} diff --git a/module/core/format_tools/src/format/wrapper/aref.rs b/module/core/format_tools/src/format/wrapper/aref.rs new file mode 100644 index 0000000000..7e6afeb049 --- /dev/null +++ b/module/core/format_tools/src/format/wrapper/aref.rs @@ -0,0 +1,116 @@ +//! +//! It's often necessary to wrap something inot a local structure and this file contains a resusable local structure for wrapping. +//! + +// use core::fmt; +use core::ops::{ Deref }; + +/// Converter into universal wrapper with transparent option of copy on write reference emphasizing a specific aspect of identity of its internal type. +pub trait IntoRef< 'a, T, Marker > +{ + /// Converter into universal wrapper with transparent option of copy on write reference emphasizing a specific aspect of identity of its internal type. + fn into_ref( self ) -> Ref< 'a, T, Marker >; +} + +impl< 'a, T, Marker > IntoRef< 'a, T, Marker > for &'a T +{ + #[ inline( always ) ] + fn into_ref( self ) -> Ref< 'a, T, Marker > + { + Ref::< 'a, T, Marker >::new( self ) + } +} + +/// Transparent reference wrapper emphasizing a specific aspect of identity of its internal type. +#[ allow( missing_debug_implementations ) ] +#[ repr( transparent ) ] +pub struct Ref< 'a, T, Marker >( pub &'a T, ::core::marker::PhantomData< fn() -> Marker > ) +where + ::core::marker::PhantomData< fn( Marker ) > : Copy, + &'a T : Copy, +; + +impl< 'a, T, Marker > Clone for Ref< 'a, T, Marker > +{ + #[ inline( always ) ] + fn clone( &self ) -> Self + { + Self::new( self.0 ) + } +} + +impl< 'a, T, Marker > Copy for Ref< 'a, T, Marker > {} + +impl< 'a, T, Marker > Ref< 'a, T, Marker > +{ + + /// Just a constructor. + #[ inline( always ) ] + pub fn new( src : &'a T ) -> Self + { + Self( src, ::core::marker::PhantomData ) + } + + /// Just a constructor. + #[ inline( always ) ] + pub fn inner( self ) -> &'a T + { + self.0 + } + +} + +impl< 'a, T, Marker > AsRef< T > for Ref< 'a, T, Marker > +{ + fn as_ref( &self ) -> &T + { + &self.0 + } +} + +impl< 'a, T, Marker > Deref for Ref< 'a, T, Marker > +{ + type Target = T; + fn deref( &self ) -> &Self::Target + { + &self.0 + } +} + +impl< 'a, T, Marker > From< &'a T > for Ref< 'a, T, Marker > +{ + fn from( src : &'a T ) -> Self + { + Ref::new( src ) + } +} + +// impl< 'a, T, Marker > From< Ref< 'a, T, Marker > > for &'a T +// { +// fn from( wrapper : Ref< 'a, T, Marker > ) -> &'a T +// { +// wrapper.0 +// } +// } + +// impl< 'a, T, Marker > Default for Ref< 'a, T, Marker > +// where +// T : Default, +// { +// fn default() -> Self +// { +// Ref( &T::default() ) +// } +// } + +// impl< 'a, T, Marker > fmt::Debug for Ref< 'a, T, Marker > +// where +// T : fmt::Debug, +// { +// fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result +// { +// f.debug_struct( "Ref" ) +// .field( "0", &self.0 ) +// .finish() +// } +// } diff --git a/module/core/format_tools/src/format/wrapper/maybe_as.rs b/module/core/format_tools/src/format/wrapper/maybe_as.rs new file mode 100644 index 0000000000..d9c4a910c3 --- /dev/null +++ b/module/core/format_tools/src/format/wrapper/maybe_as.rs @@ -0,0 +1,251 @@ +//! +//! It's often necessary to wrap something inot a local structure and this file contains wrapper of `Option< Cow< 'a, T > >`. +//! + +use core::fmt; +use std::borrow::Cow; +use core::ops::{ Deref }; + +/// Converter into universal wrapper with transparent option of copy on write reference emphasizing a specific aspect of identity of its internal type. +pub trait IntoMaybeAs< 'a, T, Marker > +where + T : Clone, +{ + /// Converter into universal wrapper with transparent option of copy on write reference emphasizing a specific aspect of identity of its internal type. + fn into_maybe_as( self ) -> MaybeAs< 'a, T, Marker >; +} + +impl< 'a, T, Marker > IntoMaybeAs< 'a, T, Marker > for T +where + T : Clone, +{ + #[ inline( always ) ] + fn into_maybe_as( self ) -> MaybeAs< 'a, T, Marker > + { + MaybeAs::< 'a, T, Marker >::new( self ) + } +} + +impl< 'a, T, Marker > IntoMaybeAs< 'a, T, Marker > for &'a T +where + T : Clone, +{ + #[ inline( always ) ] + fn into_maybe_as( self ) -> MaybeAs< 'a, T, Marker > + { + MaybeAs::< 'a, T, Marker >::new_with_ref( self ) + } +} + +// xxx +// impl< 'a, T, Marker > IntoMaybeAs< 'a, T, Marker > for () +// where +// T : Clone, +// { +// #[ inline( always ) ] +// fn into_maybe_as( self ) -> MaybeAs< 'a, T, Marker > +// { +// MaybeAs::< 'a, T, Marker >( None ) +// } +// } + +/// Universal wrapper with transparent option of copy on write reference emphasizing a specific aspect of identity of its internal type. +#[ repr( transparent ) ] +#[ derive( Clone ) ] +pub struct MaybeAs< 'a, T, Marker >( pub Option< Cow< 'a, T > >, ::core::marker::PhantomData< fn() -> Marker > ) +where + T : Clone, +; + +impl< 'a, T, Marker > MaybeAs< 'a, T, Marker > +where + T : Clone, +{ + + /// Just a constructor. + #[ inline( always ) ] + pub fn none() -> Self + { + Self( None, ::core::marker::PhantomData ) + } + + /// Just a constructor. + #[ inline( always ) ] + pub fn new( src : T ) -> Self + { + Self( Some( Cow::Owned( src ) ), ::core::marker::PhantomData ) + } + + /// Just a constructor. + #[ inline( always ) ] + pub fn new_with_ref( src : &'a T ) -> Self + { + Self( Some( Cow::Borrowed( src ) ), ::core::marker::PhantomData ) + } + + /// Just a constructor. + #[ inline( always ) ] + pub fn new_with_inner( src : Option< Cow< 'a, T > > ) -> Self + { + Self( src, ::core::marker::PhantomData ) + } + + /// Just a constructor. + #[ inline( always ) ] + pub fn inner( self ) -> Option< Cow< 'a, T > > + { + self.0 + } + +} + +impl< 'a, T, Marker > AsRef< Option< Cow< 'a, T > > > for MaybeAs< 'a, T, Marker > +where + T : Clone, + Self : 'a, +{ + fn as_ref( &self ) -> &Option< Cow< 'a, T > > + { + &self.0 + } +} + +impl< 'a, T, Marker > Deref for MaybeAs< 'a, T, Marker > +where + T : Clone, + Marker : 'static, +{ + type Target = Option< Cow< 'a, T > >; + fn deref( &self ) -> &Option< Cow< 'a, T > > + { + self.as_ref() + } +} + +// impl< 'a, T, Marker > AsRef< T > for MaybeAs< 'a, T, Marker > +// where +// T : Clone, +// Self : 'a, +// { +// fn as_ref( &self ) -> &'a T +// { +// match &self.0 +// { +// Some( src ) => +// { +// match src +// { +// Cow::Borrowed( src ) => src, +// Cow::Owned( src ) => &src, +// } +// }, +// None => panic!( "MaybeAs is None" ), +// } +// } +// } +// +// impl< 'a, T, Marker > Deref for MaybeAs< 'a, T, Marker > +// where +// T : Clone, +// { +// type Target = T; +// fn deref( &self ) -> &'a T +// { +// self.as_ref() +// } +// } + +impl< 'a, T, Marker > From< T > +for MaybeAs< 'a, T, Marker > +where + T : Clone, +{ + fn from( src : T ) -> Self + { + MaybeAs::new( src ) + } +} + +impl< 'a, T, Marker > From< Option< Cow< 'a, T > > > +for MaybeAs< 'a, T, Marker > +where + T : Clone, +{ + fn from( src : Option< Cow< 'a, T > > ) -> Self + { + MaybeAs::new_with_inner( src ) + } +} + +impl< 'a, T, Marker > From< &'a T > +for MaybeAs< 'a, T, Marker > +where + T : Clone, +{ + fn from( src : &'a T ) -> Self + { + MaybeAs::new_with_ref( src ) + } +} + +// impl< 'a, T, Marker > From< () > for MaybeAs< 'a, T, Marker > +// where +// T : (), +// { +// fn from( src : &'a T ) -> Self +// { +// MaybeAs( None ) +// } +// } + +// xxx : more from + +// impl< 'a, T, Marker > From< MaybeAs< 'a, T, Marker > > for &'a T +// where +// T : Clone, +// { +// fn from( wrapper : MaybeAs< 'a, T, Marker > ) -> &'a T +// { +// wrapper.0 +// } +// } + +impl< 'a, T, Marker > Default for MaybeAs< 'a, T, Marker > +where + T : Clone, + T : Default, +{ + fn default() -> Self + { + MaybeAs::new( T::default() ) + } +} + +impl< 'a, T, Marker > fmt::Debug for MaybeAs< 'a, T, Marker > +where + T : fmt::Debug, + T : Clone, +{ + fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result + { + f.debug_struct( "MaybeAs" ) + .field( "0", &self.0 ) + .finish() + } +} + +impl< 'a, T, Marker > PartialEq for MaybeAs< 'a, T, Marker > +where + T : Clone + PartialEq, +{ + fn eq( &self, other : &Self ) -> bool + { + self.as_ref() == other.as_ref() + } +} + +impl< 'a, T, Marker > Eq for MaybeAs< 'a, T, Marker > +where + T : Clone + Eq, +{ +} diff --git a/module/core/format_tools/tests/inc/collection_test.rs b/module/core/format_tools/tests/inc/collection_test.rs index 941f9a498b..0d066004e2 100644 --- a/module/core/format_tools/tests/inc/collection_test.rs +++ b/module/core/format_tools/tests/inc/collection_test.rs @@ -6,13 +6,12 @@ use the_module:: AsTable, TableRows, WithRef, - print, + // the_module::print, }; use std:: { collections::HashMap, - // borrow::Cow, }; use test_object::TestObject; @@ -57,14 +56,14 @@ fn dlist_basic() }; use the_module::TableFormatter; - let _as_table : AsTable< '_, Vec< TestObject >, &str, TestObject, str, WithRef > = AsTable::new( &data ); + let _as_table : AsTable< '_, Vec< TestObject >, &str, TestObject, str> = AsTable::new( &data ); let as_table = AsTable::new( &data ); let rows = TableRows::rows( &as_table ); assert_eq!( rows.len(), 2 ); let mut output = String::new(); - let mut context = print::Context::new( &mut output, Default::default() ); + let mut context = the_module::print::Context::new( &mut output, Default::default() ); let _got = the_module::TableFormatter::fmt( &as_table, &mut context ); let got = as_table.table_to_string(); assert!( got.contains( "│ id │ created_at │ file_ids │ tools │" ) ); @@ -113,14 +112,14 @@ fn hmap_basic() }; use the_module::TableFormatter; - let _as_table : AsTable< '_, HashMap< &str, TestObject >, &str, TestObject, str, WithRef > = AsTable::new( &data ); + let _as_table : AsTable< '_, HashMap< &str, TestObject >, &str, TestObject, str> = AsTable::new( &data ); let as_table = AsTable::new( &data ); let rows = TableRows::rows( &as_table ); assert_eq!( rows.len(), 2 ); let mut output = String::new(); - let mut context = print::Context::new( &mut output, Default::default() ); + let mut context = the_module::print::Context::new( &mut output, Default::default() ); let _got = the_module::TableFormatter::fmt( &as_table, &mut context ); let got = as_table.table_to_string(); assert!( got.contains( "│ id │ created_at │ file_ids │ tools │" ) ); @@ -169,14 +168,230 @@ fn bmap_basic() }; use the_module::TableFormatter; - let _as_table : AsTable< '_, Bmap< &str, TestObject >, &str, TestObject, str, WithRef > = AsTable::new( &data ); + let _as_table : AsTable< '_, Bmap< &str, TestObject >, &str, TestObject, str> = AsTable::new( &data ); let as_table = AsTable::new( &data ); let rows = TableRows::rows( &as_table ); assert_eq!( rows.len(), 2 ); let mut output = String::new(); - let mut context = print::Context::new( &mut output, Default::default() ); + let mut context = the_module::print::Context::new( &mut output, Default::default() ); + let _got = the_module::TableFormatter::fmt( &as_table, &mut context ); + let got = as_table.table_to_string(); + assert!( got.contains( "│ id │ created_at │ file_ids │ tools │" ) ); + assert!( got.contains( "│ 13 │ [ │ [ │" ) ); + assert!( got.contains( "│ 1627845583 │ [ │ │" ) ); + +} + +#[ test ] +fn bset_basic() +{ + + let data : collection_tools::Bset< TestObject > = bset! + { + TestObject + { + id : "1".to_string(), + created_at : 1627845583, + file_ids : vec![ "file1".to_string(), "file2".to_string() ], + tools : None + }, + TestObject + { + id : "2".to_string(), + created_at : 13, + file_ids : vec![ "file3".to_string(), "file4\nmore details".to_string() ], + tools : Some + ( + vec! + [ + { + let mut map = HashMap::new(); + map.insert( "tool1".to_string(), "value1".to_string() ); + map + }, + { + let mut map = HashMap::new(); + map.insert( "tool2".to_string(), "value2".to_string() ); + map + } + ] + ), + }, + }; + + use the_module::TableFormatter; + let _as_table : AsTable< '_, BTreeSet< TestObject >, &str, TestObject, str> = AsTable::new( &data ); + let as_table = AsTable::new( &data ); + + let rows = TableRows::rows( &as_table ); + assert_eq!( rows.len(), 2 ); + + let mut output = String::new(); + let mut context = the_module::print::Context::new( &mut output, Default::default() ); + let _got = the_module::TableFormatter::fmt( &as_table, &mut context ); + let got = as_table.table_to_string(); + assert!( got.contains( "│ id │ created_at │ file_ids │ tools │" ) ); + assert!( got.contains( "│ 13 │ [ │ [ │" ) ); + assert!( got.contains( "│ 1627845583 │ [ │ │" ) ); + +} + +#[ test ] +fn deque_basic() +{ + + let data : collection_tools::Deque< TestObject > = deque! + { + TestObject + { + id : "1".to_string(), + created_at : 1627845583, + file_ids : vec![ "file1".to_string(), "file2".to_string() ], + tools : None + }, + TestObject + { + id : "2".to_string(), + created_at : 13, + file_ids : vec![ "file3".to_string(), "file4\nmore details".to_string() ], + tools : Some + ( + vec! + [ + { + let mut map = HashMap::new(); + map.insert( "tool1".to_string(), "value1".to_string() ); + map + }, + { + let mut map = HashMap::new(); + map.insert( "tool2".to_string(), "value2".to_string() ); + map + } + ] + ), + }, + }; + + use the_module::TableFormatter; + let _as_table : AsTable< '_, VecDeque< TestObject >, &str, TestObject, str> = AsTable::new( &data ); + let as_table = AsTable::new( &data ); + + let rows = TableRows::rows( &as_table ); + assert_eq!( rows.len(), 2 ); + + let mut output = String::new(); + let mut context = the_module::print::Context::new( &mut output, Default::default() ); + let _got = the_module::TableFormatter::fmt( &as_table, &mut context ); + let got = as_table.table_to_string(); + assert!( got.contains( "│ id │ created_at │ file_ids │ tools │" ) ); + assert!( got.contains( "│ 13 │ [ │ [ │" ) ); + assert!( got.contains( "│ 1627845583 │ [ │ │" ) ); + +} + +#[ test ] +fn hset_basic() +{ + + let data : collection_tools::Hset< TestObject > = hset! + { + TestObject + { + id : "1".to_string(), + created_at : 1627845583, + file_ids : vec![ "file1".to_string(), "file2".to_string() ], + tools : None + }, + TestObject + { + id : "2".to_string(), + created_at : 13, + file_ids : vec![ "file3".to_string(), "file4\nmore details".to_string() ], + tools : Some + ( + vec! + [ + { + let mut map = HashMap::new(); + map.insert( "tool1".to_string(), "value1".to_string() ); + map + }, + { + let mut map = HashMap::new(); + map.insert( "tool2".to_string(), "value2".to_string() ); + map + } + ] + ), + }, + }; + + use the_module::TableFormatter; + let _as_table : AsTable< '_, HashSet< TestObject >, &str, TestObject, str> = AsTable::new( &data ); + let as_table = AsTable::new( &data ); + + let rows = TableRows::rows( &as_table ); + assert_eq!( rows.len(), 2 ); + + let mut output = String::new(); + let mut context = the_module::print::Context::new( &mut output, Default::default() ); + let _got = the_module::TableFormatter::fmt( &as_table, &mut context ); + let got = as_table.table_to_string(); + assert!( got.contains( "│ id │ created_at │ file_ids │ tools │" ) ); + assert!( got.contains( "│ 13 │ [ │ [ │" ) ); + assert!( got.contains( "│ 1627845583 │ [ │ │" ) ); + +} + +#[ test ] +fn llist_basic() +{ + + let data : collection_tools::Llist< TestObject > = llist! + { + TestObject + { + id : "1".to_string(), + created_at : 1627845583, + file_ids : vec![ "file1".to_string(), "file2".to_string() ], + tools : None + }, + TestObject + { + id : "2".to_string(), + created_at : 13, + file_ids : vec![ "file3".to_string(), "file4\nmore details".to_string() ], + tools : Some + ( + vec! + [ + { + let mut map = HashMap::new(); + map.insert( "tool1".to_string(), "value1".to_string() ); + map + }, + { + let mut map = HashMap::new(); + map.insert( "tool2".to_string(), "value2".to_string() ); + map + } + ] + ), + }, + }; + + use the_module::TableFormatter; + let _as_table : AsTable< '_, LinkedList< TestObject >, &str, TestObject, str> = AsTable::new( &data ); + let as_table = AsTable::new( &data ); + + let rows = TableRows::rows( &as_table ); + assert_eq!( rows.len(), 2 ); + + let mut output = String::new(); + let mut context = the_module::print::Context::new( &mut output, Default::default() ); let _got = the_module::TableFormatter::fmt( &as_table, &mut context ); let got = as_table.table_to_string(); assert!( got.contains( "│ id │ created_at │ file_ids │ tools │" ) ); @@ -186,3 +401,46 @@ fn bmap_basic() } // qqq : xxx : implement for other containers + +#[ test ] +fn vec_of_hashmap() +{ + let data : Vec< HashMap< String, String > > = vec! + [ + { + let mut map = HashMap::new(); + map.insert( "id".to_string(), "1".to_string() ); + map.insert( "created_at".to_string(), "1627845583".to_string() ); + map + }, + { + let mut map = HashMap::new(); + map.insert( "id".to_string(), "2".to_string() ); + map.insert( "created_at".to_string(), "13".to_string() ); + map + }, + ]; + + use std::borrow::Cow; + + use the_module::TableFormatter; + + let _as_table : AsTable< '_, Vec< HashMap< String, String > >, &str, HashMap< String, String >, str> = AsTable::new( &data ); + let as_table = AsTable::new( &data ); + + let rows = TableRows::rows( &as_table ); + assert_eq!( rows.len(), 2 ); + + let mut output = String::new(); + let mut context = the_module::print::Context::new( &mut output, Default::default() ); + + let _got = the_module::TableFormatter::fmt( &as_table, &mut context ); + + let got = as_table.table_to_string(); + + println!("{}", got); + + assert!( got.contains( "│ id │ created_at │" ) || got.contains( "│ created_at │ id │" ) ); + assert!( got.contains( "│ 1 │ 1627845583 │" ) || got.contains( "│ 1627845583 │ 1 │" ) ); + assert!( got.contains( "│ 2 │ 13 │" ) || got.contains( "│ 13 │ 2 │" ) ); +} \ No newline at end of file diff --git a/module/core/format_tools/tests/inc/fields_test.rs b/module/core/format_tools/tests/inc/fields_test.rs index 48ba295e20..32d921bed0 100644 --- a/module/core/format_tools/tests/inc/fields_test.rs +++ b/module/core/format_tools/tests/inc/fields_test.rs @@ -26,16 +26,16 @@ pub struct TestObject pub tools : Option< Vec< HashMap< String, String > > >, } -impl Fields< &'static str, OptionalCow< '_, str, WithRef > > +impl Fields< &'_ str, Option< Cow< '_, str > > > for TestObject { - type Key< 'k > = &'static str; - type Val< 'v > = OptionalCow< 'v, str, WithRef >; + type Key< 'k > = &'k str; + type Val< 'v > = Option< Cow< 'v, str > >; - fn fields( &self ) -> impl IteratorTrait< Item = ( &'static str, OptionalCow< '_, str, WithRef > ) > + fn fields( &self ) -> impl IteratorTrait< Item = ( &'_ str, Option< Cow< '_, str > > ) > { use format_tools::ref_or_display_or_debug::field; - let mut dst : Vec< ( &'static str, OptionalCow< '_, str, WithRef > ) > = Vec::new(); + let mut dst : Vec< ( &'_ str, Option< Cow< '_, str > > ) > = Vec::new(); dst.push( field!( &self.id ) ); dst.push( field!( &self.created_at ) ); @@ -47,11 +47,17 @@ for TestObject } else { - dst.push( ( "tools", OptionalCow::none() ) ); + dst.push( ( "tools", Option::None ) ); } dst.into_iter() } + +} + +pub fn is_borrowed( cow : &Option< Cow< '_, str > > ) -> bool +{ + matches!( cow, Some( Cow::Borrowed( _ ) ) ) } // @@ -76,16 +82,16 @@ fn basic_with_ref_display_debug() ), }; - let fields : Vec< ( &str, OptionalCow< '_, str, WithRef > ) > = - Fields::< &'static str, OptionalCow< '_, str, WithRef > >::fields( &test_object ).collect(); + let fields : Vec< ( &str, Option< Cow< '_, str > > ) > = + Fields::< &'static str, Option< Cow< '_, str > > >::fields( &test_object ).collect(); - // let fields : Vec< ( &str, OptionalCow< '_, str, WithRef > ) > = test_object.fields().collect(); + // let fields : Vec< ( &str, Option< Cow< '_, str > > ) > = test_object.fields().collect(); assert_eq!( fields.len(), 4 ); - assert!( fields[ 0 ].1.is_borrowed() ); - assert!( !fields[ 1 ].1.is_borrowed() ); - assert!( !fields[ 2 ].1.is_borrowed() ); - assert!( !fields[ 3 ].1.is_borrowed() ); + assert!( is_borrowed( &fields[ 0 ].1 ) ); + assert!( !is_borrowed( &fields[ 1 ].1 ) ); + assert!( !is_borrowed( &fields[ 2 ].1 ) ); + assert!( !is_borrowed( &fields[ 3 ].1 ) ); assert_eq!( fields[ 0 ], ( "id", Some( Cow::Borrowed( "12345" ) ).into() ) ); assert_eq!( fields[ 0 ], ( "id", Some( Cow::Owned( "12345".to_string() ) ).into() ) ); assert_eq!( fields[ 1 ], ( "created_at", Some( Cow::Owned( "1627845583".to_string() ) ).into() ) ); diff --git a/module/core/format_tools/tests/inc/format_records_test.rs b/module/core/format_tools/tests/inc/format_records_test.rs index c1e2cf2b26..386bb51d2e 100644 --- a/module/core/format_tools/tests/inc/format_records_test.rs +++ b/module/core/format_tools/tests/inc/format_records_test.rs @@ -5,16 +5,16 @@ use the_module:: { AsTable, WithRef, - // filter, + filter, print, output_format, }; -// use std:: -// { -// // collections::HashMap, -// // borrow::Cow, -// }; +use std:: +{ + // collections::HashMap, + borrow::Cow, +}; // @@ -23,7 +23,7 @@ fn basic() { let test_objects = test_object::test_objects_gen(); - let _as_table : AsTable< '_, Vec< test_object::TestObject >, usize, test_object::TestObject, str, WithRef > = AsTable::new( &test_objects ); + let _as_table : AsTable< '_, Vec< test_object::TestObject >, usize, test_object::TestObject, str> = AsTable::new( &test_objects ); let as_table = AsTable::new( &test_objects ); let mut output = String::new(); @@ -34,287 +34,469 @@ fn basic() assert!( got.is_ok() ); println!( "{}", &output ); - // -[ RECORD 1 ] - // sid | 3 - // sname | Alice - // gap | 5 - // -[ RECORD 2 ] - // sid | 6 - // sname | Joe - // gap | 1 - // -[ RECORD 3 ] - // sid | 10 - // sname | Boris - // gap | 5 - - let exp = r#"xxx"#; - // a_id!( output.as_str(), exp ); + let exp = r#" = 1 +│ id │ 1 │ +│ created_at │ 1627845583 │ +│ file_ids │ [ │ +│ │ "file1", │ +│ │ "file2", │ +│ │ ] │ +│ tools │ │ + = 2 +│ id │ 2 │ +│ created_at │ 13 │ +│ file_ids │ [ │ +│ │ "file3", │ +│ │ "file4\nmore details", │ +│ │ ] │ +│ tools │ [ │ +│ │ { │ +│ │ "tool1": "value1", │ +│ │ }, │ +│ │ { │ +│ │ "tool2": "value2", │ +│ │ }, │ +│ │ ] │"#; + a_id!( output.as_str(), exp ); } // -// #[ test ] -// fn table_to_string() -// { -// use the_module::TableFormatter; -// let test_objects = test_object::test_objects_gen(); -// -// // with explicit arguments -// -// let as_table : AsTable< '_, Vec< test_object::TestObject >, usize, test_object::TestObject, str, WithRef > = AsTable::new( &test_objects ); -// let table_string = as_table.table_to_string(); -// println!( "\ntable_string\n{table_string}" ); -// assert!( table_string.contains( "id" ) ); -// assert!( table_string.contains( "created_at" ) ); -// assert!( table_string.contains( "file_ids" ) ); -// assert!( table_string.contains( "tools" ) ); -// -// // without explicit arguments -// -// println!( "" ); -// let as_table = AsTable::new( &test_objects ); -// let table_string = as_table.table_to_string(); -// assert!( table_string.contains( "id" ) ); -// assert!( table_string.contains( "created_at" ) ); -// assert!( table_string.contains( "file_ids" ) ); -// assert!( table_string.contains( "tools" ) ); -// println!( "\ntable_string\n{table_string}" ); -// -// } -// -// // -// -// #[ test ] -// fn custom_format() -// { -// // use the_module::TableFormatter; -// let test_objects = test_object::test_objects_gen(); -// -// let mut format = output_format::Ordinary::default(); -// format.cell_prefix = "( ".into(); -// format.cell_postfix = " )".into(); -// format.cell_separator = "|".into(); -// format.row_prefix = ">".into(); -// format.row_postfix = "<".into(); -// format.row_separator = "\n".into(); -// -// let printer = print::Printer::with_format( &format ); -// let as_table = AsTable::new( &test_objects ); -// let mut output = String::new(); -// let mut context = print::Context::new( &mut output, printer ); -// let result = the_module::TableFormatter::fmt( &as_table, &mut context ); -// assert!( result.is_ok() ); -// -// println!( "\noutput\n{output}" ); -// assert!( output.contains( "id" ) ); -// assert!( output.contains( "created_at" ) ); -// assert!( output.contains( "file_ids" ) ); -// assert!( output.contains( "tools" ) ); -// -// let exp = r#">( id )|( created_at )|( file_ids )|( tools )< -// ───────────────────────────────────────────────────────────────────────────────────── -// >( 1 )|( 1627845583 )|( [ )|( )< -// >( )|( )|( "file1", )|( )< -// >( )|( )|( "file2", )|( )< -// >( )|( )|( ] )|( )< -// >( 2 )|( 13 )|( [ )|( [ )< -// >( )|( )|( "file3", )|( { )< -// >( )|( )|( "file4\nmore details", )|( "tool1": "value1", )< -// >( )|( )|( ] )|( }, )< -// >( )|( )|( )|( { )< -// >( )|( )|( )|( "tool2": "value2", )< -// >( )|( )|( )|( }, )< -// >( )|( )|( )|( ] )<"#; -// a_id!( output.as_str(), exp ); -// -// // using table_to_string_with_format -// -// use the_module::TableFormatter; -// -// let mut format = output_format::Ordinary::default(); -// format.cell_prefix = "( ".into(); -// format.cell_postfix = " )".into(); -// format.cell_separator = "|".into(); -// format.row_prefix = ">".into(); -// format.row_postfix = "<".into(); -// format.row_separator = "\n".into(); -// -// // let as_table = AsTable::new( &test_objects ); -// let got = AsTable::new( &test_objects ).table_to_string_with_format( &format ); -// let exp = r#">( id )|( created_at )|( file_ids )|( tools )< -// ───────────────────────────────────────────────────────────────────────────────────── -// >( 1 )|( 1627845583 )|( [ )|( )< -// >( )|( )|( "file1", )|( )< -// >( )|( )|( "file2", )|( )< -// >( )|( )|( ] )|( )< -// >( 2 )|( 13 )|( [ )|( [ )< -// >( )|( )|( "file3", )|( { )< -// >( )|( )|( "file4\nmore details", )|( "tool1": "value1", )< -// >( )|( )|( ] )|( }, )< -// >( )|( )|( )|( { )< -// >( )|( )|( )|( "tool2": "value2", )< -// >( )|( )|( )|( }, )< -// >( )|( )|( )|( ] )<"#; -// a_id!( got, exp ); -// -// } -// -// -// -// #[ test ] -// fn filter_col_none() -// { -// let test_objects = test_object::test_objects_gen(); -// -// let mut format = output_format::Ordinary::default(); -// format.cell_prefix = "( ".into(); -// format.cell_postfix = " )".into(); -// format.cell_separator = "|".into(); -// format.row_prefix = ">".into(); -// format.row_postfix = "<".into(); -// format.row_separator = "\n".into(); -// -// let mut printer = print::Printer::with_format( &format ); -// printer.filter_col = &filter::None; -// -// let as_table = AsTable::new( &test_objects ); -// let mut output = String::new(); -// let mut context = print::Context::new( &mut output, printer ); -// let result = the_module::TableFormatter::fmt( &as_table, &mut context ); -// assert!( result.is_ok() ); -// -// println!( "\noutput\n{output}" ); -// -// let exp = r#">< -// ── -// >< -// ><"#; -// -// a_id!( output.as_str(), exp ); -// -// } -// -// // -// -// #[ test ] -// fn filter_col_callback() -// { -// let test_objects = test_object::test_objects_gen(); -// -// let mut format = output_format::Ordinary::default(); -// format.cell_prefix = "( ".into(); -// format.cell_postfix = " )".into(); -// format.cell_separator = "|".into(); -// format.row_prefix = ">".into(); -// format.row_postfix = "<".into(); -// format.row_separator = "\n".into(); -// -// let mut printer = print::Printer::with_format( &format ); -// printer.filter_col = &| title : &str | -// { -// title != "tools" -// }; -// -// let as_table = AsTable::new( &test_objects ); -// let mut output = String::new(); -// let mut context = print::Context::new( &mut output, printer ); -// let result = the_module::TableFormatter::fmt( &as_table, &mut context ); -// assert!( result.is_ok() ); -// -// println!( "\noutput\n{output}" ); -// -// let exp = r#">( id )|( created_at )|( file_ids )< -// ────────────────────────────────────────────────────── -// >( 1 )|( 1627845583 )|( [ )< -// >( )|( )|( "file1", )< -// >( )|( )|( "file2", )< -// >( )|( )|( ] )< -// >( 2 )|( 13 )|( [ )< -// >( )|( )|( "file3", )< -// >( )|( )|( "file4\nmore details", )< -// >( )|( )|( ] )<"#; -// -// a_id!( output.as_str(), exp ); -// -// } -// -// // -// -// #[ test ] -// fn filter_row_none() -// { -// let test_objects = test_object::test_objects_gen(); -// -// let mut format = output_format::Ordinary::default(); -// format.cell_prefix = "( ".into(); -// format.cell_postfix = " )".into(); -// format.cell_separator = "|".into(); -// format.row_prefix = ">".into(); -// format.row_postfix = "<".into(); -// format.row_separator = "\n".into(); -// -// let mut printer = print::Printer::with_format( &format ); -// printer.filter_row = &filter::None; -// -// let as_table = AsTable::new( &test_objects ); -// let mut output = String::new(); -// let mut context = print::Context::new( &mut output, printer ); -// let result = the_module::TableFormatter::fmt( &as_table, &mut context ); -// assert!( result.is_ok() ); -// -// println!( "\noutput\n{output}" ); -// -// let exp = r#""#; -// -// a_id!( output.as_str(), exp ); -// -// } -// -// // -// -// #[ test ] -// fn filter_row_callback() -// { -// let test_objects = test_object::test_objects_gen(); -// -// let mut format = output_format::Ordinary::default(); -// format.cell_prefix = "( ".into(); -// format.cell_postfix = " )".into(); -// format.cell_separator = "|".into(); -// format.row_prefix = ">".into(); -// format.row_postfix = "<".into(); -// format.row_separator = "\n".into(); -// -// let mut printer = print::Printer::with_format( &format ); -// printer.filter_row = &| _typ, irow, _row : &[ ( Cow< '_, str >, [ usize ; 2 ] ) ] | -// { -// irow != 1 -// }; +#[ test ] +fn unicode() +{ + let test_objects = test_object::test_objects_gen_2_languages(); + + let as_table = AsTable::new( &test_objects ); + + let mut output = String::new(); + let format = output_format::Records::default(); + let printer = print::Printer::with_format( &format ); + let mut context = print::Context::new( &mut output, printer ); + let got = the_module::TableFormatter::fmt( &as_table, &mut context ); + assert!( got.is_ok() ); + println!( "{}", &output ); + + let exp = r#" = 1 +│ id │ Доміно │ +│ created_at │ 100 │ +│ file_ids │ [ │ +│ │ "файл1", │ +│ │ "файл2", │ +│ │ ] │ +│ tools │ [ │ +│ │ { │ +│ │ "тулз1": "значення1", │ +│ │ }, │ +│ │ { │ +│ │ "тулз2": "значення2", │ +│ │ }, │ +│ │ ] │ + = 2 +│ id │ File │ +│ created_at │ 120 │ +│ file_ids │ [ │ +│ │ "file1", │ +│ │ "file2", │ +│ │ ] │ +│ tools │ [ │ +│ │ { │ +│ │ "tools1": "value1", │ +│ │ }, │ +│ │ { │ +│ │ "tools1": "value2", │ +│ │ }, │ +│ │ ] │"#; + a_id!( output.as_str(), exp ); + +} + // -// let as_table = AsTable::new( &test_objects ); -// let mut output = String::new(); -// let mut context = print::Context::new( &mut output, printer ); -// let result = the_module::TableFormatter::fmt( &as_table, &mut context ); -// assert!( result.is_ok() ); + +#[ test ] +fn custom_format() +{ + // use the_module::TableFormatter; + let test_objects = test_object::test_objects_gen(); + + let mut format = output_format::Records::default(); + format.cell_prefix = "( ".into(); + format.cell_postfix = " )".into(); + format.cell_separator = "|".into(); + format.row_prefix = ">".into(); + format.row_postfix = "<".into(); + format.row_separator = "\n".into(); + + let printer = print::Printer::with_format( &format ); + let as_table = AsTable::new( &test_objects ); + let mut output = String::new(); + let mut context = print::Context::new( &mut output, printer ); + let result = the_module::TableFormatter::fmt( &as_table, &mut context ); + assert!( result.is_ok() ); + + println!( "\noutput\n{output}" ); + + let exp = r#" = 1 +>( id )|( 1 )< +>( created_at )|( 1627845583 )< +>( file_ids )|( [ )< +>( )|( "file1", )< +>( )|( "file2", )< +>( )|( ] )< +>( tools )|( )< + = 2 +>( id )|( 2 )< +>( created_at )|( 13 )< +>( file_ids )|( [ )< +>( )|( "file3", )< +>( )|( "file4\nmore details", )< +>( )|( ] )< +>( tools )|( [ )< +>( )|( { )< +>( )|( "tool1": "value1", )< +>( )|( }, )< +>( )|( { )< +>( )|( "tool2": "value2", )< +>( )|( }, )< +>( )|( ] )<"#; + a_id!( output.as_str(), exp ); + + // using table_to_string_with_format + + use the_module::TableFormatter; + + let mut format = output_format::Records::default(); + format.cell_prefix = "( ".into(); + format.cell_postfix = " )".into(); + format.cell_separator = "|".into(); + format.row_prefix = ">".into(); + format.row_postfix = "<".into(); + format.row_separator = "\n".into(); + + // let as_table = AsTable::new( &test_objects ); + let got = AsTable::new( &test_objects ).table_to_string_with_format( &format ); + let exp = r#" = 1 +>( id )|( 1 )< +>( created_at )|( 1627845583 )< +>( file_ids )|( [ )< +>( )|( "file1", )< +>( )|( "file2", )< +>( )|( ] )< +>( tools )|( )< + = 2 +>( id )|( 2 )< +>( created_at )|( 13 )< +>( file_ids )|( [ )< +>( )|( "file3", )< +>( )|( "file4\nmore details", )< +>( )|( ] )< +>( tools )|( [ )< +>( )|( { )< +>( )|( "tool1": "value1", )< +>( )|( }, )< +>( )|( { )< +>( )|( "tool2": "value2", )< +>( )|( }, )< +>( )|( ] )<"#; + a_id!( got, exp ); + +} + // -// println!( "\noutput\n{output}" ); + +#[ test ] +fn filter_col_none() +{ + let test_objects = test_object::test_objects_gen(); + + let mut format = output_format::Records::default(); + format.cell_prefix = "( ".into(); + format.cell_postfix = " )".into(); + format.cell_separator = "|".into(); + format.row_prefix = ">".into(); + format.row_postfix = "<".into(); + format.row_separator = "\n".into(); + + let mut printer = print::Printer::with_format( &format ); + printer.filter_col = &filter::None; + + let as_table = AsTable::new( &test_objects ); + let mut output = String::new(); + let mut context = print::Context::new( &mut output, printer ); + let result = the_module::TableFormatter::fmt( &as_table, &mut context ); + assert!( result.is_ok() ); + println!( "\noutput\n{output}" ); + + let exp = r#" = 1 + + = 2 +"#; + + a_id!( output.as_str(), exp ); + +} + // -// let exp = r#">( id )|( created_at )|( file_ids )|( tools )< -// ───────────────────────────────────────────────────────────────────────────────────── -// >( 2 )|( 13 )|( [ )|( [ )< -// >( )|( )|( "file3", )|( { )< -// >( )|( )|( "file4\nmore details", )|( "tool1": "value1", )< -// >( )|( )|( ] )|( }, )< -// >( )|( )|( )|( { )< -// >( )|( )|( )|( "tool2": "value2", )< -// >( )|( )|( )|( }, )< -// >( )|( )|( )|( ] )<"#; + +#[ test ] +fn filter_col_callback() +{ + let test_objects = test_object::test_objects_gen(); + + let mut format = output_format::Records::default(); + format.cell_prefix = "( ".into(); + format.cell_postfix = " )".into(); + format.cell_separator = "|".into(); + format.row_prefix = ">".into(); + format.row_postfix = "<".into(); + format.row_separator = "\n".into(); + + let mut printer = print::Printer::with_format( &format ); + printer.filter_col = &| title : &str | + { + title != "tools" + }; + + let as_table = AsTable::new( &test_objects ); + let mut output = String::new(); + let mut context = print::Context::new( &mut output, printer ); + let result = the_module::TableFormatter::fmt( &as_table, &mut context ); + assert!( result.is_ok() ); + println!( "\noutput\n{output}" ); + + let exp = r#" = 1 +>( id )|( 1 )< +>( created_at )|( 1627845583 )< +>( file_ids )|( [ )< +>( )|( "file1", )< +>( )|( "file2", )< +>( )|( ] )< + = 2 +>( id )|( 2 )< +>( created_at )|( 13 )< +>( file_ids )|( [ )< +>( )|( "file3", )< +>( )|( "file4\nmore details", )< +>( )|( ] )<"#; + + a_id!( output.as_str(), exp ); + +} + // -// a_id!( output.as_str(), exp ); + +#[ test ] +fn filter_row_none() +{ + let test_objects = test_object::test_objects_gen(); + + let mut format = output_format::Records::default(); + format.cell_prefix = "( ".into(); + format.cell_postfix = " )".into(); + format.cell_separator = "|".into(); + format.row_prefix = ">".into(); + format.row_postfix = "<".into(); + format.row_separator = "\n".into(); + + let mut printer = print::Printer::with_format( &format ); + printer.filter_row = &filter::None; + + let as_table = AsTable::new( &test_objects ); + let mut output = String::new(); + let mut context = print::Context::new( &mut output, printer ); + let result = the_module::TableFormatter::fmt( &as_table, &mut context ); + assert!( result.is_ok() ); + + println!( "\noutput\n{output}" ); + + let exp = r#""#; + + a_id!( output.as_str(), exp ); + +} + // -// } + +#[ test ] +fn filter_row_callback() +{ + let test_objects = test_object::test_objects_gen(); + + let mut format = output_format::Records::default(); + format.cell_prefix = "( ".into(); + format.cell_postfix = " )".into(); + format.cell_separator = "|".into(); + format.row_prefix = ">".into(); + format.row_postfix = "<".into(); + format.row_separator = "\n".into(); + + let mut printer = print::Printer::with_format( &format ); + printer.filter_row = &| _typ, irow, _row : &[ ( Cow< '_, str >, [ usize ; 2 ] ) ] | + { + irow != 1 + }; + + let as_table = AsTable::new( &test_objects ); + let mut output = String::new(); + let mut context = print::Context::new( &mut output, printer ); + let result = the_module::TableFormatter::fmt( &as_table, &mut context ); + assert!( result.is_ok() ); + + println!( "\noutput\n{output}" ); + + let exp = r#" = 2 +>( id )|( 2 )< +>( created_at )|( 13 )< +>( file_ids )|( [ )< +>( )|( "file3", )< +>( )|( "file4\nmore details", )< +>( )|( ] )< +>( tools )|( [ )< +>( )|( { )< +>( )|( "tool1": "value1", )< +>( )|( }, )< +>( )|( { )< +>( )|( "tool2": "value2", )< +>( )|( }, )< +>( )|( ] )<"#; + + a_id!( output.as_str(), exp ); + +} + // -// // -// xxx : enable \ No newline at end of file +// xxx : enable + +#[ test ] +fn test_width_limiting() +{ + use the_module::string; + + for width in min_width()..max_width() + { + println!("width: {}", width); + + let test_objects = test_object::test_objects_gen(); + let as_table = AsTable::new( &test_objects ); + + let mut format = output_format::Records::default(); + format.max_width = width; + + let mut output = String::new(); + let printer = print::Printer::with_format( &format ); + let mut context = print::Context::new( &mut output, printer ); + + let got = the_module::TableFormatter::fmt( &as_table, &mut context ); + + assert!( got.is_ok() ); + + for line in string::lines( &output ) + { + if line.starts_with(" = ") + { + continue; + } + + if line.chars().count() > width + { + println!("{}", output); + } + + assert!( line.chars().count() <= width ); + } + } +} + +#[ test ] +fn test_error_on_unsatisfiable_limit() +{ + // 0 is a special value that signifies no limit. + for width in 1..( min_width() ) + { + println!( "width: {}", width ); + + let test_objects = test_object::test_objects_gen(); + let as_table = AsTable::new( &test_objects ); + + let mut format = output_format::Records::default(); + format.max_width = width; + + let mut output = String::new(); + let printer = print::Printer::with_format( &format ); + let mut context = print::Context::new( &mut output, printer ); + + let got = the_module::TableFormatter::fmt( &as_table, &mut context ); + + assert!( got.is_err() ); + } +} + +#[ test ] +fn test_table_not_grows() +{ + use the_module::string; + + let expected_width = max_width(); + + // The upper bound was chosen arbitrarily. + for width in ( expected_width + 1 )..500 + { + println!( "width: {}", width ); + + let test_objects = test_object::test_objects_gen(); + let as_table = AsTable::new( &test_objects ); + + let mut format = output_format::Records::default(); + format.max_width = width; + + let mut output = String::new(); + let printer = print::Printer::with_format( &format ); + let mut context = print::Context::new( &mut output, printer ); + + let got = the_module::TableFormatter::fmt( &as_table, &mut context ); + + assert!( got.is_ok() ); + println!("{}", output); + + for line in string::lines( &output ) + { + if line.starts_with(" = ") + { + continue; + } + + assert!( line.chars().count() <= expected_width ); + } + } +} + +/// Utility function for calculating minimum table width with `test_objects_gen()` with +/// the default table style. +fn min_width() -> usize +{ + let format = output_format::Records::default(); + format.min_width() +} + +/// Utility function for calculating default table width with `test_objects_gen()` with +/// the default table style with table width limit equals to 0. +fn max_width() -> usize +{ + use the_module::string; + + let test_objects = test_object::test_objects_gen(); + let as_table = AsTable::new( &test_objects ); + + let format = output_format::Records::default(); + + let mut output = String::new(); + let printer = print::Printer::with_format( &format ); + let mut context = print::Context::new( &mut output, printer ); + + let got = the_module::TableFormatter::fmt( &as_table, &mut context ); + assert!( got.is_ok() ); + + string::lines( &output ).map( |s| s.chars().count() ).max().unwrap_or(0) +} \ No newline at end of file diff --git a/module/core/format_tools/tests/inc/format_ordinary_test.rs b/module/core/format_tools/tests/inc/format_table_test.rs similarity index 57% rename from module/core/format_tools/tests/inc/format_ordinary_test.rs rename to module/core/format_tools/tests/inc/format_table_test.rs index f0854cd6c0..9adcba28a0 100644 --- a/module/core/format_tools/tests/inc/format_ordinary_test.rs +++ b/module/core/format_tools/tests/inc/format_table_test.rs @@ -23,7 +23,7 @@ fn basic() { let test_objects = test_object::test_objects_gen(); - let _as_table : AsTable< '_, Vec< test_object::TestObject >, usize, test_object::TestObject, str, WithRef > = AsTable::new( &test_objects ); + let _as_table : AsTable< '_, Vec< test_object::TestObject >, usize, test_object::TestObject, str> = AsTable::new( &test_objects ); let as_table = AsTable::new( &test_objects ); let mut output = String::new(); @@ -70,7 +70,7 @@ fn table_to_string() // with explicit arguments - let as_table : AsTable< '_, Vec< test_object::TestObject >, usize, test_object::TestObject, str, WithRef > = AsTable::new( &test_objects ); + let as_table : AsTable< '_, Vec< test_object::TestObject >, usize, test_object::TestObject, str> = AsTable::new( &test_objects ); let table_string = as_table.table_to_string(); println!( "\ntable_string\n{table_string}" ); assert!( table_string.contains( "id" ) ); @@ -99,7 +99,7 @@ fn custom_format() // use the_module::TableFormatter; let test_objects = test_object::test_objects_gen(); - let mut format = output_format::Ordinary::default(); + let mut format = output_format::Table::default(); format.cell_prefix = "( ".into(); format.cell_postfix = " )".into(); format.cell_separator = "|".into(); @@ -140,7 +140,7 @@ fn custom_format() use the_module::TableFormatter; - let mut format = output_format::Ordinary::default(); + let mut format = output_format::Table::default(); format.cell_prefix = "( ".into(); format.cell_postfix = " )".into(); format.cell_separator = "|".into(); @@ -175,7 +175,7 @@ fn filter_col_none() { let test_objects = test_object::test_objects_gen(); - let mut format = output_format::Ordinary::default(); + let mut format = output_format::Table::default(); format.cell_prefix = "( ".into(); format.cell_postfix = " )".into(); format.cell_separator = "|".into(); @@ -210,7 +210,7 @@ fn filter_col_callback() { let test_objects = test_object::test_objects_gen(); - let mut format = output_format::Ordinary::default(); + let mut format = output_format::Table::default(); format.cell_prefix = "( ".into(); format.cell_postfix = " )".into(); format.cell_separator = "|".into(); @@ -254,7 +254,7 @@ fn filter_row_none() { let test_objects = test_object::test_objects_gen(); - let mut format = output_format::Ordinary::default(); + let mut format = output_format::Table::default(); format.cell_prefix = "( ".into(); format.cell_postfix = " )".into(); format.cell_separator = "|".into(); @@ -286,7 +286,7 @@ fn filter_row_callback() { let test_objects = test_object::test_objects_gen(); - let mut format = output_format::Ordinary::default(); + let mut format = output_format::Table::default(); format.cell_prefix = "( ".into(); format.cell_postfix = " )".into(); format.cell_separator = "|".into(); @@ -324,3 +324,212 @@ fn filter_row_callback() } // + +// xxx : implement test for vector of vectors + +// + +#[ test ] +fn no_subtract_with_overflow() +{ + let test_objects = test_object::test_objects_gen_with_unicode(); + + let format = output_format::Table::default(); + let printer = print::Printer::with_format( &format ); + + let as_table = AsTable::new( &test_objects ); + let mut output = String::new(); + let mut context = print::Context::new( &mut output, printer ); + + let result = the_module::TableFormatter::fmt( &as_table, &mut context ); + + assert!( result.is_ok() ); +} + +#[ test ] +fn test_width_limiting() +{ + use the_module::string; + + for max_width in min_width()..max_width() + { + println!("max_width: {}", max_width); + + let test_objects = test_object::test_objects_gen(); + let as_table = AsTable::new( &test_objects ); + + let mut format = output_format::Table::default(); + format.max_width = max_width; + + let mut output = String::new(); + let printer = print::Printer::with_format( &format ); + let mut context = print::Context::new( &mut output, printer ); + + let got = the_module::TableFormatter::fmt( &as_table, &mut context ); + + assert!( got.is_ok() ); + + for line in string::lines( &output ) + { + assert_eq!( max_width, line.chars().count() ); + } + } +} + +#[ test ] +fn test_error_on_unsatisfiable_limit() +{ + // 0 is a special value that signifies no limit. Therefore, the lower bound is 1. + for max_width in 1..( min_width() ) + { + println!( "max_width: {}", max_width ); + + let test_objects = test_object::test_objects_gen(); + let as_table = AsTable::new( &test_objects ); + + let mut format = output_format::Table::default(); + format.max_width = max_width; + + let mut output = String::new(); + let printer = print::Printer::with_format( &format ); + let mut context = print::Context::new( &mut output, printer ); + + let got = the_module::TableFormatter::fmt( &as_table, &mut context ); + + assert!( got.is_err() ); + } +} + +#[ test ] +fn test_table_not_grows() +{ + use the_module::string; + + let expected_width = max_width(); + + // The upper bound was chosen arbitrarily. + for max_width in ( expected_width + 1 )..500 + { + println!( "max_width: {}", max_width ); + + let test_objects = test_object::test_objects_gen(); + let as_table = AsTable::new( &test_objects ); + + let mut format = output_format::Table::default(); + format.max_width = max_width; + + let mut output = String::new(); + let printer = print::Printer::with_format( &format ); + let mut context = print::Context::new( &mut output, printer ); + + let got = the_module::TableFormatter::fmt( &as_table, &mut context ); + + assert!( got.is_ok() ); + + for line in string::lines( &output ) + { + assert_eq!( expected_width, line.chars().count() ); + } + } +} + +/// Utility function for calculating minimum table width with `test_objects_gen()` with +/// the default table style. +fn min_width() -> usize +{ + use the_module::Fields; + + let format = output_format::Table::default(); + let test_objects = test_object::test_objects_gen(); + let col_count = test_objects[0].fields().count(); + + format.min_width( col_count ) +} + +/// Utility function for calculating default table width with `test_objects_gen()` with +/// the default table style without any maximum width. +fn max_width() -> usize +{ + use the_module::string; + + let test_objects = test_object::test_objects_gen(); + let as_table = AsTable::new( &test_objects ); + + let format = output_format::Table::default(); + + let mut output = String::new(); + let printer = print::Printer::with_format( &format ); + let mut context = print::Context::new( &mut output, printer ); + + let got = the_module::TableFormatter::fmt( &as_table, &mut context ); + assert!( got.is_ok() ); + + for line in string::lines( &output ) + { + return line.chars().count(); + } + + 0 +} + +#[ test ] +fn ukrainian_chars() +{ + let test_objects = test_object::test_objects_gen_with_unicode(); + let as_table = AsTable::new( &test_objects ); + + let mut output = String::new(); + let mut context = print::Context::new( &mut output, Default::default() ); + let got = the_module::TableFormatter::fmt( &as_table, &mut context ); + assert!( got.is_ok() ); + println!( "{}", &output ); + + let exp = r#"│ id │ created_at │ file_ids │ tools │ +─────────────────────────────────────────────────────────────────────────────────────────────────────── +│ Доміно │ 100 │ [ │ │ +│ │ │ "файл1", │ │ +│ │ │ "файл2", │ │ +│ │ │ ] │ │ +│ Інший юнікод │ 120 │ [] │ [ │ +│ │ │ │ { │ +│ │ │ │ "тулз1": "значення1", │ +│ │ │ │ }, │ +│ │ │ │ { │ +│ │ │ │ "тулз2": "значення2", │ +│ │ │ │ }, │ +│ │ │ │ ] │"#; + a_id!( output.as_str(), exp ); +} + +#[ test ] +fn ukrainian_and_english_chars() +{ + let test_objects = test_object::test_objects_gen_2_languages(); + let as_table = AsTable::new( &test_objects ); + + let mut output = String::new(); + let mut context = print::Context::new( &mut output, Default::default() ); + let got = the_module::TableFormatter::fmt( &as_table, &mut context ); + assert!( got.is_ok() ); + println!( "{}", &output ); + + let exp = r#"│ id │ created_at │ file_ids │ tools │ +──────────────────────────────────────────────────────────────────────────────────────────── +│ Доміно │ 100 │ [ │ [ │ +│ │ │ "файл1", │ { │ +│ │ │ "файл2", │ "тулз1": "значення1", │ +│ │ │ ] │ }, │ +│ │ │ │ { │ +│ │ │ │ "тулз2": "значення2", │ +│ │ │ │ }, │ +│ │ │ │ ] │ +│ File │ 120 │ [ │ [ │ +│ │ │ "file1", │ { │ +│ │ │ "file2", │ "tools1": "value1", │ +│ │ │ ] │ }, │ +│ │ │ │ { │ +│ │ │ │ "tools1": "value2", │ +│ │ │ │ }, │ +│ │ │ │ ] │"#; + a_id!( output.as_str(), exp ); +} \ No newline at end of file diff --git a/module/core/format_tools/tests/inc/mod.rs b/module/core/format_tools/tests/inc/mod.rs index 5188190547..5f35b24fcc 100644 --- a/module/core/format_tools/tests/inc/mod.rs +++ b/module/core/format_tools/tests/inc/mod.rs @@ -1,18 +1,19 @@ -#[ allow( unused_imports ) ] use super::*; #[ cfg( feature = "enabled" ) ] #[ path = "." ] mod fundamental { - #[ allow( unused_imports ) ] use super::*; mod test_object; mod table_test; - mod format_ordinary_test; + mod tabe_foreign_test; + + mod format_table_test; mod format_records_test; + // mod format_keys_test; // qqq : xxx : implement mod collection_test; mod fields_test; diff --git a/module/core/format_tools/tests/inc/tabe_foreign_test.rs b/module/core/format_tools/tests/inc/tabe_foreign_test.rs new file mode 100644 index 0000000000..6cbfd68249 --- /dev/null +++ b/module/core/format_tools/tests/inc/tabe_foreign_test.rs @@ -0,0 +1,131 @@ +use super::*; + +use the_module:: +{ + AsTable, + Cells, + TableRows, + TableHeader, + WithRef, +}; + +use std:: +{ + borrow::Cow, +}; + +// + +#[ test ] +fn iterator_over_objects_without_impl() +{ + use the_module::TestObjectWithoutImpl as TestObjectWithoutImpl; + use the_module:: + { + Fields, + IteratorTrait, + TableWithFields, + WithRef, + OptionalCow, + output_format, + }; + + // xxx : Clone should not be required + #[ derive( Debug, Clone ) ] + pub struct TestObjecWrap( TestObjectWithoutImpl ); + + impl TableWithFields for TestObjecWrap {} + impl Fields< &'_ str, Option< Cow< '_, str > > > + for TestObjecWrap + { + type Key< 'k > = &'k str; + type Val< 'v > = Option< Cow< 'v, str > >; + + fn fields( &self ) -> impl 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.created_at ) ); + dst.push( field!( &self.0.file_ids ) ); + + if let Some( tools ) = &self.0.tools + { + dst.push( field!( tools ) ); + } + else + { + dst.push( ( "tools", Option::None ) ); + } + + dst.into_iter() + } + + } + + let data : collection_tools::Vec< TestObjecWrap > = the_module::test_objects_gen() + .into_iter() + .map( | e | TestObjecWrap( e ) ) + .collect() + ; + + use the_module::TableFormatter; + let _as_table : AsTable< '_, Vec< TestObjecWrap >, &str, TestObjecWrap, str > = AsTable::new( &data ); + let as_table = AsTable::new( &data ); + + let rows = TableRows::rows( &as_table ); + assert_eq!( rows.len(), 2 ); + + let mut output = String::new(); + let mut context = the_module::print::Context::new( &mut output, Default::default() ); + let _result = the_module::TableFormatter::fmt( &as_table, &mut context ); + + // = output as table + + let got = as_table.table_to_string(); + assert!( got.contains( "│ id │ created_at │ file_ids │ tools │" ) ); + assert!( got.contains( "│ 13 │ [ │ [ │" ) ); + assert!( got.contains( "│ 1627845583 │ [ │ │" ) ); + + let got = AsTable::new( &data ).table_to_string(); + assert!( got.contains( "│ id │ created_at │ file_ids │ tools │" ) ); + assert!( got.contains( "│ 13 │ [ │ [ │" ) ); + assert!( got.contains( "│ 1627845583 │ [ │ │" ) ); + + let got = AsTable::new( &data ).table_to_string_with_format( &output_format::Table::default() ); + println!( "{}", &got ); + assert!( got.contains( "│ id │ created_at │ file_ids │ tools │" ) ); + assert!( got.contains( "│ 13 │ [ │ [ │" ) ); + assert!( got.contains( "│ 1627845583 │ [ │ │" ) ); + + // = output as records + + // let format = output_format::Records::default(); + let mut output = String::new(); + let format = the_module::output_format::Records::default(); + let printer = the_module::print::Printer::with_format( &format ); + let mut context = the_module::print::Context::new( &mut output, printer ); + let got = the_module::TableFormatter::fmt( &as_table, &mut context ); + assert!( got.is_ok() ); + + let got = AsTable::new( &data ).table_to_string_with_format( &output_format::Records::default() ); + println!( "{}", &got ); + assert!( got.contains( "│ id │ 1 │" ) ); + assert!( got.contains( "│ created_at │ 1627845583 │" ) ); + assert!( got.contains( "│ id │ 2 │" ) ); + assert!( got.contains( "│ created_at │ 13 │" ) ); + + // = output as keys + + let got = AsTable::new( &data ).table_to_string_with_format( &output_format::Keys::default() ); + println!( "{}", &got ); + assert!( got.contains( "- id" ) ); + assert!( got.contains( "- created_at" ) ); + assert!( got.contains( "- file_ids" ) ); + assert!( got.contains( "- tools" ) ); + assert!( got.contains( "4 fields" ) ); + + // assert!( false ); + +} diff --git a/module/core/format_tools/tests/inc/table_test.rs b/module/core/format_tools/tests/inc/table_test.rs index 2357d44aea..af57655085 100644 --- a/module/core/format_tools/tests/inc/table_test.rs +++ b/module/core/format_tools/tests/inc/table_test.rs @@ -10,6 +10,11 @@ use the_module:: WithRef, }; +use std:: +{ + borrow::Cow, +}; + // #[ test ] @@ -19,13 +24,13 @@ fn basic() { let test_objects = test_object::test_objects_gen(); - let cells = Cells::< str, WithRef >::cells( &test_objects[ 0 ] ); + let cells = Cells::< str>::cells( &test_objects[ 0 ] ); assert_eq!( cells.len(), 4 ); - let cells = Cells::< str, WithRef >::cells( &test_objects[ 1 ] ); + let cells = Cells::< str>::cells( &test_objects[ 1 ] ); assert_eq!( cells.len(), 4 ); drop( cells ); - let as_table : AsTable< '_, Vec< test_object::TestObject >, usize, test_object::TestObject, str, WithRef > = AsTable::new( &test_objects ); + let as_table : AsTable< '_, Vec< test_object::TestObject >, usize, test_object::TestObject, str> = AsTable::new( &test_objects ); // let mcells = TableSize::mcells( &as_table ); // assert_eq!( mcells, [ 4, 3 ] ); let rows = TableRows::rows( &as_table ); @@ -45,3 +50,310 @@ fn basic() dbg!( header.collect::< Vec< _ > >() ); } + +// + +#[ test ] +fn iterator_over_optional_cow() +{ + // use test_object::TestObject2 as TestObject2; + use the_module:: + { + Fields, + IteratorTrait, + TableWithFields, + WithRef, + OptionalCow, + }; + + /// Struct representing a test object with various fields. + #[ derive( Clone, Debug, PartialEq, Eq ) ] + pub struct TestObject2 + { + pub id : String, + pub created_at : i64, + pub file_ids : Vec< String >, + pub tools : Option< Vec< HashMap< String, String > > >, + } + + impl TableWithFields for TestObject2 {} + + impl Fields< &'_ str, Option< Cow< '_, str > > > + for TestObject2 + { + type Key< 'k > = &'k str; + type Val< 'v > = Option< Cow< 'v, str > >; + + fn fields( &self ) -> impl IteratorTrait< Item = ( &'_ str, Option< Cow< '_, str > > ) > + { + use format_tools::ref_or_display_or_debug_multiline::field; + // use format_tools::ref_or_display_or_debug::field; + let mut dst : Vec< ( &'_ str, Option< Cow< '_, str > > ) > = Vec::new(); + + // trace_macros!( true ); + dst.push( field!( &self.id ) ); + // trace_macros!( false ); + + dst.push( field!( &self.created_at ) ); + dst.push( field!( &self.file_ids ) ); + + if let Some( tools ) = &self.tools + { + dst.push( field!( tools ) ); + } + else + { + dst.push( ( "tools", Option::None ) ); + } + + dst.into_iter() + } + + } + + let data : collection_tools::Vec< TestObject2 > = dlist! + { + TestObject2 + { + id : "1".to_string(), + created_at : 1627845583, + file_ids : vec![ "file1".to_string(), "file2".to_string() ], + tools : None + }, + TestObject2 + { + id : "2".to_string(), + created_at : 13, + file_ids : vec![ "file3".to_string(), "file4\nmore details".to_string() ], + tools : Some + ( + vec! + [ + { + let mut map = HashMap::new(); + map.insert( "tool1".to_string(), "value1".to_string() ); + map + }, + { + let mut map = HashMap::new(); + map.insert( "tool2".to_string(), "value2".to_string() ); + map + } + ] + ), + }, + }; + + use the_module::TableFormatter; + let _as_table : AsTable< '_, Vec< TestObject2 >, &str, TestObject2, str> = AsTable::new( &data ); + let as_table = AsTable::new( &data ); + + let rows = TableRows::rows( &as_table ); + assert_eq!( rows.len(), 2 ); + + let mut output = String::new(); + let mut context = the_module::print::Context::new( &mut output, Default::default() ); + let _got = the_module::TableFormatter::fmt( &as_table, &mut context ); + let got = as_table.table_to_string(); + assert!( got.contains( "│ id │ created_at │ file_ids │ tools │" ) ); + assert!( got.contains( "│ 13 │ [ │ [ │" ) ); + assert!( got.contains( "│ 1627845583 │ [ │ │" ) ); + + let got = AsTable::new( &data ).table_to_string(); + assert!( got.contains( "│ id │ created_at │ file_ids │ tools │" ) ); + assert!( got.contains( "│ 13 │ [ │ [ │" ) ); + assert!( got.contains( "│ 1627845583 │ [ │ │" ) ); + +} + +// + +#[ test ] +fn iterator_over_strings() +{ + + fn to_owned< 'a, T1 >( src : ( T1, Option< Cow< 'a, str > > ) ) -> ( T1, String ) + { + let val = match src.1 + { + Some( c ) => c.into_owned(), + None => String::default(), + }; + ( src.0, val ) + } + + // fn into< 'a, T1, T2 : Copy >( src : ( T1, OptionalCow< 'a, str, T2 > ) ) -> ( T1, Option< Cow< 'a, str > > ) + // { + // ( src.0, src.1.into() ) + // } + + // use test_object::TestObject as TestObject3; + use the_module:: + { + Fields, + IteratorTrait, + TableWithFields, + WithRef, + OptionalCow, + }; + + use std::borrow::Cow; + + /// Struct representing a test object with various fields. + #[ derive( Clone, Debug, PartialEq, Eq ) ] + pub struct TestObject3 + { + pub id : String, + pub created_at : i64, + pub file_ids : Vec< String >, + pub tools : Option< Vec< HashMap< String, String > > >, + } + + impl TableWithFields for TestObject3 {} + + impl Fields< &'_ str, String > + for TestObject3 + { + type Key< 'k > = &'k str; + type Val< 'v > = String; + + fn fields( &self ) -> impl IteratorTrait< Item = ( &'_ str, String ) > + { + use format_tools::ref_or_display_or_debug_multiline::field; + // use format_tools::ref_or_display_or_debug::field; + let mut dst : Vec< ( &'_ str, String ) > = Vec::new(); + + dst.push( to_owned( field!( &self.id ) ) ); + + dst.push( to_owned( field!( &self.created_at ) ) ); + dst.push( to_owned( field!( &self.file_ids ) ) ); + + if let Some( tools ) = &self.tools + { + dst.push( to_owned( field!( tools ) ) ); + } + else + { + dst.push( ( "tools", String::default() ) ); + } + + dst.into_iter() + } + + } + + let _data : collection_tools::Vec< TestObject3 > = dlist! + { + TestObject3 + { + id : "1".to_string(), + created_at : 1627845583, + file_ids : vec![ "file1".to_string(), "file2".to_string() ], + tools : None + }, + TestObject3 + { + id : "2".to_string(), + created_at : 13, + file_ids : vec![ "file3".to_string(), "file4\nmore details".to_string() ], + tools : Some + ( + vec! + [ + { + let mut map = HashMap::new(); + map.insert( "tool1".to_string(), "value1".to_string() ); + map + }, + { + let mut map = HashMap::new(); + map.insert( "tool2".to_string(), "value2".to_string() ); + map + } + ] + ), + }, + }; + + // no variability in what Fields iterate over by design! + + // use the_module::TableFormatter; + // let _as_table : AsTable< '_, Vec< TestObject3 >, &str, TestObject3, str> = AsTable::new( &data ); + // let as_table = AsTable::new( &data ); + +// let rows = TableRows::rows( &as_table ); +// assert_eq!( rows.len(), 2 ); +// +// let mut output = String::new(); +// let mut context = the_module::print::Context::new( &mut output, Default::default() ); +// let _got = the_module::TableFormatter::fmt( &as_table, &mut context ); +// let got = as_table.table_to_string(); +// assert!( got.contains( "│ id │ created_at │ file_ids │ tools │" ) ); +// assert!( got.contains( "│ 13 │ [ │ [ │" ) ); +// assert!( got.contains( "│ 1627845583 │ [ │ │" ) ); + +// let got = AsTable::new( &data ).table_to_string(); +// assert!( got.contains( "│ id │ created_at │ file_ids │ tools │" ) ); +// assert!( got.contains( "│ 13 │ [ │ [ │" ) ); +// assert!( got.contains( "│ 1627845583 │ [ │ │" ) ); + +} + +#[ test ] +fn test_vector_table() +{ + let column_names : Vec< Cow< 'static, str > > = vec![ + "id".into(), + "created_at".into(), + "file_ids".into(), + "tools".into(), + ]; + + let rows : Vec< Vec< Cow< 'static, str > > > = vec! + [ + vec! + [ + "1".into(), + "1627845583".into(), + "[ file1, file2 ]".into(), + "".into(), + ], + + vec! + [ + "2".into(), + "13".into(), + "[ file3, file4 ]".into(), + "[ tool1 ]".into(), + ], + ]; + + use the_module:: + { + output_format, + filter, + print, + }; + + let mut output = String::new(); + let mut context = print::Context::new( &mut output, Default::default() ); + + let res = output_format::vector_table_write + ( + column_names, + true, + rows, + &mut context, + ); + + assert!( res.is_ok() ); + + println!( "{}", output ); + + let exp = r#"│ id │ created_at │ file_ids │ tools │ +────────────────────────────────────────────────── +│ 1 │ 1627845583 │ [ file1, file2 ] │ │ +│ 2 │ 13 │ [ file3, file4 ] │ [ tool1 ] │"#; + + a_id!( output.as_str(), exp ); +} \ No newline at end of file diff --git a/module/core/format_tools/tests/inc/test_object.rs b/module/core/format_tools/tests/inc/test_object.rs index 729a96fa85..019b3eb9d2 100644 --- a/module/core/format_tools/tests/inc/test_object.rs +++ b/module/core/format_tools/tests/inc/test_object.rs @@ -13,11 +13,14 @@ use the_module:: use std:: { collections::HashMap, - // borrow::Cow, + hash::Hasher, + hash::Hash, + cmp::Ordering, + borrow::Cow, }; /// Struct representing a test object with various fields. -#[ derive( Clone, Debug ) ] +#[ derive( Clone, Debug, PartialEq, Eq ) ] pub struct TestObject { pub id : String, @@ -28,19 +31,51 @@ pub struct TestObject impl TableWithFields for TestObject {} -impl Fields< &'_ str, OptionalCow< '_, str, WithRef > > +// impl Fields< &'_ str, Option< Cow< '_, str > > > +// for TestObject +// { +// type Key< 'k > = &'k str; +// type Val< 'v > = OptionalCow< 'v, str>; +// +// fn fields( &self ) -> impl IteratorTrait< Item = ( &'_ str, Option< Cow< '_, str > > ) > +// { +// use format_tools::ref_or_display_or_debug_multiline::field; +// // use format_tools::ref_or_display_or_debug::field; +// let mut dst : Vec< ( &'_ str, Option< Cow< '_, str > > ) > = Vec::new(); +// +// dst.push( field!( &self.id ) ); +// dst.push( field!( &self.created_at ) ); +// dst.push( field!( &self.file_ids ) ); +// +// if let Some( tools ) = &self.tools +// { +// dst.push( field!( tools ) ); +// } +// else +// { +// dst.push( ( "tools", OptionalCow::none() ) ); +// } +// +// dst.into_iter() +// } +// +// } + +impl Fields< &'_ str, Option< Cow< '_, str > > > for TestObject { type Key< 'k > = &'k str; - type Val< 'v > = OptionalCow< 'v, str, WithRef >; + type Val< 'v > = Option< Cow< 'v, str > >; - fn fields( &self ) -> impl IteratorTrait< Item = ( &'_ str, OptionalCow< '_, str, WithRef > ) > + fn fields( &self ) -> impl IteratorTrait< Item = ( &'_ str, Option< Cow< '_, str > > ) > { use format_tools::ref_or_display_or_debug_multiline::field; // use format_tools::ref_or_display_or_debug::field; - let mut dst : Vec< ( &'_ str, OptionalCow< '_, str, WithRef > ) > = Vec::new(); + let mut dst : Vec< ( &'_ str, Option< Cow< '_, str > > ) > = Vec::new(); + // trace_macros!( true ); dst.push( field!( &self.id ) ); + // trace_macros!( false ); dst.push( field!( &self.created_at ) ); dst.push( field!( &self.file_ids ) ); @@ -50,7 +85,7 @@ for TestObject } else { - dst.push( ( "tools", OptionalCow::none() ) ); + dst.push( ( "tools", Option::None ) ); } dst.into_iter() @@ -58,6 +93,74 @@ for TestObject } +impl Hash for TestObject +{ + + fn hash< H: Hasher >( &self, state: &mut H ) + { + self.id.hash( state ); + self.created_at.hash( state ); + self.file_ids.hash( state ); + + if let Some( tools ) = &self.tools + { + for tool in tools + { + for ( key, value ) in tool + { + key.hash( state ); + value.hash( state ); + } + } + } + else + { + state.write_u8( 0 ); + } + } + +} + +// impl PartialEq for TestObject +// { +// +// fn eq( &self, other: &Self ) -> bool +// { +// self.id == other.id && +// self.created_at == other.created_at && +// self.file_ids == other.file_ids && +// self.tools == other.tools +// } +// +// } +// +// impl Eq for TestObject +// { +// } + +impl PartialOrd for TestObject +{ + + fn partial_cmp( &self, other: &Self ) -> Option< Ordering > + { + Some( self.cmp( other ) ) + } + +} + +impl Ord for TestObject +{ + + fn cmp( &self, other: &Self ) -> Ordering + { + self.id + .cmp( &other.id ) + .then_with( | | self.created_at.cmp( &other.created_at ) ) + .then_with( | | self.file_ids.cmp( &other.file_ids ) ) + } + +} + // pub fn test_objects_gen() -> Vec< TestObject > @@ -97,3 +200,90 @@ pub fn test_objects_gen() -> Vec< TestObject > ] } + +pub fn test_objects_gen_with_unicode() -> Vec< TestObject > +{ + vec! + [ + TestObject + { + id : "Доміно".to_string(), + created_at : 100, + file_ids : vec![ "файл1".to_string(), "файл2".to_string() ], + tools : None, + }, + TestObject + { + id : "Інший юнікод".to_string(), + created_at : 120, + file_ids : vec![], + tools : Some + ( + vec! + [ + { + let mut map = HashMap::new(); + map.insert( "тулз1".to_string(), "значення1".to_string() ); + map + }, + { + let mut map = HashMap::new(); + map.insert( "тулз2".to_string(), "значення2".to_string() ); + map + } + ] + ), + } + ] +} + +pub fn test_objects_gen_2_languages() -> Vec< TestObject > +{ + vec! + [ + TestObject + { + id : "Доміно".to_string(), + created_at : 100, + file_ids : vec![ "файл1".to_string(), "файл2".to_string() ], + tools : Some + ( + vec! + [ + { + let mut map = HashMap::new(); + map.insert( "тулз1".to_string(), "значення1".to_string() ); + map + }, + { + let mut map = HashMap::new(); + map.insert( "тулз2".to_string(), "значення2".to_string() ); + map + } + ] + ), + }, + TestObject + { + id : "File".to_string(), + created_at : 120, + file_ids : vec![ "file1".to_string(), "file2".to_string() ], + tools : Some + ( + vec! + [ + { + let mut map = HashMap::new(); + map.insert( "tools1".to_string(), "value1".to_string() ); + map + }, + { + let mut map = HashMap::new(); + map.insert( "tools1".to_string(), "value2".to_string() ); + map + } + ] + ), + } + ] +} \ No newline at end of file diff --git a/module/core/format_tools/tests/inc/to_string_example.rs b/module/core/format_tools/tests/inc/to_string_example.rs new file mode 100644 index 0000000000..2bc356a052 --- /dev/null +++ b/module/core/format_tools/tests/inc/to_string_example.rs @@ -0,0 +1,56 @@ +#[ allow( unused_imports ) ] +use super::*; + +// xxx : qqq : make example from this test and add also into readme + +#[ test ] +fn exmaple() +{ + + use core::fmt; + use format_tools:: + { + WithDebug, + WithDisplay, + to_string_with_fallback, + }; + + struct Both; + + impl fmt::Debug for Both + { + fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result + { + write!( f, "This is debug" ) + } + } + + impl fmt::Display for Both + { + fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result + { + write!( f, "This is display" ) + } + } + + struct OnlyDebug; + + impl fmt::Debug for OnlyDebug + { + fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result + { + write!( f, "This is debug" ) + } + } + + let src = Both; + let got = to_string_with_fallback!( WithDisplay, WithDebug, src ); + let exp = "This is display".to_string(); + assert_eq!( got, exp ); + + let src = OnlyDebug; + let got = to_string_with_fallback!( WithDisplay, WithDebug, src ); + let exp = "This is debug".to_string(); + assert_eq!( got, exp ); + +} diff --git a/module/core/format_tools/tests/inc/to_string_with_fallback_test.rs b/module/core/format_tools/tests/inc/to_string_with_fallback_test.rs index bd9947cd71..e0c39527c3 100644 --- a/module/core/format_tools/tests/inc/to_string_with_fallback_test.rs +++ b/module/core/format_tools/tests/inc/to_string_with_fallback_test.rs @@ -9,12 +9,12 @@ use the_module:: WithDebug, WithDisplay, // the_module::to_string_with_fallback::Ref, - to_string_with_fallback, + to_string_with_fallback }; use std:: { - // fmt, + fmt, // collections::HashMap, borrow::Cow, }; diff --git a/module/core/format_tools/tests/smoke_test.rs b/module/core/format_tools/tests/smoke_test.rs index 828e9b016b..cd7b1f36a8 100644 --- a/module/core/format_tools/tests/smoke_test.rs +++ b/module/core/format_tools/tests/smoke_test.rs @@ -1,12 +1,13 @@ +//! Smoke tests. - +/// Smoke test of local version of the crate. #[ test ] fn local_smoke_test() { ::test_tools::smoke_test_for_local_run(); } - +/// Smoke test of published version of the crate. #[ test ] fn published_smoke_test() { diff --git a/module/core/format_tools/tests/tests.rs b/module/core/format_tools/tests/tests.rs index eae1668ea0..c8e636300b 100644 --- a/module/core/format_tools/tests/tests.rs +++ b/module/core/format_tools/tests/tests.rs @@ -1,10 +1,10 @@ +//! Primary tests. + // #![ feature( trace_macros ) ] +#![ allow( unused_imports ) ] -#[ allow( unused_imports ) ] use format_tools as the_module; -#[ allow( unused_imports ) ] use test_tools::exposed::*; #[ cfg( feature = "enabled" ) ] mod inc; - diff --git a/module/core/former/Cargo.toml b/module/core/former/Cargo.toml index 255f72c486..61b616c264 100644 --- a/module/core/former/Cargo.toml +++ b/module/core/former/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "former" -version = "2.8.0" +version = "2.17.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" ] @@ -63,7 +63,6 @@ former_meta = { workspace = true } former_types = { workspace = true } # collection_tools = { workspace = true, features = [ "collection_constructors" ] } - [dev-dependencies] -test_tools = { workspace = true, features = [ "full" ] } +test_tools = { workspace = true } collection_tools = { workspace = true, features = [ "collection_constructors" ] } diff --git a/module/core/former/License b/module/core/former/License index e3e9e057cf..a23529f45b 100644 --- a/module/core/former/License +++ b/module/core/former/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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 d36ce4b061..fcb75fa363 100644 --- a/module/core/former/Readme.md +++ b/module/core/former/Readme.md @@ -2,1376 +2,220 @@ # Module :: former - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_former_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_former_push.yml) [![docs.rs](https://img.shields.io/docsrs/former?color=e3e8f0&logo=docs.rs)](https://docs.rs/former) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fformer%2Fexamples%2Fformer_trivial.rs,RUN_POSTFIX=--example%20former_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_former_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_former_push.yml) [![docs.rs](https://img.shields.io/docsrs/former?color=e3e8f0&logo=docs.rs)](https://docs.rs/former) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fformer%2Fexamples%2Fformer_trivial.rs,RUN_POSTFIX=--example%20module%2Fcore%2Fformer%2Fexamples%2Fformer_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) A flexible 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 : i32, + timeout : Option< i32 >, // 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/former_custom_collection.rs b/module/core/former/examples/former_custom_collection.rs index a3b1e1b667..1f275425d3 100644 --- a/module/core/former/examples/former_custom_collection.rs +++ b/module/core/former/examples/former_custom_collection.rs @@ -44,7 +44,7 @@ fn main() K : core::cmp::Eq + std::hash::Hash, { type Item = K; - type IntoIter = collection_tools::hset::IntoIter< K >; + type IntoIter = collection_tools::hash_set::IntoIter< K >; fn into_iter( self ) -> Self::IntoIter { @@ -58,7 +58,7 @@ fn main() K : core::cmp::Eq + std::hash::Hash, { type Item = &'a K; - type IntoIter = collection_tools::hset::Iter< 'a, K >; + type IntoIter = collection_tools::hash_set::Iter< 'a, K >; fn into_iter( self ) -> Self::IntoIter { 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/plan.md b/module/core/former/plan.md new file mode 100644 index 0000000000..85583947f5 --- /dev/null +++ b/module/core/former/plan.md @@ -0,0 +1,96 @@ +# Former Standalone Constructors Feature Plan + +This plan outlines the steps to implement and verify the `#[standalone_constructors]` and `#[arg_for_constructor]` features for the `former` crate, adopting **Option 2** logic where `#[arg_for_constructor]` solely determines constructor arguments and return type. + +## Progress Summary + +* [✅] **Increment 1:** Verify Zero-Argument Standalone Constructors (Existing Files - Modified) +* [✅] **Increment 2:** Create New Test Files for Argument Constructors (Enums & Structs) +* [⬜] **Increment 3 (Rework):** Modify Derive Macro for Option 2 Logic (Enums) +* [⬜] **Increment 4 (Rework):** Update Manual Implementation for Option 2 (Enums) +* [⬜] **Increment 5 (Rework):** Update Tests for Option 2 (Enums) +* [⬜] **Increment 6 (Rework):** Verify Enum Tests (Option 2) +* [⬜] **Increment 7 (Rework):** Implement Manual Argument Constructor Tests (Structs - Option 2) +* [⬜] **Increment 8 (Rework):** Implement Derive Argument Constructor Tests (Structs - Option 2) +* [⬜] **Increment 9 (Rework):** Update Documentation + +## Detailed Plan + +1. **Increment 1: Verify Zero-Argument Standalone Constructors (Existing Files - Modified)** + * **Status:** ✅ Done + * **Goal:** Ensure the basic `#[standalone_constructors]` feature (without `#[arg_for_constructor]`) works correctly for both structs and enums using the *existing* test files, with argument-related tests commented out. + * **Files & Actions:** + * `standalone_constructor_manual.rs` (structs & enums): Ensured constructors take **zero** arguments. + * `standalone_constructor_derive.rs` (structs & enums): Ensured `#[standalone_constructors]` is present, but `#[arg_for_constructor]` is **commented out**. + * `standalone_constructor_only_test.rs` (structs & enums): Ensured **only** the zero-argument tests (`no_args_test`, `unit_variant_test`, `tuple_variant_test`, `struct_variant_test`) are **uncommented**. Commented out the `*_with_args_test` functions. + * **Verification:** Ran `cargo test`. All uncommented tests passed for both manual and derive targets. + +2. **Increment 2: Create New Test Files for Argument Constructors** + * **Status:** ✅ Done + * **Goal:** Set up the file structure for testing the `#[arg_for_constructor]` feature separately. + * **Action:** + * Created `module/core/former/tests/inc/former_struct_tests/standalone_constructor_args_manual.rs`. + * Created `module/core/former/tests/inc/former_struct_tests/standalone_constructor_args_derive.rs`. + * Created `module/core/former/tests/inc/former_struct_tests/standalone_constructor_args_only_test.rs`. + * Created `module/core/former/tests/inc/former_enum_tests/standalone_constructor_args_manual.rs`. + * Created `module/core/former/tests/inc/former_enum_tests/standalone_constructor_args_derive.rs`. + * Created `module/core/former/tests/inc/former_enum_tests/standalone_constructor_args_only_test.rs`. + * Added `mod standalone_constructor_args_manual;` and `mod standalone_constructor_args_derive;` to `module/core/former/tests/inc/former_struct_tests/mod.rs`. + * Added `mod standalone_constructor_args_manual;` and `mod standalone_constructor_args_derive;` to `module/core/former/tests/inc/former_enum_tests/mod.rs`. + +3. **Increment 3 (Rework): Modify Derive Macro for Option 2 Logic (Enums)** + * **Status:** ⬜ Not Started + * **Goal:** Update `former_enum.rs` to generate standalone constructors according to Option 2 rules (checking if all fields have `#[arg_for_constructor]` to determine return type and body). Remove dependency on `#[scalar]` for standalone constructor generation. + * **File:** `module/core/former_meta/src/derive_former/former_enum.rs` + +4. **Increment 4 (Rework): Update Manual Implementation for Option 2 (Enums)** + * **Status:** ⬜ Not Started + * **Goal:** Align the manual enum implementation (`standalone_constructor_args_manual.rs`) with Option 2 logic. Constructors for variants where all fields are args should return `Self`. Others return the Former. + * **File:** `module/core/former/tests/inc/former_enum_tests/standalone_constructor_args_manual.rs` + +5. **Increment 5 (Rework): Update Tests for Option 2 (Enums)** + * **Status:** ⬜ Not Started + * **Goal:** Adjust tests in `standalone_constructor_args_only_test.rs` to match Option 2 expectations (check return type based on whether all fields are args). + * **File:** `module/core/former/tests/inc/former_enum_tests/standalone_constructor_args_only_test.rs` + +6. **Increment 6 (Rework): Verify Enum Tests (Option 2)** + * **Status:** ⬜ Not Started + * **Goal:** Run tests and ensure they pass for both manual and derive implementations according to Option 2 logic. + * **Action:** `cargo test`. + +7. **Increment 7 (Rework): Implement Manual Argument Constructor Tests (Structs - Option 2)** + * **Status:** ⬜ Not Started + * **Goal:** Implement manual struct tests reflecting Option 2 (constructor returns `Self` if all fields have `#[arg_for_constructor]`, otherwise returns `Former`). + * **Files:** `standalone_constructor_args_manual.rs` (struct), `standalone_constructor_args_only_test.rs` (struct). + +8. **Increment 8 (Rework): Implement Derive Argument Constructor Tests (Structs - Option 2)** + * **Status:** ⬜ Not Started + * **Goal:** Implement derive struct tests reflecting Option 2. Ensure derive logic in `former_struct.rs` is updated if necessary. + * **Files:** `standalone_constructor_args_derive.rs` (struct), `standalone_constructor_args_only_test.rs` (struct), `module/core/former_meta/src/derive_former/former_struct.rs`. + +9. **Increment 9 (Rework): Update Documentation** + * **Status:** ⬜ Not Started + * **Goal:** Document Option 2 behavior for the attributes. + * **Files:** + * `module/core/former/Readme.md` + * `module/core/former/advanced.md` + * `module/core/former_meta/src/lib.rs` + +## Notes / Struggling Points / Insights + +* **Initial Struggle (Enum Tests):** Encountered significant difficulty verifying the `#[arg_for_constructor]` implementation for enums using the initial test setup (`standalone_constructor_manual.rs`, `_derive.rs`, `_only_test.rs`). The shared test file (`_only_test.rs`) contained tests for both zero-argument and argument-taking constructors. +* **Conflict:** The manual implementation (`_manual.rs`) could only define standalone constructors with a single signature (either zero-args or arg-taking). This created a conflict: + * If manual constructors took zero args, the argument-taking tests failed compilation against the manual target. + * If manual constructors took arguments, the zero-argument tests failed compilation against the manual target. +* **Resolution/Insight:** The correct approach was to **separate the test cases**. + * The original files (`standalone_constructor_*`) were adjusted to *only* test the zero-argument constructor scenario (where `#[arg_for_constructor]` is absent or commented out). + * New files (`standalone_constructor_args_*`) were created specifically to test the argument-taking constructor scenario (where `#[arg_for_constructor]` is active). This resolved the conflict and allowed independent verification of both scenarios for manual and derive implementations. +* **Derive vs Manual Behavior:** Realized that standalone constructors for non-unit enum variants (even scalar ones) should return a `Former` type, not `Self` directly. The tests were adjusted accordingly. (Note: This insight is now being revised based on the switch to Option 2). +* **`#[scalar]` vs `#[arg_for_constructor]`:** Clarified that `#[scalar]` on an enum variant implies a direct *associated method* returning `Self`, but the *standalone constructor* still returns a Former. `#[arg_for_constructor]` controls the arguments for the standalone constructor (and potentially initial storage state). Using `#[arg_for_constructor]` within a `#[scalar]` variant is disallowed by the derive macro. (Note: This insight is now being revised based on the switch to Option 2). +* **Decision Change:** Switched from implementing Option 1 (where `#[scalar]` dictated direct return) to **Option 2** (where `#[arg_for_constructor]` on *all* fields dictates direct return). This requires reworking the derive logic and tests for argument handling. + +## General Notes + +* This plan adopts **Option 2**: `#[arg_for_constructor]` on fields solely determines the standalone constructor's arguments. Standalone constructor returns `Self` if *all* fields have `#[arg_for_constructor]`, otherwise returns the implicit `VariantFormer`. `#[scalar]` is irrelevant for standalone constructors. +* Each increment involving code changes should be followed by running `cargo test` to ensure stability and verify the specific goal of the increment. +* Warnings should be addressed as they appear. diff --git a/module/core/former/src/lib.rs b/module/core/former/src/lib.rs index 635a8e85e0..e6424721ad 100644 --- a/module/core/former/src/lib.rs +++ b/module/core/former/src/lib.rs @@ -4,6 +4,14 @@ #![ doc( html_root_url = "https://docs.rs/former/latest/former/" ) ] #![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] +// xxx : introduce body( struct/enum ) attribute `standalone_constructors` which create stand-alone, top-level constructors for struct/enum. for struct it's always single function, for enum it's as many functions as enum has vartianys. if there is no `arg_for_constructor` then constructors expect exaclty zero arguments. start from implementations without respect of attribute attribute `arg_for_constructor`. by default `standalone_constructors` is false +// xxx : introduce field attribute to mark an attribute `arg_for_constructor` as an argument which should be used in constructing functions ( either standalone consturcting function or associated with struct ). in case of enums attribute `arg_for_constructor` is attachable only to fields of variant and attempt to attach attribute `arg_for_constructor` to variant must throw understandable error. name standalone constructor of struct the same way struct named, but snake case and for enums the same name variant is named, but snake case. by default it's false. + +// 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 @@ -22,6 +30,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use orphan::*; @@ -35,6 +44,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; @@ -45,6 +55,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] diff --git a/module/core/former/tests/experimental.rs b/module/core/former/tests/experimental.rs index fe640ab353..9713734b7b 100644 --- a/module/core/former/tests/experimental.rs +++ b/module/core/former/tests/experimental.rs @@ -1,9 +1,8 @@ +//! For experimenting. +#![ allow( unused_imports ) ] include!( "../../../../module/step/meta/src/module/terminal.rs" ); -#[ allow( unused_imports ) ] -use test_tools::exposed::*; -#[ allow( unused_imports ) ] use former as the_module; // #[ path = "./inc/components_composite.rs" ] 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..6d1d153a51 --- /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..d33f0e1d33 --- /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..b851446c80 --- /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..94085ad0ca --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/generics_independent_tuple_derive.rs @@ -0,0 +1,47 @@ +// //! 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" ); +// qqq : uncomment and fix issues \ 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..573aa63871 --- /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..4265931ab1 --- /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..05740242da --- /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..4d6ca69110 --- /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..6d3593c419 --- /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/standalone_constructor_args_derive.rs b/module/core/former/tests/inc/former_enum_tests/standalone_constructor_args_derive.rs new file mode 100644 index 0000000000..c58c13942f --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/standalone_constructor_args_derive.rs @@ -0,0 +1,56 @@ +// // module/core/former/tests/inc/former_enum_tests/standalone_constructor_args_derive.rs +// //! +// //! Derive-based tests for standalone constructors for enums with arguments. +// //! Uses distinct names matching the manual version for testing. +// //! +// +// #[ allow( unused_imports ) ] +// use ::former::prelude::*; +// use ::former::Former; // Import derive macro +// +// // === Enum Definition === +// +// /// Enum using derive for standalone constructors with arguments. +// #[ derive( Debug, PartialEq, Clone, Former ) ] +// #[ standalone_constructors ] // Enable standalone constructors +// pub enum TestEnumArgs // Use the distinct name +// { +// /// A unit variant. +// UnitVariantArgs, // Use the distinct name +// /// A tuple variant with one field marked as constructor arg. +// TupleVariantArgs // Use the distinct name +// ( +// #[ arg_for_constructor ] // Mark field as constructor arg +// i32 +// ), +// /// A struct variant with one field marked as constructor arg. +// StructVariantArgs // Use the distinct name +// { +// #[ arg_for_constructor ] // Mark field as constructor arg +// field : String, +// }, +// /// A tuple variant with multiple fields marked as constructor args. +// #[ scalar ] // <<< Keep scalar attribute +// MultiTupleArgs // Use the distinct name +// ( +// // #[ arg_for_constructor ] // <<< REMOVED +// i32, +// // #[ arg_for_constructor ] // <<< REMOVED +// bool, +// ), +// /// A struct variant with multiple fields marked as constructor args. +// // #[ scalar ] // <<< Keep scalar attribute +// MultiStructArgs // Use the distinct name +// { +// #[ arg_for_constructor ] +// a : i32, +// #[ arg_for_constructor ] +// b : bool, +// }, +// } +// +// // === Include Test Logic === +// include!( "standalone_constructor_args_only_test.rs" ); // Include the specific test file +// +// qqq : finish it please +// \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/standalone_constructor_args_manual.rs b/module/core/former/tests/inc/former_enum_tests/standalone_constructor_args_manual.rs new file mode 100644 index 0000000000..aef29713ce --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/standalone_constructor_args_manual.rs @@ -0,0 +1,696 @@ +// module/core/former/tests/inc/former_enum_tests/standalone_constructor_args_manual.rs +//! +//! Manual implementation for testing standalone constructors for enums with arguments. +//! Uses distinct names to avoid conflicts with zero-arg tests. +//! + +#[ allow( unused_imports ) ] +use ::former::prelude::*; +#[ allow( unused_imports ) ] +use ::former_types:: +{ + Storage, StoragePreform, + FormerDefinitionTypes, FormerMutator, FormerDefinition, + FormingEnd, ReturnPreformed, +}; + +// === Enum Definition === + +/// Enum for manual testing of standalone constructors with arguments. +#[ derive( Debug, PartialEq, Clone ) ] +pub enum TestEnumArgs // New name +{ + /// A unit variant. + UnitVariantArgs, // New name + /// A tuple variant with one field (intended as constructor arg). + TupleVariantArgs( i32 ), // New name + /// A struct variant with one field (intended as constructor arg). + StructVariantArgs // New name + { + field : String, + }, + /// A tuple variant with multiple fields (intended as constructor args). + MultiTupleArgs( i32, bool ), // <<< New Variant + /// A struct variant with multiple fields (intended as constructor args). + MultiStructArgs // <<< New Variant + { + a : i32, + b : bool, + }, +} + +// === Manual Former Implementation for TupleVariantArgs === + +// Storage +/// Storage for TestEnumArgsTupleVariantArgsFormer. +#[ derive( Debug, Default ) ] +pub struct TestEnumArgsTupleVariantArgsFormerStorage +{ + /// Option to store the value for the tuple field. + pub _0 : ::core::option::Option< i32 >, +} + +impl Storage for TestEnumArgsTupleVariantArgsFormerStorage +{ + type Preformed = i32; +} + +impl StoragePreform for TestEnumArgsTupleVariantArgsFormerStorage +{ + #[ inline( always ) ] + fn preform( mut self ) -> Self::Preformed + { + // Should ideally panic if None and not defaulted by constructor arg, + // but for manual test, assume it's set. + self._0.take().unwrap_or_default() + } +} + +// Definition Types +/// Definition types for TestEnumArgsTupleVariantArgsFormer. +#[ derive( Debug, Default ) ] +pub struct TestEnumArgsTupleVariantArgsFormerDefinitionTypes< Context = (), Formed = TestEnumArgs > +{ + _phantom : core::marker::PhantomData< ( Context, Formed ) >, +} + +impl< Context, Formed > FormerDefinitionTypes +for TestEnumArgsTupleVariantArgsFormerDefinitionTypes< Context, Formed > +{ + type Storage = TestEnumArgsTupleVariantArgsFormerStorage; + type Formed = Formed; + type Context = Context; +} + +// Mutator +impl< Context, Formed > FormerMutator +for TestEnumArgsTupleVariantArgsFormerDefinitionTypes< Context, Formed > +{ +} + +// Definition +/// Definition for TestEnumArgsTupleVariantArgsFormer. +#[ derive( Debug, Default ) ] +pub struct TestEnumArgsTupleVariantArgsFormerDefinition +< Context = (), Formed = TestEnumArgs, End = TestEnumArgsTupleVariantArgsEnd > +{ + _phantom : core::marker::PhantomData< ( Context, Formed, End ) >, +} + +impl< Context, Formed, End > FormerDefinition +for TestEnumArgsTupleVariantArgsFormerDefinition< Context, Formed, End > +where + End : FormingEnd< TestEnumArgsTupleVariantArgsFormerDefinitionTypes< Context, Formed > >, +{ + type Storage = TestEnumArgsTupleVariantArgsFormerStorage; + type Formed = Formed; + type Context = Context; + type Types = TestEnumArgsTupleVariantArgsFormerDefinitionTypes< Context, Formed >; + type End = End; +} + +// Former +/// Manual Former implementation for TestEnumArgs::TupleVariantArgs. +#[ derive( Debug ) ] +pub struct TestEnumArgsTupleVariantArgsFormer +< Definition = TestEnumArgsTupleVariantArgsFormerDefinition > +where + Definition : FormerDefinition< Storage = TestEnumArgsTupleVariantArgsFormerStorage >, +{ + storage : Definition::Storage, + context : Option< Definition::Context >, + on_end : Option< Definition::End >, +} + +impl< Definition > TestEnumArgsTupleVariantArgsFormer< Definition > +where + Definition : FormerDefinition< Storage = TestEnumArgsTupleVariantArgsFormerStorage >, + Definition::Types : FormerDefinitionTypes< Storage = TestEnumArgsTupleVariantArgsFormerStorage >, + Definition::Types : FormerMutator, +{ + #[ 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 ) } + } + + #[ inline( always ) ] + #[allow(dead_code)] + pub fn new( on_end : Definition::End ) -> Self + { + Self::begin( None, None, on_end ) + } + + /// Setter for the tuple field. + #[ inline ] + pub fn _0( mut self, src : impl Into< i32 > ) -> Self + { + // debug_assert!( self.storage._0.is_none(), "Field '_0' was already set" ); + self.storage._0 = Some( src.into() ); + self + } +} + +// End Struct for TupleVariantArgs +/// End handler for TestEnumArgsTupleVariantArgsFormer. +#[ derive( Debug, Default ) ] +pub struct TestEnumArgsTupleVariantArgsEnd; + +impl FormingEnd< TestEnumArgsTupleVariantArgsFormerDefinitionTypes< (), TestEnumArgs > > +for TestEnumArgsTupleVariantArgsEnd +{ + #[ inline( always ) ] + fn call + ( + &self, + storage : TestEnumArgsTupleVariantArgsFormerStorage, + _context : Option< () >, + ) -> TestEnumArgs + { + let val = storage.preform(); + TestEnumArgs::TupleVariantArgs( val ) + } +} + +// === Manual Former Implementation for StructVariantArgs === + +// Storage +/// Storage for TestEnumArgsStructVariantArgsFormer. +#[ derive( Debug, Default ) ] +pub struct TestEnumArgsStructVariantArgsFormerStorage +{ + /// Option to store the value for the struct field. + pub field : ::core::option::Option< String >, +} + +impl Storage for TestEnumArgsStructVariantArgsFormerStorage +{ + type Preformed = String; +} + +impl StoragePreform for TestEnumArgsStructVariantArgsFormerStorage +{ + #[ inline( always ) ] + fn preform( mut self ) -> Self::Preformed + { + // Should ideally panic if None and not defaulted by constructor arg, + // but for manual test, assume it's set. + self.field.take().unwrap_or_default() + } +} + +// Definition Types +/// Definition types for TestEnumArgsStructVariantArgsFormer. +#[ derive( Debug, Default ) ] +pub struct TestEnumArgsStructVariantArgsFormerDefinitionTypes< Context = (), Formed = TestEnumArgs > +{ + _phantom : core::marker::PhantomData< ( Context, Formed ) >, +} + +impl< Context, Formed > FormerDefinitionTypes +for TestEnumArgsStructVariantArgsFormerDefinitionTypes< Context, Formed > +{ + type Storage = TestEnumArgsStructVariantArgsFormerStorage; + type Formed = Formed; + type Context = Context; +} + +// Mutator +impl< Context, Formed > FormerMutator +for TestEnumArgsStructVariantArgsFormerDefinitionTypes< Context, Formed > +{ +} + +// Definition +/// Definition for TestEnumArgsStructVariantArgsFormer. +#[ derive( Debug, Default ) ] +pub struct TestEnumArgsStructVariantArgsFormerDefinition +< Context = (), Formed = TestEnumArgs, End = TestEnumArgsStructVariantArgsEnd > +{ + _phantom : core::marker::PhantomData< ( Context, Formed, End ) >, +} + +impl< Context, Formed, End > FormerDefinition +for TestEnumArgsStructVariantArgsFormerDefinition< Context, Formed, End > +where + End : FormingEnd< TestEnumArgsStructVariantArgsFormerDefinitionTypes< Context, Formed > >, +{ + type Storage = TestEnumArgsStructVariantArgsFormerStorage; + type Formed = Formed; + type Context = Context; + type Types = TestEnumArgsStructVariantArgsFormerDefinitionTypes< Context, Formed >; + type End = End; +} + +// Former +/// Manual Former implementation for TestEnumArgs::StructVariantArgs. +#[ derive( Debug ) ] +pub struct TestEnumArgsStructVariantArgsFormer +< Definition = TestEnumArgsStructVariantArgsFormerDefinition > +where + Definition : FormerDefinition< Storage = TestEnumArgsStructVariantArgsFormerStorage >, +{ + storage : Definition::Storage, + context : Option< Definition::Context >, + on_end : Option< Definition::End >, +} + +impl< Definition > TestEnumArgsStructVariantArgsFormer< Definition > +where + Definition : FormerDefinition< Storage = TestEnumArgsStructVariantArgsFormerStorage >, + Definition::Types : FormerDefinitionTypes< Storage = TestEnumArgsStructVariantArgsFormerStorage >, + Definition::Types : FormerMutator, +{ + #[ 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 ) } + } + + #[ inline( always ) ] + #[allow(dead_code)] + pub fn new( on_end : Definition::End ) -> Self + { + Self::begin( None, None, on_end ) + } + + /// Setter for the struct field. + #[ inline ] + #[allow(dead_code)] + pub fn field( mut self, src : impl Into< String > ) -> Self + { + // debug_assert!( self.storage.field.is_none(), "Field 'field' was already set" ); + self.storage.field = Some( src.into() ); + self + } +} + +// End Struct for StructVariantArgs +/// End handler for TestEnumArgsStructVariantArgsFormer. +#[ derive( Debug, Default ) ] +pub struct TestEnumArgsStructVariantArgsEnd; + +impl FormingEnd< TestEnumArgsStructVariantArgsFormerDefinitionTypes< (), TestEnumArgs > > +for TestEnumArgsStructVariantArgsEnd +{ + #[ inline( always ) ] + fn call + ( + &self, + storage : TestEnumArgsStructVariantArgsFormerStorage, + _context : Option< () >, + ) -> TestEnumArgs + { + let val = storage.preform(); + TestEnumArgs::StructVariantArgs { field : val } + } +} + + +// === Manual Former Implementation for MultiTupleArgs === <<< NEW >>> + +// Storage +#[ derive( Debug, Default ) ] +pub struct TestEnumArgsMultiTupleArgsFormerStorage +{ + pub _0 : ::core::option::Option< i32 >, + pub _1 : ::core::option::Option< bool >, +} +impl Storage for TestEnumArgsMultiTupleArgsFormerStorage +{ + type Preformed = ( i32, bool ); +} +impl StoragePreform for TestEnumArgsMultiTupleArgsFormerStorage +{ + #[ inline( always ) ] + fn preform( mut self ) -> Self::Preformed + { + ( self._0.take().unwrap_or_default(), self._1.take().unwrap_or_default() ) + } +} +// Definition Types +#[ derive( Debug, Default ) ] +pub struct TestEnumArgsMultiTupleArgsFormerDefinitionTypes +< Context = (), Formed = TestEnumArgs > +{ + _phantom : core::marker::PhantomData< ( Context, Formed ) >, +} +impl< Context, Formed > FormerDefinitionTypes +for TestEnumArgsMultiTupleArgsFormerDefinitionTypes< Context, Formed > +{ + type Storage = TestEnumArgsMultiTupleArgsFormerStorage; + type Formed = Formed; + type Context = Context; +} +impl< Context, Formed > FormerMutator +for TestEnumArgsMultiTupleArgsFormerDefinitionTypes< Context, Formed > +{ +} +// Definition +#[ derive( Debug, Default ) ] +pub struct TestEnumArgsMultiTupleArgsFormerDefinition +< Context = (), Formed = TestEnumArgs, End = TestEnumArgsMultiTupleArgsEnd > +{ + _phantom : core::marker::PhantomData< ( Context, Formed, End ) >, +} +impl< Context, Formed, End > FormerDefinition +for TestEnumArgsMultiTupleArgsFormerDefinition< Context, Formed, End > +where + End : FormingEnd< TestEnumArgsMultiTupleArgsFormerDefinitionTypes< Context, Formed > >, +{ + type Storage = TestEnumArgsMultiTupleArgsFormerStorage; + type Formed = Formed; + type Context = Context; + type Types = TestEnumArgsMultiTupleArgsFormerDefinitionTypes< Context, Formed >; + type End = End; +} +// Former +#[ derive( Debug ) ] +pub struct TestEnumArgsMultiTupleArgsFormer +< Definition = TestEnumArgsMultiTupleArgsFormerDefinition > +where + Definition : FormerDefinition< Storage = TestEnumArgsMultiTupleArgsFormerStorage >, +{ + storage : Definition::Storage, + context : Option< Definition::Context >, + on_end : Option< Definition::End >, +} +impl< Definition > TestEnumArgsMultiTupleArgsFormer< Definition > +where + Definition : FormerDefinition< Storage = TestEnumArgsMultiTupleArgsFormerStorage >, + Definition::Types : FormerDefinitionTypes< Storage = TestEnumArgsMultiTupleArgsFormerStorage >, + Definition::Types : FormerMutator, +{ + #[ 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 ) } + } + #[ inline( always ) ] + #[allow(dead_code)] + pub fn new( on_end : Definition::End ) -> Self + { + Self::begin( None, None, on_end ) + } + #[ inline ] + pub fn _0( mut self, src : impl Into< i32 > ) -> Self + { + self.storage._0 = Some( src.into() ); + self + } + #[ inline ] + pub fn _1( mut self, src : impl Into< bool > ) -> Self + { + self.storage._1 = Some( src.into() ); + self + } +} +// End Struct +#[ derive( Debug, Default ) ] +pub struct TestEnumArgsMultiTupleArgsEnd; +impl FormingEnd< TestEnumArgsMultiTupleArgsFormerDefinitionTypes< (), TestEnumArgs > > +for TestEnumArgsMultiTupleArgsEnd +{ + #[ inline( always ) ] + fn call + ( + &self, + storage : TestEnumArgsMultiTupleArgsFormerStorage, + _context : Option< () >, + ) -> TestEnumArgs + { + let ( val0, val1 ) = storage.preform(); + TestEnumArgs::MultiTupleArgs( val0, val1 ) + } +} + + +// === Manual Former Implementation for MultiStructArgs === <<< NEW >>> + +// Storage +#[ derive( Debug, Default ) ] +pub struct TestEnumArgsMultiStructArgsFormerStorage +{ + pub a : ::core::option::Option< i32 >, + pub b : ::core::option::Option< bool >, +} +impl Storage for TestEnumArgsMultiStructArgsFormerStorage +{ + type Preformed = ( i32, bool ); +} +impl StoragePreform for TestEnumArgsMultiStructArgsFormerStorage +{ + #[ inline( always ) ] + fn preform( mut self ) -> Self::Preformed + { + ( self.a.take().unwrap_or_default(), self.b.take().unwrap_or_default() ) + } +} +// Definition Types +#[ derive( Debug, Default ) ] +pub struct TestEnumArgsMultiStructArgsFormerDefinitionTypes +< Context = (), Formed = TestEnumArgs > +{ + _phantom : core::marker::PhantomData< ( Context, Formed ) >, +} +impl< Context, Formed > FormerDefinitionTypes +for TestEnumArgsMultiStructArgsFormerDefinitionTypes< Context, Formed > +{ + type Storage = TestEnumArgsMultiStructArgsFormerStorage; + type Formed = Formed; + type Context = Context; +} +impl< Context, Formed > FormerMutator +for TestEnumArgsMultiStructArgsFormerDefinitionTypes< Context, Formed > +{ +} +// Definition +#[ derive( Debug, Default ) ] +pub struct TestEnumArgsMultiStructArgsFormerDefinition +< Context = (), Formed = TestEnumArgs, End = TestEnumArgsMultiStructArgsEnd > +{ + _phantom : core::marker::PhantomData< ( Context, Formed, End ) >, +} +impl< Context, Formed, End > FormerDefinition +for TestEnumArgsMultiStructArgsFormerDefinition< Context, Formed, End > +where + End : FormingEnd< TestEnumArgsMultiStructArgsFormerDefinitionTypes< Context, Formed > >, +{ + type Storage = TestEnumArgsMultiStructArgsFormerStorage; + type Formed = Formed; + type Context = Context; + type Types = TestEnumArgsMultiStructArgsFormerDefinitionTypes< Context, Formed >; + type End = End; +} +// Former +#[ derive( Debug ) ] +pub struct TestEnumArgsMultiStructArgsFormer +< Definition = TestEnumArgsMultiStructArgsFormerDefinition > +where + Definition : FormerDefinition< Storage = TestEnumArgsMultiStructArgsFormerStorage >, +{ + storage : Definition::Storage, + context : Option< Definition::Context >, + on_end : Option< Definition::End >, +} +impl< Definition > TestEnumArgsMultiStructArgsFormer< Definition > +where + Definition : FormerDefinition< Storage = TestEnumArgsMultiStructArgsFormerStorage >, + Definition::Types : FormerDefinitionTypes< Storage = TestEnumArgsMultiStructArgsFormerStorage >, + Definition::Types : FormerMutator, +{ + #[ 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 ) } + } + #[ inline( always ) ] + #[allow(dead_code)] + pub fn new( on_end : Definition::End ) -> Self + { + Self::begin( None, None, on_end ) + } + #[ inline ] + #[allow(dead_code)] + pub fn a( mut self, src : impl Into< i32 > ) -> Self + { + self.storage.a = Some( src.into() ); + self + } + #[ inline ] + #[allow(dead_code)] + pub fn b( mut self, src : impl Into< bool > ) -> Self + { + self.storage.b = Some( src.into() ); + self + } +} +// End Struct +#[ derive( Debug, Default ) ] +pub struct TestEnumArgsMultiStructArgsEnd; +impl FormingEnd< TestEnumArgsMultiStructArgsFormerDefinitionTypes< (), TestEnumArgs > > +for TestEnumArgsMultiStructArgsEnd +{ + #[ inline( always ) ] + fn call + ( + &self, + storage : TestEnumArgsMultiStructArgsFormerStorage, + _context : Option< () >, + ) -> TestEnumArgs + { + let ( val_a, val_b ) = storage.preform(); + TestEnumArgs::MultiStructArgs { a : val_a, b : val_b } + } +} + + +// === Standalone Constructors (Manual - Argument Taking) === + +/// Manual standalone constructor for TestEnumArgs::UnitVariantArgs. +pub fn unit_variant_args() -> TestEnumArgs +{ + TestEnumArgs::UnitVariantArgs +} + +/// Manual standalone constructor for TestEnumArgs::TupleVariantArgs (takes arg). +pub fn tuple_variant_args( _0 : impl Into< i32 > ) +-> +TestEnumArgsTupleVariantArgsFormer +< + TestEnumArgsTupleVariantArgsFormerDefinition< (), TestEnumArgs, TestEnumArgsTupleVariantArgsEnd > +> +{ + let initial_storage = TestEnumArgsTupleVariantArgsFormerStorage + { + _0 : Some( _0.into() ), + }; + TestEnumArgsTupleVariantArgsFormer::begin( Some( initial_storage ), None, TestEnumArgsTupleVariantArgsEnd ) +} + +/// Manual standalone constructor for TestEnumArgs::StructVariantArgs (takes arg). +pub fn struct_variant_args( field : impl Into< String > ) +-> +TestEnumArgsStructVariantArgsFormer +< + TestEnumArgsStructVariantArgsFormerDefinition< (), TestEnumArgs, TestEnumArgsStructVariantArgsEnd > +> +{ + let initial_storage = TestEnumArgsStructVariantArgsFormerStorage + { + field : Some( field.into() ), + }; + TestEnumArgsStructVariantArgsFormer::begin( Some( initial_storage ), None, TestEnumArgsStructVariantArgsEnd ) +} + +/// Manual standalone constructor for TestEnumArgs::MultiTupleArgs (takes args). <<< NEW >>> +pub fn multi_tuple_args( _0 : impl Into< i32 >, _1 : impl Into< bool > ) +-> +TestEnumArgsMultiTupleArgsFormer +< + TestEnumArgsMultiTupleArgsFormerDefinition< (), TestEnumArgs, TestEnumArgsMultiTupleArgsEnd > +> +{ + let initial_storage = TestEnumArgsMultiTupleArgsFormerStorage + { + _0 : Some( _0.into() ), + _1 : Some( _1.into() ), + }; + TestEnumArgsMultiTupleArgsFormer::begin( Some( initial_storage ), None, TestEnumArgsMultiTupleArgsEnd ) +} + +/// Manual standalone constructor for TestEnumArgs::MultiStructArgs (takes args). <<< NEW >>> +pub fn multi_struct_args( a : impl Into< i32 >, b : impl Into< bool > ) +-> +TestEnumArgsMultiStructArgsFormer +< + TestEnumArgsMultiStructArgsFormerDefinition< (), TestEnumArgs, TestEnumArgsMultiStructArgsEnd > +> +{ + let initial_storage = TestEnumArgsMultiStructArgsFormerStorage + { + a : Some( a.into() ), + b : Some( b.into() ), + }; + TestEnumArgsMultiStructArgsFormer::begin( Some( initial_storage ), None, TestEnumArgsMultiStructArgsEnd ) +} + + +// === Include Test Logic === +include!( "standalone_constructor_args_only_test.rs" ); \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/standalone_constructor_args_only_test.rs b/module/core/former/tests/inc/former_enum_tests/standalone_constructor_args_only_test.rs new file mode 100644 index 0000000000..841a53a606 --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/standalone_constructor_args_only_test.rs @@ -0,0 +1,62 @@ +// module/core/former/tests/inc/former_enum_tests/standalone_constructor_args_only_test.rs +// +// Contains the shared test logic for *argument-taking* standalone enum constructors. +// This file is included by both the manual and derive test files for the args case. +// + +// Use the items defined in the including file (manual or derive for args) +use super::*; + +/// Tests the standalone constructor for a unit variant (still takes no args). +#[ test ] +fn unit_variant_args_test() // New test name +{ + // Assumes `unit_variant_args` is defined in the including scope + let instance = unit_variant_args(); // Returns Enum directly + let expected = TestEnumArgs::UnitVariantArgs; + assert_eq!( instance, expected ); +} + +/// Tests the standalone constructor for a tuple variant that takes arguments. +#[ test ] +fn tuple_variant_args_test() // New test name +{ + // Assumes `tuple_variant_args` takes an i32 argument and returns a Former + let former = tuple_variant_args( 202 ); + let instance = former.form(); // Call form() + let expected = TestEnumArgs::TupleVariantArgs( 202 ); + assert_eq!( instance, expected ); +} + +/// Tests the standalone constructor for a struct variant that takes arguments. +#[ test ] +fn struct_variant_args_test() // New test name +{ + // Assumes `struct_variant_args` takes a String argument and returns a Former + let former = struct_variant_args( "arg_value" ); + let instance = former.form(); // Call form() + let expected = TestEnumArgs::StructVariantArgs { field : "arg_value".to_string() }; + assert_eq!( instance, expected ); +} + +/// Tests the standalone constructor for a multi-field tuple variant that takes arguments. +#[ test ] +fn multi_tuple_variant_args_test() +{ + // Assumes `multi_tuple_args` takes i32 and bool arguments and returns a Former + let former = multi_tuple_args( 99, true ); // <<< Get the former + let instance = former.form(); // <<< Call .form() + let expected = TestEnumArgs::MultiTupleArgs( 99, true ); + assert_eq!( instance, expected ); +} + +/// Tests the standalone constructor for a multi-field struct variant that takes arguments. +#[ test ] +fn multi_struct_variant_args_test() +{ + // Assumes `multi_struct_args` takes i32 and bool arguments and returns a Former + let former = multi_struct_args( -1, false ); + let instance = former.form(); // Call form() + let expected = TestEnumArgs::MultiStructArgs { a : -1, b : false }; + assert_eq!( instance, expected ); +} diff --git a/module/core/former/tests/inc/former_enum_tests/standalone_constructor_derive.rs b/module/core/former/tests/inc/former_enum_tests/standalone_constructor_derive.rs new file mode 100644 index 0000000000..a98d5d106e --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/standalone_constructor_derive.rs @@ -0,0 +1,56 @@ +// module/core/former/tests/inc/former_enum_tests/standalone_constructor_derive.rs +//! +//! Derive-based tests for standalone constructors for enums. +//! This file defines an enum mirroring the manual one but uses the derive macro +//! with the new attributes (`standalone_constructors`, `arg_for_constructor`). +//! It includes the shared test logic. +//! + +#[ allow( unused_imports ) ] +use ::former::prelude::*; +use ::former::Former; // Import derive macro + +// === Enum Definition === + +/// Enum using derive for standalone constructors. +// Attributes to be implemented by the derive macro +#[ derive( Debug, PartialEq, Clone, Former ) ] +#[ standalone_constructors ] // New attribute is active +pub enum TestEnum // Consistent name +{ + /// A unit variant. + UnitVariant, + /// A tuple variant with one field. + TupleVariant // Defaults to subformer behavior + ( + // #[ arg_for_constructor ] // <<< Keep commented out for this increment + i32 + ), + /// A struct variant with one field. + StructVariant // Defaults to subformer behavior + { + // #[ arg_for_constructor ] // <<< Keep commented out for this increment + field : String, + }, +} + +// === Standalone Constructor Calls (Expected Usage in Tests) === +// These functions are expected to be generated by the derive macro eventually. + +/* +// Expected generated constructor for TestEnum::UnitVariant +pub fn unit_variant() -> TestEnum { ... } + +// Expected generated constructor for TestEnum::TupleVariant (Subformer) +pub fn tuple_variant() -> TestEnumTupleVariantFormer< ... > { ... } // Takes no args yet + +// Expected generated constructor for TestEnum::StructVariant (Subformer) +pub fn struct_variant() -> TestEnumStructVariantFormer< ... > { ... } // Takes no args yet +*/ + + +// === Include Test Logic === +// Includes tests that call the *expected* generated functions. +include!( "standalone_constructor_only_test.rs" ); // Use the consistent name + +// qqq : xxx : finish it // Keep this comment \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/standalone_constructor_manual.rs b/module/core/former/tests/inc/former_enum_tests/standalone_constructor_manual.rs new file mode 100644 index 0000000000..5ad5eeb267 --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/standalone_constructor_manual.rs @@ -0,0 +1,362 @@ +// module/core/former/tests/inc/former_enum_tests/standalone_constructor_manual.rs +//! +//! Manual implementation for testing standalone constructors for enums. +//! Uses consistent names matching the derive version for testing. +//! + +#[ allow( unused_imports ) ] +use ::former::prelude::*; +#[ allow( unused_imports ) ] +use ::former_types:: +{ + Storage, StoragePreform, + FormerDefinitionTypes, FormerMutator, FormerDefinition, + FormingEnd, ReturnPreformed, +}; + +// === Enum Definition === + +/// Enum for manual testing of standalone constructors. +#[ derive( Debug, PartialEq, Clone ) ] +pub enum TestEnum // Consistent name +{ + /// A unit variant. + UnitVariant, + /// A tuple variant with one field. + TupleVariant( i32 ), + /// A struct variant with one field. + StructVariant + { + field : String, + }, +} + +// === Manual Former Implementation for TupleVariant === + +// Storage +/// Storage for TestEnumTupleVariantFormer. +#[ derive( Debug, Default ) ] +pub struct TestEnumTupleVariantFormerStorage +{ + /// Option to store the value for the tuple field. + pub _0 : ::core::option::Option< i32 >, +} + +impl Storage for TestEnumTupleVariantFormerStorage +{ + type Preformed = i32; // Preformed is the inner type +} + +impl StoragePreform for TestEnumTupleVariantFormerStorage +{ + #[ inline( always ) ] + fn preform( mut self ) -> Self::Preformed + { + self._0.take().unwrap_or_default() + } +} + +// Definition Types +/// Definition types for TestEnumTupleVariantFormer. +#[ derive( Debug, Default ) ] +pub struct TestEnumTupleVariantFormerDefinitionTypes< Context = (), Formed = TestEnum > +{ + _phantom : core::marker::PhantomData< ( Context, Formed ) >, +} + +impl< Context, Formed > FormerDefinitionTypes +for TestEnumTupleVariantFormerDefinitionTypes< Context, Formed > +{ + type Storage = TestEnumTupleVariantFormerStorage; + type Formed = Formed; + type Context = Context; +} + +// Mutator +impl< Context, Formed > FormerMutator +for TestEnumTupleVariantFormerDefinitionTypes< Context, Formed > +{ +} + +// Definition +/// Definition for TestEnumTupleVariantFormer. +#[ derive( Debug, Default ) ] +pub struct TestEnumTupleVariantFormerDefinition< Context = (), Formed = TestEnum, End = TestEnumTupleVariantEnd > // Use consistent End name +{ + _phantom : core::marker::PhantomData< ( Context, Formed, End ) >, +} + +impl< Context, Formed, End > FormerDefinition +for TestEnumTupleVariantFormerDefinition< Context, Formed, End > +where + End : FormingEnd< TestEnumTupleVariantFormerDefinitionTypes< Context, Formed > >, +{ + type Storage = TestEnumTupleVariantFormerStorage; + type Formed = Formed; + type Context = Context; + type Types = TestEnumTupleVariantFormerDefinitionTypes< Context, Formed >; + type End = End; +} + +// Former +/// Manual Former implementation for TestEnum::TupleVariant. +#[ derive( Debug ) ] +pub struct TestEnumTupleVariantFormer< Definition = TestEnumTupleVariantFormerDefinition > // Use consistent Def name +where + Definition : FormerDefinition< Storage = TestEnumTupleVariantFormerStorage >, +{ + storage : Definition::Storage, + context : Option< Definition::Context >, + on_end : Option< Definition::End >, +} + +impl< Definition > TestEnumTupleVariantFormer< Definition > +where + Definition : FormerDefinition< Storage = TestEnumTupleVariantFormerStorage >, + Definition::Types : FormerDefinitionTypes< Storage = TestEnumTupleVariantFormerStorage >, + Definition::Types : FormerMutator, +{ + #[ 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 ) } + } + + #[ inline( always ) ] + #[ allow( dead_code) ] + pub fn new( on_end : Definition::End ) -> Self + { + Self::begin( None, None, on_end ) + } + + /// Setter for the tuple field. + #[ inline ] + #[ allow( dead_code) ] + pub fn _0( mut self, src : impl Into< i32 > ) -> Self + { + debug_assert!( self.storage._0.is_none(), "Field '_0' was already set" ); + self.storage._0 = Some( src.into() ); + self + } +} + +// End Struct for TupleVariant +/// End handler for TestEnumTupleVariantFormer. +#[ derive( Debug, Default ) ] +pub struct TestEnumTupleVariantEnd; // Consistent name + +impl FormingEnd< TestEnumTupleVariantFormerDefinitionTypes< (), TestEnum > > +for TestEnumTupleVariantEnd // Consistent name +{ + #[ inline( always ) ] + fn call + ( + &self, + storage : TestEnumTupleVariantFormerStorage, + _context : Option< () >, + ) -> TestEnum + { + let val = storage.preform(); + TestEnum::TupleVariant( val ) // Use consistent enum name + } +} + +// === Manual Former Implementation for StructVariant === + +// Storage +/// Storage for TestEnumStructVariantFormer. +#[ derive( Debug, Default ) ] +pub struct TestEnumStructVariantFormerStorage +{ + /// Option to store the value for the struct field. + pub field : ::core::option::Option< String >, +} + +impl Storage for TestEnumStructVariantFormerStorage +{ + type Preformed = String; // Preformed is the inner type +} + +impl StoragePreform for TestEnumStructVariantFormerStorage +{ + #[ inline( always ) ] + fn preform( mut self ) -> Self::Preformed + { + self.field.take().unwrap_or_default() + } +} + +// Definition Types +/// Definition types for TestEnumStructVariantFormer. +#[ derive( Debug, Default ) ] +pub struct TestEnumStructVariantFormerDefinitionTypes< Context = (), Formed = TestEnum > +{ + _phantom : core::marker::PhantomData< ( Context, Formed ) >, +} + +impl< Context, Formed > FormerDefinitionTypes +for TestEnumStructVariantFormerDefinitionTypes< Context, Formed > +{ + type Storage = TestEnumStructVariantFormerStorage; + type Formed = Formed; + type Context = Context; +} + +// Mutator +impl< Context, Formed > FormerMutator +for TestEnumStructVariantFormerDefinitionTypes< Context, Formed > +{ +} + +// Definition +/// Definition for TestEnumStructVariantFormer. +#[ derive( Debug, Default ) ] +pub struct TestEnumStructVariantFormerDefinition< Context = (), Formed = TestEnum, End = TestEnumStructVariantEnd > // Use consistent End name +{ + _phantom : core::marker::PhantomData< ( Context, Formed, End ) >, +} + +impl< Context, Formed, End > FormerDefinition +for TestEnumStructVariantFormerDefinition< Context, Formed, End > +where + End : FormingEnd< TestEnumStructVariantFormerDefinitionTypes< Context, Formed > >, +{ + type Storage = TestEnumStructVariantFormerStorage; + type Formed = Formed; + type Context = Context; + type Types = TestEnumStructVariantFormerDefinitionTypes< Context, Formed >; + type End = End; +} + +// Former +/// Manual Former implementation for TestEnum::StructVariant. +#[ derive( Debug ) ] +pub struct TestEnumStructVariantFormer< Definition = TestEnumStructVariantFormerDefinition > // Use consistent Def name +where + Definition : FormerDefinition< Storage = TestEnumStructVariantFormerStorage >, +{ + storage : Definition::Storage, + context : Option< Definition::Context >, + on_end : Option< Definition::End >, +} + +impl< Definition > TestEnumStructVariantFormer< Definition > +where + Definition : FormerDefinition< Storage = TestEnumStructVariantFormerStorage >, + Definition::Types : FormerDefinitionTypes< Storage = TestEnumStructVariantFormerStorage >, + Definition::Types : FormerMutator, +{ + #[ 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 ) } + } + + #[ inline( always ) ] + #[ allow( dead_code) ] + pub fn new( on_end : Definition::End ) -> Self + { + Self::begin( None, None, on_end ) + } + + /// Setter for the struct field. + #[ inline ] + pub fn field( mut self, src : impl Into< String > ) -> Self + { + debug_assert!( self.storage.field.is_none(), "Field 'field' was already set" ); + self.storage.field = Some( src.into() ); + self + } +} + +// End Struct for StructVariant +/// End handler for TestEnumStructVariantFormer. +#[ derive( Debug, Default ) ] +pub struct TestEnumStructVariantEnd; // Consistent name + +impl FormingEnd< TestEnumStructVariantFormerDefinitionTypes< (), TestEnum > > +for TestEnumStructVariantEnd // Consistent name +{ + #[ inline( always ) ] + fn call + ( + &self, + storage : TestEnumStructVariantFormerStorage, + _context : Option< () >, + ) -> TestEnum + { + let val = storage.preform(); + TestEnum::StructVariant { field : val } // Use consistent enum name + } +} + +// === Standalone Constructors (Manual) === + +/// Manual standalone constructor for TestEnum::UnitVariant. +pub fn unit_variant() -> TestEnum // Consistent name +{ + TestEnum::UnitVariant // Consistent name +} + +/// Manual standalone constructor for TestEnum::TupleVariant. +/// Returns a Former instance for the variant. +// <<< Takes ZERO arguments >>> +pub fn tuple_variant() // Consistent name +-> // Arrow and type on new line +TestEnumTupleVariantFormer< TestEnumTupleVariantFormerDefinition< (), TestEnum, TestEnumTupleVariantEnd > > // Consistent names +{ + // <<< Begins with None storage >>> + TestEnumTupleVariantFormer::begin( None, None, TestEnumTupleVariantEnd ) // Consistent names +} + +/// Manual standalone constructor for TestEnum::StructVariant. +/// Returns a Former instance for the variant. +// <<< Takes ZERO arguments >>> +pub fn struct_variant() // Consistent name +-> // Arrow and type on new line +TestEnumStructVariantFormer< TestEnumStructVariantFormerDefinition< (), TestEnum, TestEnumStructVariantEnd > > // Consistent names +{ + // <<< Begins with None storage >>> + TestEnumStructVariantFormer::begin( None, None, TestEnumStructVariantEnd ) // Consistent names +} + +// === Include Test Logic === +include!( "standalone_constructor_only_test.rs" ); // Use the consistent name \ No newline at end of file diff --git a/module/core/former/tests/inc/former_enum_tests/standalone_constructor_only_test.rs b/module/core/former/tests/inc/former_enum_tests/standalone_constructor_only_test.rs new file mode 100644 index 0000000000..091d6ad0f3 --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/standalone_constructor_only_test.rs @@ -0,0 +1,61 @@ +// module/core/former/tests/inc/former_enum_tests/standalone_constructor_only_test.rs +// +// Contains the shared test logic for standalone enum constructors. +// This file is included by both the manual and derive test files. +// + +// Use the items defined in the including file (manual or derive) +use super::*; + +/// Tests the standalone constructor for a unit variant. +#[ test ] +fn unit_variant_test() // Use enum-specific test name +{ + // Call the constructor function (manual or derived) + // Assumes `unit_variant` is defined in the including scope + let instance = unit_variant(); + + // Define the expected enum instance (using the consistent enum name) + let expected = TestEnum::UnitVariant; // Use TestEnum + + // Assert that the formed instance matches the expected one + assert_eq!( instance, expected ); +} + +/// Tests the standalone constructor for a tuple variant. +#[ test ] +fn tuple_variant_test() // Use enum-specific test name +{ + // Call the constructor function (manual or derived) + let former = tuple_variant(); // <<< Call with zero args + + // Use the former to build the variant + let instance = former + ._0( 101 ) // Set the tuple field using the generated setter + .form(); + + // Define the expected enum instance (using the consistent enum name) + let expected = TestEnum::TupleVariant( 101 ); // Use TestEnum + + // Assert that the formed instance matches the expected one + assert_eq!( instance, expected ); +} + +/// Tests the standalone constructor for a struct variant. +#[ test ] +fn struct_variant_test() // Use enum-specific test name +{ + // Call the constructor function (manual or derived) + let former = struct_variant(); // <<< Call with zero args + + // Use the former to build the variant + let instance = former + .field( "value".to_string() ) // Set the struct field using the generated setter + .form(); + + // Define the expected enum instance (using the consistent enum name) + let expected = TestEnum::StructVariant { field : "value".to_string() }; // Use TestEnum + + // Assert that the formed instance matches the expected one + assert_eq!( instance, expected ); +} diff --git a/module/core/former/tests/inc/former_enum_tests/subform_collection_test.rs b/module/core/former/tests/inc/former_enum_tests/subform_collection_test.rs new file mode 100644 index 0000000000..0864196046 --- /dev/null +++ b/module/core/former/tests/inc/former_enum_tests/subform_collection_test.rs @@ -0,0 +1,64 @@ +// // File: module/core/former/tests/inc/former_enum_tests/subform_collection_test.rs +// //! Minimal test case demonstrating the compilation failure +// //! when using `#[subform_entry]` on a `Vec`. +// +// use super::*; +// use former::Former; +// use std::vec::Vec; +// +// /// A simple enum deriving Former. +// #[ derive( Debug, PartialEq, Clone, Former ) ] +// pub enum SimpleEnum +// { +// /// Unit variant. +// Unit, +// /// Tuple variant with a single value. +// #[ scalar ] // Use scalar for direct constructor +// Value( i32 ), +// } +// +// /// A struct containing a vector of the enum. +// #[ derive( Debug, PartialEq, Default, Former ) ] +// pub struct StructWithEnumVec +// { +// /// Field attempting to use subform_entry on Vec. +// #[ subform_entry ] +// items : Vec< SimpleEnum >, +// } +// +// /// Test attempting to use the subformer generated for `items`. +// /// This test FAIL TO COMPILE because `former` does not +// /// currently support generating the necessary subformer logic for enum entries +// /// within a collection via `#[subform_entry]`. +// #[ test ] +// fn attempt_subform_enum_vec() +// { +// // This code block demonstrates the intended usage that fails. +// /* +// let _result = StructWithEnumVec::former() +// // Trying to access the subformer for the Vec field. +// // The derive macro does not generate the `.items()` method correctly +// // for Vec with #[subform_entry]. It doesn't know how to +// // return a former that can then construct *specific enum variants*. +// .items() +// // Attempting to call a variant constructor method (e.g., .value()) +// // on the hypothetical subformer returned by .items(). This method +// // would not be generated. +// .value( 10 ) +// // Ending the hypothetical subformer for the first enum entry. +// .end() +// // Attempting to start another entry. +// .items() +// // Attempting to call the unit variant constructor method. +// .unit() +// // Ending the hypothetical subformer for the second enum entry. +// .end() +// // Finalizing the parent struct. +// .form(); +// */ +// +// // Assertion to make the test function valid, though it won't be reached +// // if the compilation fails as expected. +// assert!( true, "Test executed - compilation should have failed before this point." ); +// } +// // qqq : xxx : make it working 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..63bf71c363 --- /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 90% 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 index 20dea37cf8..937e0f36cc 100644 --- a/module/core/former/tests/inc/former_tests/attribute_feature.rs +++ b/module/core/former/tests/inc/former_struct_tests/attribute_feature.rs @@ -1,4 +1,5 @@ -#[ allow( unused_imports ) ] +#![ allow( unexpected_cfgs ) ] + use super::*; #[ derive( Debug, PartialEq ) ] 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..5bc7c3a156 --- /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_struct_tests/parametrized_dyn.rs b/module/core/former/tests/inc/former_struct_tests/parametrized_dyn.rs new file mode 100644 index 0000000000..04006d88c7 --- /dev/null +++ b/module/core/former/tests/inc/former_struct_tests/parametrized_dyn.rs @@ -0,0 +1,412 @@ + +// xxx2 : qqq2 : +// - uncomment code +// - duplicate the file and actually use macro Former +// - make macro working taking into account this corner case +// - for your conveniency there expansion of macro is below + +// use super::*; +// use core::fmt; +// +// pub trait FilterCol : fmt::Debug +// { +// fn filter_col( &self, key : &str ) -> bool; +// } +// +// #[ derive( Debug, Default, PartialEq, Clone, Copy ) ] +// pub struct All; +// +// impl All +// { +// pub fn instance() -> & 'static dyn FilterCol +// { +// static INSTANCE : All = All; +// &INSTANCE +// } +// } +// +// impl Default for &'static dyn FilterCol +// { +// #[ inline( always ) ] +// fn default() -> Self +// { +// All::instance() +// } +// } +// +// impl FilterCol for All +// { +// #[ inline( always ) ] +// fn filter_col( &self, _key : &str ) -> bool +// { +// true +// } +// } +// +// #[ derive( Default ) ] +// // #[ derive( former::Former ) ] // xxx : qqq : uncomment and fix problem with lifetime +// // #[ derive( former::Former ) ] #[ debug ] +// pub struct Styles< 'callback > +// { +// +// // pub output_format : &'static dyn AsRef< str >, +// pub filter : &'callback dyn FilterCol, +// +// } +// +// // === begin_coercing of generated +// +// #[automatically_derived] +// impl< 'callback > Styles< 'callback > where +// { +// #[doc = r""] +// #[doc = r" Provides a mechanism to initiate the formation process with a default completion behavior."] +// #[doc = r""] +// #[inline(always)] +// pub fn former() -> StylesFormer< 'callback, StylesFormerDefinition< 'callback, (), Styles< 'callback >, former::ReturnPreformed > > +// { +// StylesFormer::< 'callback, StylesFormerDefinition< 'callback, (), Styles< 'callback >, former::ReturnPreformed > >::new_coercing(former::ReturnPreformed) +// } +// } +// +// impl< 'callback, Definition > former::EntityToFormer< Definition > for Styles< 'callback > +// where +// Definition : former::FormerDefinition< Storage = StylesFormerStorage< 'callback > >, +// { +// type Former = StylesFormer< 'callback, Definition >; +// } +// +// impl< 'callback > former::EntityToStorage for Styles< 'callback > +// where +// { +// type Storage = StylesFormerStorage< 'callback >; +// } +// +// impl< 'callback, __Context, __Formed, __End > former::EntityToDefinition< __Context, __Formed, __End > for Styles< 'callback > +// where +// __End : former::FormingEnd< StylesFormerDefinitionTypes< 'callback, __Context, __Formed > >, +// { +// type Definition = StylesFormerDefinition< 'callback, __Context, __Formed, __End >; +// type Types = StylesFormerDefinitionTypes< 'callback, __Context, __Formed >; +// } +// +// impl< 'callback, __Context, __Formed > former::EntityToDefinitionTypes< __Context, __Formed > for Styles< 'callback > +// where +// { +// type Types = StylesFormerDefinitionTypes< 'callback, __Context, __Formed >; +// } +// +// #[doc = r" Defines the generic parameters for formation behavior including context, form, and end conditions."] +// #[derive(Debug)] +// pub struct StylesFormerDefinitionTypes< 'callback, __Context = (), __Formed = Styles< 'callback > > +// where +// { +// _phantom: ::core::marker::PhantomData< ( & 'callback (), * const __Context, * const __Formed ) >, +// } +// +// impl< 'callback, __Context, __Formed > ::core::default::Default for StylesFormerDefinitionTypes< 'callback, __Context, __Formed > +// where +// { +// fn default() -> Self +// { +// Self { _phantom: ::core::marker::PhantomData } +// } +// } +// +// impl< 'callback, __Context, __Formed > former::FormerDefinitionTypes for StylesFormerDefinitionTypes< 'callback, __Context, __Formed > +// where +// { +// type Storage = StylesFormerStorage< 'callback >; +// type Formed = __Formed; +// type Context = __Context; +// } +// +// #[doc = r" Holds the definition types used during the formation process."] +// #[derive(Debug)] +// pub struct StylesFormerDefinition< 'callback, __Context = (), __Formed = Styles< 'callback >, __End = former::ReturnPreformed > +// where +// { +// _phantom: ::core::marker::PhantomData< ( & 'callback (), * const __Context, * const __Formed, * const __End ) >, +// } +// +// impl< 'callback, __Context, __Formed, __End > ::core::default::Default for StylesFormerDefinition< 'callback, __Context, __Formed, __End > +// where +// { +// fn default() -> Self +// { +// Self { _phantom: ::core::marker::PhantomData } +// } +// } +// +// impl< 'callback, __Context, __Formed, __End > former::FormerDefinition for StylesFormerDefinition< 'callback, __Context, __Formed, __End > +// where +// __End : former::FormingEnd< StylesFormerDefinitionTypes< 'callback, __Context, __Formed > >, +// { +// type Types = StylesFormerDefinitionTypes< 'callback, __Context, __Formed >; +// type End = __End; +// type Storage = StylesFormerStorage< 'callback >; +// type Formed = __Formed; +// type Context = __Context; +// } +// +// impl< 'callback, __Context, __Formed > former::FormerMutator for StylesFormerDefinitionTypes< 'callback, __Context, __Formed > +// where +// {} +// +// #[doc = "Stores potential values for fields during the formation process."] +// #[allow(explicit_outlives_requirements)] +// pub struct StylesFormerStorage< 'callback > +// where +// { +// #[doc = r" A field"] +// pub filter: ::core::option::Option< & 'callback dyn FilterCol >, +// } +// +// impl< 'callback > ::core::default::Default for StylesFormerStorage< 'callback > +// where +// { +// #[inline(always)] +// fn default() -> Self +// { +// Self { filter: ::core::option::Option::None } +// } +// } +// +// impl< 'callback > former::Storage for StylesFormerStorage< 'callback > +// where +// { +// type Preformed = Styles< 'callback >; +// } +// +// impl< 'callback > former::StoragePreform for StylesFormerStorage< 'callback > +// where +// { +// fn preform(mut self) -> Self::Preformed +// { +// let filter = if self.filter.is_some() +// { +// self.filter.take().unwrap() +// } +// else +// { +// { +// trait MaybeDefault +// { +// fn maybe_default(self: &Self) -> T +// { +// panic!("Field 'filter' isn't initialized") +// } +// } +// impl MaybeDefault for &::core::marker::PhantomData +// {} +// impl MaybeDefault for ::core::marker::PhantomData +// where +// T: ::core::default::Default, +// { +// fn maybe_default(self: &Self) -> T +// { +// T::default() +// } +// } +// (&::core::marker::PhantomData::<&'callback dyn FilterCol>).maybe_default() +// } +// }; +// let result = Styles::< 'callback > { filter }; +// return result; +// } +// } +// +// #[doc = "\nStructure to form [Styles]. Represents a forming entity designed to construct objects through a builder pattern.\n\nThis structure holds temporary storage and context during the formation process and\nutilizes a defined end strategy to finalize the object creation.\n"] +// pub struct StylesFormer< 'callback, Definition = StylesFormerDefinition< 'callback, (), Styles< 'callback >, former::ReturnPreformed > > +// where +// Definition: former::FormerDefinition< Storage = StylesFormerStorage< 'callback > >, +// Definition::Types: former::FormerDefinitionTypes< Storage = StylesFormerStorage< 'callback > >, +// { +// #[doc = r" Temporary storage for all fields during the formation process. It contains"] +// #[doc = r" partial data that progressively builds up to the final object."] +// pub storage: Definition::Storage, +// #[doc = r" An optional context providing additional data or state necessary for custom"] +// #[doc = r" formation logic or to facilitate this former's role as a subformer within another former."] +// pub context: ::core::option::Option< Definition::Context >, +// #[doc = r" An optional closure or handler that is invoked to transform the accumulated"] +// #[doc = r" temporary storage into the final object structure once formation is complete."] +// pub on_end: ::core::option::Option< Definition::End >, +// } +// +// #[automatically_derived] +// impl< 'callback, Definition > StylesFormer< 'callback, Definition > +// where +// Definition: former::FormerDefinition< Storage = StylesFormerStorage< 'callback > >, +// Definition::Types: former::FormerDefinitionTypes< Storage = StylesFormerStorage< 'callback > >, +// { +// #[doc = r""] +// #[doc = r" Initializes a former with an end condition and default storage."] +// #[doc = r""] +// #[inline(always)] +// pub fn new(on_end: Definition::End) -> Self +// { +// Self::begin_coercing(::core::option::Option::None, ::core::option::Option::None, on_end) +// } +// +// #[doc = r""] +// #[doc = r" Initializes a former with a coercible end condition."] +// #[doc = r""] +// #[inline(always)] +// pub fn new_coercing(end: IntoEnd) -> Self +// where +// IntoEnd: ::core::convert::Into, +// { +// Self::begin_coercing(::core::option::Option::None, ::core::option::Option::None, end) +// } +// +// #[doc = r""] +// #[doc = r" Begins the formation process with specified context and termination logic."] +// #[doc = r""] +// #[inline(always)] +// pub fn begin( +// mut storage: ::core::option::Option, +// context: ::core::option::Option, +// on_end: ::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), +// } +// } +// +// #[doc = r""] +// #[doc = r" Starts the formation process with coercible end condition and optional initial values."] +// #[doc = r""] +// #[inline(always)] +// pub fn begin_coercing( +// mut storage: ::core::option::Option, +// context: ::core::option::Option, +// on_end: IntoEnd, +// ) -> Self +// where +// IntoEnd: ::core::convert::Into<::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)), +// } +// } +// +// #[doc = r""] +// #[doc = r" Wrapper for `end` to align with common builder pattern terminologies."] +// #[doc = r""] +// #[inline(always)] +// pub fn form(self) -> ::Formed +// { +// self.end() +// } +// +// #[doc = r""] +// #[doc = r" Completes the formation and returns the formed object."] +// #[doc = r""] +// #[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) +// } +// +// #[doc = "Scalar setter for the 'filter' field."] +// #[inline] +// pub fn filter(mut self, src: Src) -> Self +// where +// Src: ::core::convert::Into<& 'callback dyn FilterCol>, +// { +// debug_assert!(self.storage.filter.is_none()); +// self.storage.filter = ::core::option::Option::Some(::core::convert::Into::into(src)); +// self +// } +// } +// +// impl< 'callback, Definition > StylesFormer< 'callback, Definition > +// where +// Definition: former::FormerDefinition< Storage = StylesFormerStorage< 'callback >, Formed = Styles< 'callback > >, +// Definition::Types: former::FormerDefinitionTypes< Storage = StylesFormerStorage< 'callback >, Formed = Styles< 'callback > >, +// Definition: former::FormerDefinition< Storage = StylesFormerStorage< 'callback > >, +// Definition::Types: former::FormerDefinitionTypes< Storage = StylesFormerStorage< 'callback > >, +// { +// #[doc = r" Executes the transformation from the former's storage state to the preformed object as specified by the definition."] +// pub fn preform(self) -> ::Formed +// { +// former::StoragePreform::preform(self.storage) +// } +// } +// +// #[automatically_derived] +// impl< 'callback, Definition > StylesFormer< 'callback, Definition > +// where +// Definition: former::FormerDefinition< Storage = StylesFormerStorage< 'callback >, Formed = Styles< 'callback > >, +// Definition::Types: former::FormerDefinitionTypes< Storage = StylesFormerStorage< 'callback >, Formed = Styles< 'callback > >, +// { +// #[doc = r""] +// #[doc = r" Finish setting options and call perform on formed entity."] +// #[doc = r""] +// #[doc = r" If `perform` defined then associated method is called and its result returned instead of entity."] +// #[doc = r" For example `perform()` of structure with : `#[ perform( fn after1() -> &str > )` returns `&str`."] +// #[doc = r""] +// #[inline(always)] +// pub fn perform(self) -> Definition::Formed +// { +// let result = self.form(); +// return result; +// } +// } +// +// impl< 'callback, Definition > former::FormerBegin< Definition > for StylesFormer< 'callback, Definition > +// where +// Definition: former::FormerDefinition< Storage = StylesFormerStorage< 'callback > >, +// { +// #[inline(always)] +// fn former_begin( +// storage: ::core::option::Option, +// context: ::core::option::Option, +// on_end: Definition::End, +// ) -> Self +// { +// debug_assert!(storage.is_none()); +// Self::begin(::core::option::Option::None, context, on_end) +// } +// } +// +// #[doc = r" Provides a specialized former for structure using predefined settings for superformer and end conditions."] +// #[doc = r""] +// #[doc = r" This type alias configures former of the structure with a specific definition to streamline its usage in broader contexts,"] +// #[doc = r" especially where structure needs to be integrated into larger structures with a clear termination condition."] +// pub type StylesAsSubformer< 'callback, __Superformer, __End > = StylesFormer< 'callback, StylesFormerDefinition< 'callback, __Superformer, __Superformer, __End > >; +// +// #[doc = "\nRepresents an end condition for former of [`$Styles`], tying the lifecycle of forming processes to a broader context.\n\nThis trait is intended for use with subformer alias, ensuring that end conditions are met according to the\nspecific needs of the broader forming context. It mandates the implementation of `former::FormingEnd`.\n "] +// pub trait StylesAsSubformerEnd< 'callback, SuperFormer > +// where +// Self: former::FormingEnd< StylesFormerDefinitionTypes< 'callback, SuperFormer, SuperFormer > >, +// {} +// impl< 'callback, SuperFormer, __T > StylesAsSubformerEnd< 'callback, SuperFormer > for __T +// where +// Self: former::FormingEnd< StylesFormerDefinitionTypes< 'callback, SuperFormer, SuperFormer > >, +// {} +// +// // === end of generated +// +// #[ test ] +// fn basic() +// { +// } \ No newline at end of file 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_struct_tests/standalone_constructor_derive.rs b/module/core/former/tests/inc/former_struct_tests/standalone_constructor_derive.rs new file mode 100644 index 0000000000..df042362db --- /dev/null +++ b/module/core/former/tests/inc/former_struct_tests/standalone_constructor_derive.rs @@ -0,0 +1,42 @@ +// module/core/former/tests/inc/former_struct_tests/standalone_constructor_derive.rs +//! +//! Derive-based tests for standalone constructors for structs. +//! Uses consistent names matching the manual version for testing. +//! + +#[ allow( unused_imports ) ] +use ::former::prelude::*; +use ::former::Former; // Import derive macro + +// === Struct Definition: No Args === + +/// Struct using derive for standalone constructors without arguments. +// Attributes to be implemented by the derive macro +#[ derive( Debug, PartialEq, Default, Clone, Former ) ] +#[ standalone_constructors ] // New attribute +pub struct TestStructNoArgs // Consistent name +{ + /// A simple field. + pub field1 : i32, +} + +// === Struct Definition: With Args === + +/// Struct using derive for standalone constructors with arguments. +// Attributes to be implemented by the derive macro +#[ derive( Debug, PartialEq, Default, Clone, Former ) ] +#[ standalone_constructors ] // New attribute +pub struct TestStructWithArgs // Consistent name +{ + /// Field A (constructor arg - attribute removed for now). + #[ arg_for_constructor ] // <<< Uncommented + pub field_a : String, + /// Field B (constructor arg - attribute removed for now). + #[ arg_for_constructor ] // <<< Uncommented + pub field_b : bool, + /// Field C (optional, not constructor arg). + pub field_c : Option< f32 >, +} + +// === Include Test Logic === +include!( "standalone_constructor_only_test.rs" ); // Include the single test file diff --git a/module/core/former/tests/inc/former_struct_tests/standalone_constructor_manual.rs b/module/core/former/tests/inc/former_struct_tests/standalone_constructor_manual.rs new file mode 100644 index 0000000000..8b02ccee32 --- /dev/null +++ b/module/core/former/tests/inc/former_struct_tests/standalone_constructor_manual.rs @@ -0,0 +1,381 @@ +//! +//! Manual implementation for testing standalone constructors. +//! Uses consistent names matching the derive version for testing. +//! + +#[ allow( unused_imports ) ] +use ::former::prelude::*; +#[ allow( unused_imports ) ] +use ::former_types:: +{ + Storage, StoragePreform, + FormerDefinitionTypes, FormerMutator, FormerDefinition, + FormingEnd, ReturnPreformed, +}; + +// === Struct Definition: No Args === + +/// Manual struct without constructor args. +#[ derive( Debug, PartialEq, Default, Clone ) ] +pub struct TestStructNoArgs +{ + /// A simple field. + pub field1 : i32, +} + +// === Manual Former Implementation: No Args === +// ... (No changes needed here, as all methods/fields are used by no_args_test) ... +// Storage +/// Manual storage for TestStructNoArgsFormer. +#[ derive( Debug, Default ) ] +pub struct TestStructNoArgsFormerStorage +{ + /// Optional storage for field1. + pub field1 : Option< i32 >, +} + +impl Storage for TestStructNoArgsFormerStorage +{ + type Preformed = TestStructNoArgs; +} + +impl StoragePreform for TestStructNoArgsFormerStorage +{ + #[ inline( always ) ] + fn preform( mut self ) -> Self::Preformed + { + TestStructNoArgs + { + field1 : self.field1.take().unwrap_or_default(), + } + } +} + +// Definition Types +/// Manual definition types for TestStructNoArgsFormer. +#[ derive( Debug, Default ) ] +pub struct TestStructNoArgsFormerDefinitionTypes< Context = (), Formed = TestStructNoArgs > +{ + _phantom : core::marker::PhantomData< ( Context, Formed ) >, +} + +impl< Context, Formed > FormerDefinitionTypes +for TestStructNoArgsFormerDefinitionTypes< Context, Formed > +{ + type Storage = TestStructNoArgsFormerStorage; + type Formed = Formed; + type Context = Context; +} + +impl< Context, Formed > FormerMutator +for TestStructNoArgsFormerDefinitionTypes< Context, Formed > +{ +} + +// Definition +/// Manual definition for TestStructNoArgsFormer. +#[ derive( Debug, Default ) ] +pub struct TestStructNoArgsFormerDefinition< Context = (), Formed = TestStructNoArgs, End = ReturnPreformed > +{ + _phantom : core::marker::PhantomData< ( Context, Formed, End ) >, +} + +impl< Context, Formed, End > FormerDefinition +for TestStructNoArgsFormerDefinition< Context, Formed, End > +where + End : FormingEnd< TestStructNoArgsFormerDefinitionTypes< Context, Formed > >, +{ + type Storage = TestStructNoArgsFormerStorage; + type Formed = Formed; + type Context = Context; + type Types = TestStructNoArgsFormerDefinitionTypes< Context, Formed >; + type End = End; +} + +// Former +/// Manual Former for TestStructNoArgs. +#[ derive( Debug ) ] +pub struct TestStructNoArgsFormer< Definition = TestStructNoArgsFormerDefinition > +where + Definition : FormerDefinition< Storage = TestStructNoArgsFormerStorage >, +{ + /// Former storage. + pub storage : Definition::Storage, + /// Former context. + pub context : Option< Definition::Context >, + /// Former end handler. + pub on_end : Option< Definition::End >, +} + +impl< Definition > TestStructNoArgsFormer< Definition > +where + Definition : FormerDefinition< Storage = TestStructNoArgsFormerStorage >, + Definition::Types : FormerDefinitionTypes< Storage = TestStructNoArgsFormerStorage >, + Definition::Types : FormerMutator, +{ + /// Finalizes the forming process. + #[ inline( always ) ] + pub fn form( self ) -> < Definition::Types as FormerDefinitionTypes >::Formed + { + self.end() + } + + /// Finalizes the forming process. + #[ inline( always ) ] + pub fn end( mut self ) + -> + < Definition::Types as FormerDefinitionTypes >::Formed + { + let end = self.on_end.take().unwrap(); + < Definition::Types as FormerMutator >::form_mutation( &mut self.storage, &mut self.context ); + end.call( self.storage, self.context.take() ) + } + + /// Begins the forming process. + #[ inline( always ) ] + pub fn begin + ( + s : Option< Definition::Storage >, + c : Option< Definition::Context >, + e : Definition::End, + ) -> Self + { + Self + { + storage : s.unwrap_or_default(), + context : c, + on_end : Some( e ), + } + } + + /// Creates a new former instance. + #[ inline( always ) ] + pub fn new( e : Definition::End ) -> Self + { + Self::begin( None, None, e ) + } + + /// Setter for field1. + #[ inline ] + pub fn field1( mut self, src : impl Into< i32 > ) -> Self + { + debug_assert!( self.storage.field1.is_none() ); + self.storage.field1 = Some( src.into() ); + self + } +} + +// === Standalone Constructor (Manual): No Args === +/// Manual standalone constructor for TestStructNoArgs. +pub fn test_struct_no_args() +-> +TestStructNoArgsFormer< TestStructNoArgsFormerDefinition< (), TestStructNoArgs, ReturnPreformed > > +{ + TestStructNoArgsFormer::new( ReturnPreformed ) +} + +// === Struct Definition: With Args === +/// Manual struct with constructor args. +#[ derive( Debug, PartialEq, Default, Clone ) ] +pub struct TestStructWithArgs +{ + /// Field A. + pub field_a : String, + /// Field B. + pub field_b : bool, + /// Field C (optional). + pub field_c : Option< f32 >, +} + +// === Manual Former Implementation: With Args === +// ... (Storage, DefTypes, Def implementations remain the same) ... +/// Manual storage for TestStructWithArgsFormer. +#[ derive( Debug, Default ) ] +pub struct TestStructWithArgsFormerStorage +{ + /// Optional storage for field_a. + pub field_a : Option< String >, + /// Optional storage for field_b. + pub field_b : Option< bool >, + /// Optional storage for field_c. + pub field_c : Option< f32 >, +} + +impl Storage for TestStructWithArgsFormerStorage +{ + type Preformed = TestStructWithArgs; +} + +impl StoragePreform for TestStructWithArgsFormerStorage +{ + #[ inline( always ) ] + fn preform( mut self ) -> Self::Preformed + { + TestStructWithArgs + { + field_a : self.field_a.take().unwrap_or_default(), + field_b : self.field_b.take().unwrap_or_default(), + field_c : self.field_c.take(), + } + } +} + +/// Manual definition types for TestStructWithArgsFormer. +#[ derive( Debug, Default ) ] +pub struct TestStructWithArgsFormerDefinitionTypes< C = (), F = TestStructWithArgs > +{ + _p : core::marker::PhantomData< ( C, F ) >, +} + +impl< C, F > FormerDefinitionTypes +for TestStructWithArgsFormerDefinitionTypes< C, F > +{ + type Storage = TestStructWithArgsFormerStorage; + type Formed = F; + type Context = C; +} + +impl< C, F > FormerMutator +for TestStructWithArgsFormerDefinitionTypes< C, F > +{ +} + +/// Manual definition for TestStructWithArgsFormer. +#[ derive( Debug, Default ) ] +pub struct TestStructWithArgsFormerDefinition< C = (), F = TestStructWithArgs, E = ReturnPreformed > +{ + _p : core::marker::PhantomData< ( C, F, E ) >, +} + +impl< C, F, E > FormerDefinition +for TestStructWithArgsFormerDefinition< C, F, E > +where + E : FormingEnd< TestStructWithArgsFormerDefinitionTypes< C, F > >, +{ + type Storage = TestStructWithArgsFormerStorage; + type Formed = F; + type Context = C; + type Types = TestStructWithArgsFormerDefinitionTypes< C, F >; + type End = E; +} + + +/// Manual Former for TestStructWithArgs. +#[ derive( Debug ) ] +#[ allow( dead_code ) ] // Allow dead code for the whole struct as tests might not use all fields +pub struct TestStructWithArgsFormer< D = TestStructWithArgsFormerDefinition > +where + D : FormerDefinition< Storage = TestStructWithArgsFormerStorage >, +{ + /// Former storage. + pub storage : D::Storage, + /// Former context. + pub context : Option< D::Context >, // Warning: field is never read + /// Former end handler. + pub on_end : Option< D::End >, // Warning: field is never read +} + +impl< D > TestStructWithArgsFormer< D > +where + D : FormerDefinition< Storage = TestStructWithArgsFormerStorage >, + D::Types : FormerDefinitionTypes< Storage = TestStructWithArgsFormerStorage >, + D::Types : FormerMutator, +{ + /// Finalizes the forming process. + #[ inline( always ) ] + #[ allow( dead_code ) ] // Warning: method is never used + pub fn form( self ) -> < D::Types as FormerDefinitionTypes >::Formed + { + self.end() + } + + /// Finalizes the forming process. + #[ inline( always ) ] + #[ allow( dead_code ) ] // Warning: method is never used + pub fn end( mut self ) + -> + < D::Types as FormerDefinitionTypes >::Formed + { + let end = self.on_end.take().unwrap(); + < D::Types as FormerMutator >::form_mutation( &mut self.storage, &mut self.context ); + end.call( self.storage, self.context.take() ) + } + + /// Begins the forming process. + #[ inline( always ) ] + pub fn begin + ( + s : Option< D::Storage >, + c : Option< D::Context >, + e : D::End, + ) -> Self + { + Self + { + storage : s.unwrap_or_default(), + context : c, + on_end : Some( e ), + } + } + + /// Creates a new former instance. + #[ inline( always ) ] + #[ allow( dead_code ) ] + pub fn new( e : D::End ) -> Self + { + Self::begin( None, None, e ) + } + + /// Setter for field_a. + #[ inline ] + #[ allow( dead_code ) ] + pub fn field_a( mut self, src : impl Into< String > ) -> Self + { + debug_assert!( self.storage.field_a.is_none() ); + self.storage.field_a = Some( src.into() ); + self + } + + /// Setter for field_b. + #[ inline ] + #[ allow( dead_code ) ] + pub fn field_b( mut self, src : impl Into< bool > ) -> Self + { + debug_assert!( self.storage.field_b.is_none() ); + self.storage.field_b = Some( src.into() ); + self + } + + /// Setter for field_c. + #[ inline ] + #[ allow( dead_code ) ] // Warning: method is never used + pub fn field_c( mut self, src : impl Into< f32 > ) -> Self + { + debug_assert!( self.storage.field_c.is_none() ); + self.storage.field_c = Some( src.into() ); + self + } +} + +// === Standalone Constructor (Manual): With Args === +/// Manual standalone constructor for TestStructWithArgs. +#[ allow( dead_code ) ] // Warning: function is never used +pub fn test_struct_with_args +( + field_a : impl Into< String >, + field_b : impl Into< bool >, +) +-> +TestStructWithArgsFormer< TestStructWithArgsFormerDefinition< (), TestStructWithArgs, ReturnPreformed > > +{ + let initial_storage = TestStructWithArgsFormerStorage + { + field_a : Some( field_a.into() ), + field_b : Some( field_b.into() ), + field_c : None, + }; + TestStructWithArgsFormer::begin( Some( initial_storage ), None, ReturnPreformed ) +} + +// === Include Test Logic === +include!( "standalone_constructor_only_test.rs" ); // Include the single test file \ No newline at end of file diff --git a/module/core/former/tests/inc/former_struct_tests/standalone_constructor_only_test.rs b/module/core/former/tests/inc/former_struct_tests/standalone_constructor_only_test.rs new file mode 100644 index 0000000000..da689d13e0 --- /dev/null +++ b/module/core/former/tests/inc/former_struct_tests/standalone_constructor_only_test.rs @@ -0,0 +1,70 @@ +// module/core/former/tests/inc/former_struct_tests/standalone_constructor_only_test.rs +// +// Contains the shared test logic for standalone constructors. +// This file is included by both the manual and derive test files. +// It uses consistent names defined in the including files. +// + +// Use the items defined in the including file (manual or derive) +use super::*; + +/// Tests the standalone constructor for a struct with no arguments. +#[ test ] +fn no_args_test() // Generic test name +{ + // Call the constructor function (manual or derived) + // Assumes `test_struct_no_args` is defined in the including scope + let former = test_struct_no_args(); + + // Use the former to build the struct + let instance = former + .field1( 42 ) // Set the field using the regular setter + .form(); + + // Define the expected struct instance (using the consistent struct name) + let expected = TestStructNoArgs + { + field1 : 42, + }; + + // Assert that the formed instance matches the expected one + assert_eq!( instance, expected ); +} + +// qqq : Uncomment tests below once arg_for_constructor is implemented for structs // Removed comment block start +/// Tests the standalone constructor for a struct with arguments. +#[ test ] +fn with_args_test() // Generic test name +{ + // Call the constructor function (manual or derived) with arguments + // Assumes `test_struct_with_args` is defined in the including scope + let former = test_struct_with_args( "hello", true ); // Use literal args + + // Use the former to set the remaining optional field and build the struct + let instance = former + .field_c( 3.14 ) // Set the non-constructor field + .form(); + + // Define the expected struct instance (using the consistent struct name) + let expected = TestStructWithArgs + { + field_a : "hello".to_string(), + field_b : true, + field_c : Some( 3.14 ), + }; + + // Assert that the formed instance matches the expected one + assert_eq!( instance, expected ); + + // Test case where the non-constructor field is not set + let former2 = test_struct_with_args( "world", false ); + let instance2 = former2.form(); // field_c remains None + + let expected2 = TestStructWithArgs + { + field_a : "world".to_string(), + field_b : false, + field_c : None, + }; + assert_eq!( instance2, expected2 ); +} \ No newline at end of file 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 97% 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 index 00851f857d..684cc4775a 100644 --- 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 @@ -36,7 +36,7 @@ where K : core::cmp::Eq + std::hash::Hash, { type Item = K; - type IntoIter = collection_tools::hset::IntoIter< K >; + type IntoIter = collection_tools::hash_set::IntoIter< K >; fn into_iter( self ) -> Self::IntoIter { @@ -49,7 +49,7 @@ where K : core::cmp::Eq + std::hash::Hash, { type Item = &'a K; - type IntoIter = collection_tools::hset::Iter< 'a, K >; + type IntoIter = collection_tools::hash_set::Iter< 'a, K >; fn into_iter( self ) -> Self::IntoIter { 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/former_tests/parametrized_dyn.rs b/module/core/former/tests/inc/former_tests/parametrized_dyn.rs deleted file mode 100644 index 813f06f165..0000000000 --- a/module/core/former/tests/inc/former_tests/parametrized_dyn.rs +++ /dev/null @@ -1,405 +0,0 @@ -use super::*; - - -pub trait FilterCol : fmt::Debug -{ - fn filter_col( &self, key : &str ) -> bool; -} - -#[ derive( Debug, Default, PartialEq, Clone, Copy ) ] -pub struct All; - -impl All -{ - pub fn instance() -> & 'static dyn FilterCol - { - static INSTANCE : All = All; - &INSTANCE - } -} - -impl Default for &'static dyn FilterCol -{ - #[ inline( always ) ] - fn default() -> Self - { - All::instance() - } -} - -impl FilterCol for All -{ - #[ inline( always ) ] - fn filter_col( &self, _key : &str ) -> bool - { - true - } -} - -#[ derive( Default ) ] -// #[ derive( former::Former ) ] // xxx : qqq : uncomment and fix problem with lifetime -// #[ derive( former::Former ) ] #[ debug ] -pub struct Styles< 'callback > -{ - - // pub output_format : &'static dyn AsRef< str >, - pub filter : &'callback dyn FilterCol, - -} - -// === begin_coercing of generated - -#[automatically_derived] -impl< 'callback > Styles< 'callback > where -{ - #[doc = r""] - #[doc = r" Provides a mechanism to initiate the formation process with a default completion behavior."] - #[doc = r""] - #[inline(always)] - pub fn former() -> StylesFormer< 'callback, StylesFormerDefinition< 'callback, (), Styles< 'callback >, former::ReturnPreformed > > - { - StylesFormer::< 'callback, StylesFormerDefinition< 'callback, (), Styles< 'callback >, former::ReturnPreformed > >::new_coercing(former::ReturnPreformed) - } -} - -impl< 'callback, Definition > former::EntityToFormer< Definition > for Styles< 'callback > -where - Definition : former::FormerDefinition< Storage = StylesFormerStorage< 'callback > >, -{ - type Former = StylesFormer< 'callback, Definition >; -} - -impl< 'callback > former::EntityToStorage for Styles< 'callback > -where -{ - type Storage = StylesFormerStorage< 'callback >; -} - -impl< 'callback, __Context, __Formed, __End > former::EntityToDefinition< __Context, __Formed, __End > for Styles< 'callback > -where - __End : former::FormingEnd< StylesFormerDefinitionTypes< 'callback, __Context, __Formed > >, -{ - type Definition = StylesFormerDefinition< 'callback, __Context, __Formed, __End >; - type Types = StylesFormerDefinitionTypes< 'callback, __Context, __Formed >; -} - -impl< 'callback, __Context, __Formed > former::EntityToDefinitionTypes< __Context, __Formed > for Styles< 'callback > -where -{ - type Types = StylesFormerDefinitionTypes< 'callback, __Context, __Formed >; -} - -#[doc = r" Defines the generic parameters for formation behavior including context, form, and end conditions."] -#[derive(Debug)] -pub struct StylesFormerDefinitionTypes< 'callback, __Context = (), __Formed = Styles< 'callback > > -where -{ - _phantom: ::core::marker::PhantomData< ( & 'callback (), * const __Context, * const __Formed ) >, -} - -impl< 'callback, __Context, __Formed > ::core::default::Default for StylesFormerDefinitionTypes< 'callback, __Context, __Formed > -where -{ - fn default() -> Self - { - Self { _phantom: ::core::marker::PhantomData } - } -} - -impl< 'callback, __Context, __Formed > former::FormerDefinitionTypes for StylesFormerDefinitionTypes< 'callback, __Context, __Formed > -where -{ - type Storage = StylesFormerStorage< 'callback >; - type Formed = __Formed; - type Context = __Context; -} - -#[doc = r" Holds the definition types used during the formation process."] -#[derive(Debug)] -pub struct StylesFormerDefinition< 'callback, __Context = (), __Formed = Styles< 'callback >, __End = former::ReturnPreformed > -where -{ - _phantom: ::core::marker::PhantomData< ( & 'callback (), * const __Context, * const __Formed, * const __End ) >, -} - -impl< 'callback, __Context, __Formed, __End > ::core::default::Default for StylesFormerDefinition< 'callback, __Context, __Formed, __End > -where -{ - fn default() -> Self - { - Self { _phantom: ::core::marker::PhantomData } - } -} - -impl< 'callback, __Context, __Formed, __End > former::FormerDefinition for StylesFormerDefinition< 'callback, __Context, __Formed, __End > -where - __End : former::FormingEnd< StylesFormerDefinitionTypes< 'callback, __Context, __Formed > >, -{ - type Types = StylesFormerDefinitionTypes< 'callback, __Context, __Formed >; - type End = __End; - type Storage = StylesFormerStorage< 'callback >; - type Formed = __Formed; - type Context = __Context; -} - -impl< 'callback, __Context, __Formed > former::FormerMutator for StylesFormerDefinitionTypes< 'callback, __Context, __Formed > -where -{} - -#[doc = "Stores potential values for fields during the formation process."] -#[allow(explicit_outlives_requirements)] -pub struct StylesFormerStorage< 'callback > -where -{ - #[doc = r" A field"] - pub filter: ::core::option::Option< & 'callback dyn FilterCol >, -} - -impl< 'callback > ::core::default::Default for StylesFormerStorage< 'callback > -where -{ - #[inline(always)] - fn default() -> Self - { - Self { filter: ::core::option::Option::None } - } -} - -impl< 'callback > former::Storage for StylesFormerStorage< 'callback > -where -{ - type Preformed = Styles< 'callback >; -} - -impl< 'callback > former::StoragePreform for StylesFormerStorage< 'callback > -where -{ - fn preform(mut self) -> Self::Preformed - { - let filter = if self.filter.is_some() - { - self.filter.take().unwrap() - } - else - { - { - trait MaybeDefault - { - fn maybe_default(self: &Self) -> T - { - panic!("Field 'filter' isn't initialized") - } - } - impl MaybeDefault for &::core::marker::PhantomData - {} - impl MaybeDefault for ::core::marker::PhantomData - where - T: ::core::default::Default, - { - fn maybe_default(self: &Self) -> T - { - T::default() - } - } - (&::core::marker::PhantomData::<&'callback dyn FilterCol>).maybe_default() - } - }; - let result = Styles::< 'callback > { filter }; - return result; - } -} - -#[doc = "\nStructure to form [Styles]. Represents a forming entity designed to construct objects through a builder pattern.\n\nThis structure holds temporary storage and context during the formation process and\nutilizes a defined end strategy to finalize the object creation.\n"] -pub struct StylesFormer< 'callback, Definition = StylesFormerDefinition< 'callback, (), Styles< 'callback >, former::ReturnPreformed > > -where - Definition: former::FormerDefinition< Storage = StylesFormerStorage< 'callback > >, - Definition::Types: former::FormerDefinitionTypes< Storage = StylesFormerStorage< 'callback > >, -{ - #[doc = r" Temporary storage for all fields during the formation process. It contains"] - #[doc = r" partial data that progressively builds up to the final object."] - pub storage: Definition::Storage, - #[doc = r" An optional context providing additional data or state necessary for custom"] - #[doc = r" formation logic or to facilitate this former's role as a subformer within another former."] - pub context: ::core::option::Option< Definition::Context >, - #[doc = r" An optional closure or handler that is invoked to transform the accumulated"] - #[doc = r" temporary storage into the final object structure once formation is complete."] - pub on_end: ::core::option::Option< Definition::End >, -} - -#[automatically_derived] -impl< 'callback, Definition > StylesFormer< 'callback, Definition > -where - Definition: former::FormerDefinition< Storage = StylesFormerStorage< 'callback > >, - Definition::Types: former::FormerDefinitionTypes< Storage = StylesFormerStorage< 'callback > >, -{ - #[doc = r""] - #[doc = r" Initializes a former with an end condition and default storage."] - #[doc = r""] - #[inline(always)] - pub fn new(on_end: Definition::End) -> Self - { - Self::begin_coercing(::core::option::Option::None, ::core::option::Option::None, on_end) - } - - #[doc = r""] - #[doc = r" Initializes a former with a coercible end condition."] - #[doc = r""] - #[inline(always)] - pub fn new_coercing(end: IntoEnd) -> Self - where - IntoEnd: ::core::convert::Into, - { - Self::begin_coercing(::core::option::Option::None, ::core::option::Option::None, end) - } - - #[doc = r""] - #[doc = r" Begins the formation process with specified context and termination logic."] - #[doc = r""] - #[inline(always)] - pub fn begin( - mut storage: ::core::option::Option, - context: ::core::option::Option, - on_end: ::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), - } - } - - #[doc = r""] - #[doc = r" Starts the formation process with coercible end condition and optional initial values."] - #[doc = r""] - #[inline(always)] - pub fn begin_coercing( - mut storage: ::core::option::Option, - context: ::core::option::Option, - on_end: IntoEnd, - ) -> Self - where - IntoEnd: ::core::convert::Into<::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)), - } - } - - #[doc = r""] - #[doc = r" Wrapper for `end` to align with common builder pattern terminologies."] - #[doc = r""] - #[inline(always)] - pub fn form(self) -> ::Formed - { - self.end() - } - - #[doc = r""] - #[doc = r" Completes the formation and returns the formed object."] - #[doc = r""] - #[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) - } - - #[doc = "Scalar setter for the 'filter' field."] - #[inline] - pub fn filter(mut self, src: Src) -> Self - where - Src: ::core::convert::Into<& 'callback dyn FilterCol>, - { - debug_assert!(self.storage.filter.is_none()); - self.storage.filter = ::core::option::Option::Some(::core::convert::Into::into(src)); - self - } -} - -impl< 'callback, Definition > StylesFormer< 'callback, Definition > -where - Definition: former::FormerDefinition< Storage = StylesFormerStorage< 'callback >, Formed = Styles< 'callback > >, - Definition::Types: former::FormerDefinitionTypes< Storage = StylesFormerStorage< 'callback >, Formed = Styles< 'callback > >, - Definition: former::FormerDefinition< Storage = StylesFormerStorage< 'callback > >, - Definition::Types: former::FormerDefinitionTypes< Storage = StylesFormerStorage< 'callback > >, -{ - #[doc = r" Executes the transformation from the former's storage state to the preformed object as specified by the definition."] - pub fn preform(self) -> ::Formed - { - former::StoragePreform::preform(self.storage) - } -} - -#[automatically_derived] -impl< 'callback, Definition > StylesFormer< 'callback, Definition > -where - Definition: former::FormerDefinition< Storage = StylesFormerStorage< 'callback >, Formed = Styles< 'callback > >, - Definition::Types: former::FormerDefinitionTypes< Storage = StylesFormerStorage< 'callback >, Formed = Styles< 'callback > >, -{ - #[doc = r""] - #[doc = r" Finish setting options and call perform on formed entity."] - #[doc = r""] - #[doc = r" If `perform` defined then associated method is called and its result returned instead of entity."] - #[doc = r" For example `perform()` of structure with : `#[ perform( fn after1() -> &str > )` returns `&str`."] - #[doc = r""] - #[inline(always)] - pub fn perform(self) -> Definition::Formed - { - let result = self.form(); - return result; - } -} - -impl< 'callback, Definition > former::FormerBegin< Definition > for StylesFormer< 'callback, Definition > -where - Definition: former::FormerDefinition< Storage = StylesFormerStorage< 'callback > >, -{ - #[inline(always)] - fn former_begin( - storage: ::core::option::Option, - context: ::core::option::Option, - on_end: Definition::End, - ) -> Self - { - debug_assert!(storage.is_none()); - Self::begin(::core::option::Option::None, context, on_end) - } -} - -#[doc = r" Provides a specialized former for structure using predefined settings for superformer and end conditions."] -#[doc = r""] -#[doc = r" This type alias configures former of the structure with a specific definition to streamline its usage in broader contexts,"] -#[doc = r" especially where structure needs to be integrated into larger structures with a clear termination condition."] -pub type StylesAsSubformer< 'callback, __Superformer, __End > = StylesFormer< 'callback, StylesFormerDefinition< 'callback, __Superformer, __Superformer, __End > >; - -#[doc = "\nRepresents an end condition for former of [`$Styles`], tying the lifecycle of forming processes to a broader context.\n\nThis trait is intended for use with subformer alias, ensuring that end conditions are met according to the\nspecific needs of the broader forming context. It mandates the implementation of `former::FormingEnd`.\n "] -pub trait StylesAsSubformerEnd< 'callback, SuperFormer > -where - Self: former::FormingEnd< StylesFormerDefinitionTypes< 'callback, SuperFormer, SuperFormer > >, -{} -impl< 'callback, SuperFormer, __T > StylesAsSubformerEnd< 'callback, SuperFormer > for __T -where - Self: former::FormingEnd< StylesFormerDefinitionTypes< 'callback, SuperFormer, SuperFormer > >, -{} - -// === end of generated - -#[ test ] -fn basic() -{ -} \ No newline at end of file diff --git a/module/core/former/tests/inc/mod.rs b/module/core/former/tests/inc/mod.rs index d259269d35..2429e28433 100644 --- a/module/core/former/tests/inc/mod.rs +++ b/module/core/former/tests/inc/mod.rs @@ -1,12 +1,82 @@ -// #![ 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 -#[ allow( unused_imports ) ] use super::*; +use test_tools::exposed::*; #[ cfg( feature = "derive_former" ) ] -mod former_tests +mod former_struct_tests { - #[ allow( unused_imports ) ] use super::*; // = basic @@ -46,14 +116,12 @@ 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 - mod parametrized_dyn; + mod parametrized_dyn; // xxx2 : qqq2 : fix the issue #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] mod parametrized_struct_manual; @@ -151,6 +219,62 @@ mod former_tests #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] mod subform_all_parametrized; + // = standalone constructor + + mod standalone_constructor_manual; + mod standalone_constructor_derive; + +} + +#[ 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; + + // = standalone constructor + + mod standalone_constructor_manual; + mod standalone_constructor_derive; + mod standalone_constructor_args_manual; + mod standalone_constructor_args_derive; + + // = subform + + mod subform_collection_test; + } #[ cfg( feature = "derive_components" ) ] @@ -162,21 +286,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; @@ -200,10 +340,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 ); } @@ -222,4 +364,4 @@ only_for_terminal_module! } -} +} \ No newline at end of file diff --git a/module/core/former/tests/smoke_test.rs b/module/core/former/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/core/former/tests/smoke_test.rs +++ b/module/core/former/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/core/former/tests/tests.rs b/module/core/former/tests/tests.rs index fe0db783b8..5796f74d30 100644 --- a/module/core/former/tests/tests.rs +++ b/module/core/former/tests/tests.rs @@ -1,9 +1,8 @@ +//! All tests. +#![ allow( unused_imports ) ] include!( "../../../../module/step/meta/src/module/terminal.rs" ); -#[ allow( unused_imports ) ] -use test_tools::exposed::*; -#[ allow( unused_imports ) ] use former as the_module; #[ cfg( feature = "enabled" ) ] diff --git a/module/core/former_meta/Cargo.toml b/module/core/former_meta/Cargo.toml index 537a170711..0c7a9aae27 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.8.0" +version = "2.16.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -24,6 +24,9 @@ workspace = true features = [ "full" ] all-features = false +[lib] +proc-macro = true + [features] default = [ @@ -47,14 +50,11 @@ derive_components_assign = [ "derive_component_assign", "convert_case" ] derive_component_from = [] derive_from_components = [] -[lib] -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 = [] } [dev-dependencies] -test_tools = { workspace = true, features = [ "full" ] } +test_tools = { workspace = true } diff --git a/module/core/former_meta/License b/module/core/former_meta/License index e3e9e057cf..a23529f45b 100644 --- a/module/core/former_meta/License +++ b/module/core/former_meta/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/core/former_meta/Readme.md index 716940ba96..1fa3cb805f 100644 --- a/module/core/former_meta/Readme.md +++ b/module/core/former_meta/Readme.md @@ -1,13 +1,13 @@ -# Module :: former_meta +# Module :: `former_meta` [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_former_meta_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_former_meta_push.yml) [![docs.rs](https://img.shields.io/docsrs/former_meta?color=e3e8f0&logo=docs.rs)](https://docs.rs/former_meta) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) -A flexible implementation of the Builder pattern supporting nested builders and collection-specific subformers. Implementation of its derive macro. Should not be used independently, instead use module::former which relies on the module. +A flexible implementation of the Builder pattern supporting nested builders and collection-specific subformers. Implementation of its derive macro. Should not be used independently, instead use `module::former` which relies on the module. -Not intended to be used without runtime. This module and runtime is aggregate in module::former is [here](https://github.com/Wandalen/wTools/tree/master/module/core/former). +Not intended to be used without runtime. This module and runtime is aggregate in `module::former` is [here](https://github.com/Wandalen/wTools/tree/master/module/core/former). ### To add to your project diff --git a/module/core/former_meta/src/component/component_assign.rs b/module/core/former_meta/src/component/component_assign.rs index de12fc7f5f..a9b9776fd2 100644 --- a/module/core/former_meta/src/component/component_assign.rs +++ b/module/core/former_meta/src/component/component_assign.rs @@ -1,5 +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. @@ -11,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 @@ -41,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 >, @@ -73,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 c5613a48fa..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,39 +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/components_assign.rs b/module/core/former_meta/src/component/components_assign.rs index 6b495e7629..3ad19ae423 100644 --- a/module/core/former_meta/src/component/components_assign.rs +++ b/module/core/former_meta/src/component/components_assign.rs @@ -1,13 +1,13 @@ +#[ allow( clippy::wildcard_imports ) ] use super::*; use macro_tools::{ attr, diag, Result, format_ident }; -use iter_tools::{ Itertools }; +use iter_tools::Itertools; /// /// Generate `ComponentsAssign` trait implementation for the type, providing `components_assign` function /// /// Output example can be found in in the root of the module /// - pub fn components_assign( input : proc_macro::TokenStream ) -> Result< proc_macro2::TokenStream > { use convert_case::{ Case, Casing }; @@ -44,7 +44,7 @@ pub fn components_assign( input : proc_macro::TokenStream ) -> Result< proc_macr let component_assigns : Vec< _ > = component_assigns.into_iter().collect::< Result< _ > >()?; // code - let doc = format!( "Interface to assign instance from set of components exposed by a single argument." ); + let doc = "Interface to assign instance from set of components exposed by a single argument.".to_string(); let trait_bounds = qt! { #( #bounds1 )* IntoT : Clone }; let impl_bounds = qt! { #( #bounds2 )* #( #bounds1 )* IntoT : Clone }; let component_assigns = qt! { #( #component_assigns )* }; @@ -75,7 +75,7 @@ pub fn components_assign( input : proc_macro::TokenStream ) -> Result< proc_macr if has_debug { - let about = format!( "derive : ComponentsAssign\nstructure : {0}", item_name ); + let about = format!( "derive : ComponentsAssign\nstructure : {item_name}" ); diag::report_print( about, &original_input, &result ); } @@ -96,6 +96,7 @@ pub fn components_assign( input : proc_macro::TokenStream ) -> Result< proc_macr /// IntoT : Into< i32 > /// ``` /// +#[ allow( clippy::unnecessary_wraps ) ] fn generate_trait_bounds( field_type : &syn::Type ) -> Result< proc_macro2::TokenStream > { Ok @@ -116,6 +117,7 @@ fn generate_trait_bounds( field_type : &syn::Type ) -> Result< proc_macro2::Toke /// T : former::Assign< i32, IntoT >, /// ``` /// +#[ allow( clippy::unnecessary_wraps ) ] fn generate_impl_bounds( field_type : &syn::Type ) -> Result< proc_macro2::TokenStream > { Ok @@ -137,6 +139,7 @@ fn generate_impl_bounds( field_type : &syn::Type ) -> Result< proc_macro2::Token /// former::Assign::< i32, _ >::assign( self.component.clone() ); /// ``` /// +#[ allow( clippy::unnecessary_wraps ) ] fn generate_component_assign_call( field : &syn::Field ) -> Result< proc_macro2::TokenStream > { // let field_name = field.ident.as_ref().expect( "Expected the field to have a name" ); diff --git a/module/core/former_meta/src/component/from_components.rs b/module/core/former_meta/src/component/from_components.rs index d76029ca0a..0357a81ddb 100644 --- a/module/core/former_meta/src/component/from_components.rs +++ b/module/core/former_meta/src/component/from_components.rs @@ -1,5 +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 @@ -8,30 +15,25 @@ 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 /// } /// } /// ``` /// - #[ inline ] pub fn from_components( input : proc_macro::TokenStream ) -> Result< proc_macro2::TokenStream > { @@ -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,32 +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.into() ) + 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 | @@ -110,27 +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! { @@ -138,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 b4d163608e..1299b73748 100644 --- a/module/core/former_meta/src/derive_former.rs +++ b/module/core/former_meta/src/derive_former.rs @@ -1,46 +1,31 @@ - +// 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 ) ] use field_attrs::*; mod field; +#[ allow( clippy::wildcard_imports ) ] use field::*; mod struct_attrs; +#[ allow( clippy::wildcard_imports ) ] 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 ( item : &syn::Ident, @@ -55,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 > @@ -70,11 +57,12 @@ pub fn mutator } }; + // If debug is enabled for the mutator attribute, print a helpful example. if mutator.debug.value( false ) { let debug = format! ( - r#" + r" = Example of custom mutator impl< {} > former::FormerMutator @@ -84,696 +72,88 @@ 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 -item : {item}"#, +r"derive : Former +item : {item}", ); diag::report_print( about, original_input, debug ); - }; + } 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 ) { let doc_former_mod = format! ( -r#" Implementation of former for [{}]. -"#, - item +r" Implementation of former for [{item}]. +" ); let doc_former_struct = format! ( -r#" -Structure to form [{}]. Represents a forming entity designed to construct objects through a builder pattern. +r" +Structure to form [{item}]. Represents a forming entity designed to construct objects through a builder pattern. This structure holds temporary storage and context during the formation process and utilizes a defined end strategy to finalize the object creation. -"#, - item +" ); ( 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 = match syn::parse::< syn::DeriveInput >( input ) - { - Ok( syntax_tree ) => syntax_tree, - Err( err ) => return Err( err ), - }; + 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.into() ).args; - - /* parameters for former */ - - let extra : macro_tools::GenericsWithWhere = parse_quote! - { - < Definition = #former_definition < #former_definition_args > > - where - Definition : former::FormerDefinition< Storage = #former_storage < #struct_generics_ty > >, - Definition::Types : former::FormerDefinitionTypes< Storage = #former_storage < #struct_generics_ty > >, - }; - let extra = generic_params::merge( &generics, &extra.into() ); - - let ( former_generics_with_defaults, former_generics_impl, former_generics_ty, former_generics_where ) - = generic_params::decompose( &extra ); - - /* parameters for former perform */ - - let extra : macro_tools::GenericsWithWhere = parse_quote! - { - < Definition = #former_definition < #former_definition_args > > - where - Definition : former::FormerDefinition - < - Storage = #former_storage < #struct_generics_ty >, - Formed = #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 | + // Dispatch based on whether the input is a struct, enum, or union. + let result = match ast.data { - 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 - { - Self - { - _phantom : ::core::marker::PhantomData, - } - } - } - - impl < #former_definition_generics_impl > former::FormerDefinition - for #former_definition < #former_definition_generics_ty > - where - __End : former::FormingEnd< #former_definition_types < #former_definition_types_generics_ty > >, - #former_definition_generics_where - { - type Types = #former_definition_types < #former_definition_types_generics_ty >; - type End = __End; - type Storage = #former_storage < #struct_generics_ty >; - type Formed = __Formed; - type Context = __Context; - } - - // = former mutator - - #former_mutator_code - - // = storage - - #[ doc = "Stores potential values for fields during the formation process." ] - #[ allow( explicit_outlives_requirements ) ] - #vis struct #former_storage < #struct_generics_with_defaults > - where - #struct_generics_where - { - #( - /// A field - #storage_field_optional, - )* - } - - impl < #struct_generics_impl > ::core::default::Default - for #former_storage < #struct_generics_ty > - where - #struct_generics_where - { - - #[ inline( always ) ] - fn default() -> Self - { - Self - { - #( #storage_field_none, )* - } - } - - } - - impl < #struct_generics_impl > former::Storage - for #former_storage < #struct_generics_ty > - where - #struct_generics_where - { - type Preformed = #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 + former_for_enum( &ast, data_enum, &original_input, has_debug ) + }, + syn::Data::Union( _ ) => { - let result = self.form(); - #perform + // Unions are not supported. + Err( syn::Error::new( ast.span(), "Former derive does not support unions" ) ) } + }?; - } - - // = 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 089470ea84..dafc324500 100644 --- a/module/core/former_meta/src/derive_former/field.rs +++ b/module/core/former_meta/src/derive_former/field.rs @@ -1,11 +1,11 @@ - +// File: module/core/former_meta/src/derive_former/field.rs +#[ allow( clippy::wildcard_imports ) ] use super::*; -use macro_tools::{ container_kind }; +use macro_tools::container_kind; /// /// Definition of a field. /// - #[ allow( dead_code ) ] pub struct FormerField< 'a > { @@ -26,25 +26,24 @@ impl< 'a > FormerField< 'a > /** methods -from_syn +`from_syn` -storage_fields_none -storage_field_optional -storage_field_preform -storage_field_name -former_field_setter -scalar_setter -subform_entry_setter -subform_collection_setter +`storage_fields_none` +`storage_field_optional` +`storage_field_preform` +`storage_field_name` +`former_field_setter` +`scalar_setter` +`subform_entry_setter` +`subform_collection_setter` -scalar_setter_name -subform_scalar_setter_name, -subform_collection_setter_name -subform_entry_setter_name -scalar_setter_required +`scalar_setter_name` +`subform_scalar_setter_name`, +`subform_collection_setter_name` +`subform_entry_setter_name` +`scalar_setter_required` */ - /// Construct former field from [`syn::Field`] pub fn from_syn( field : &'a syn::Field, for_storage : bool, for_formed : bool ) -> Result< Self > { @@ -86,7 +85,6 @@ scalar_setter_required /// int_optional_1 : core::option::Option::None, /// ``` /// - #[ inline( always ) ] pub fn storage_fields_none( &self ) -> TokenStream { @@ -114,7 +112,6 @@ scalar_setter_required /// pub string_optional_1 : core::option::Option< String >, /// ``` /// - #[ inline( always ) ] pub fn storage_field_optional( &self ) -> TokenStream { @@ -171,8 +168,8 @@ scalar_setter_required /// }; /// ``` /// - #[ inline( always ) ] + #[ allow( clippy::unnecessary_wraps ) ] pub fn storage_field_preform( &self ) -> Result< TokenStream > { @@ -228,7 +225,7 @@ scalar_setter_required { None => { - let panic_msg = format!( "Field '{}' isn't initialized", ident ); + let panic_msg = format!( "Field '{ident}' isn't initialized" ); qt! { { @@ -288,7 +285,6 @@ scalar_setter_required /// /// Extract name of a field out. /// - #[ inline( always ) ] pub fn storage_field_name( &self ) -> TokenStream { @@ -325,8 +321,8 @@ scalar_setter_required /// - **Subform Setters**: Generated for fields annotated as subforms, allowing for nested /// forming processes where a field itself can be formed using a dedicated former. /// - #[ inline ] + #[ allow( clippy::too_many_arguments ) ] pub fn former_field_setter ( &self, @@ -376,7 +372,7 @@ scalar_setter_required }; // subform collection setter - let ( setters_code, namespace_code ) = if let Some( _ ) = &self.attrs.subform_collection + let ( setters_code, namespace_code ) = if self.attrs.subform_collection.is_some() { let ( setters_code2, namespace_code2 ) = self.subform_collection_setter ( @@ -421,9 +417,9 @@ scalar_setter_required } /// - /// Generate a single scalar setter for the 'field_ident' with the 'setter_name' name. + /// Generate a single scalar setter for the '`field_ident`' with the '`setter_name`' name. /// - /// Used as a helper function for former_field_setter(), which generates alias setters + /// Used as a helper function for `former_field_setter()`, which generates alias setters /// /// # Example of generated code /// @@ -439,8 +435,8 @@ scalar_setter_required /// self /// } /// ``` - #[ inline ] + #[ allow( clippy::format_in_format_args ) ] pub fn scalar_setter ( &self, @@ -460,7 +456,7 @@ scalar_setter_required { let debug = format! ( - r#" + r" impl< Definition > {former}< Definition > where Definition : former::FormerDefinition< Storage = {former_storage} >, @@ -475,14 +471,14 @@ where self }} }} - "#, + ", format!( "{}", qt!{ #typ } ), ); let about = format! ( -r#"derive : Former +r"derive : Former item : {item} -field : {field_ident}"#, +field : {field_ident}", ); diag::report_print( about, original_input, debug ); } @@ -494,8 +490,7 @@ field : {field_ident}"#, let doc = format! ( - "Scalar setter for the '{}' field.", - field_ident, + "Scalar setter for the '{field_ident}' field.", ); qt! @@ -515,12 +510,12 @@ field : {field_ident}"#, } /// - /// Generate a collection setter for the 'field_ident' with the 'setter_name' name. + /// Generate a collection setter for the '`field_ident`' with the '`setter_name`' name. /// /// See `tests/inc/former_tests/subform_collection_manual.rs` for example of generated code. /// - #[ inline ] + #[ allow( clippy::too_many_lines, clippy::too_many_arguments ) ] pub fn subform_collection_setter ( &self, @@ -537,16 +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` @@ -584,10 +586,7 @@ field : {field_ident}"#, let doc = format! ( - "Collection setter for the '{}' field. Method {} unlike method {} accept custom collection subformer.", - field_ident, - subform_collection, - field_ident, + "Collection setter for the '{field_ident}' field. Method {subform_collection} unlike method {field_ident} accept custom collection subformer." ); let setter1 = @@ -701,7 +700,7 @@ field : {field_ident}"#, { let debug = format! ( - r#" + r" /// The collection setter provides a collection setter that returns a CollectionFormer tailored for managing a collection of child entities. It employs a generic collection definition to facilitate operations on the entire collection, such as adding or updating elements. impl< Definition, > {former}< Definition, > @@ -721,14 +720,14 @@ where }} }} - "#, + ", format!( "{}", qt!{ #( #params, )* } ), ); let about = format! ( -r#"derive : Former +r"derive : Former item : {item} -field : {field_ident}"#, +field : {field_ident}", ); diag::report_print( about, original_input, debug ); } @@ -744,17 +743,17 @@ field : {field_ident}"#, let subform_collection_end_doc = format! ( - r#" + r" A callback structure to manage the final stage of forming a `{0}` for the `{item}` collection. This callback is used to integrate the contents of a temporary `{0}` back into the original `{item}` former after the subforming process is completed. It replaces the existing content of the `{field_ident}` field in `{item}` with the new content generated during the subforming process. - "#, + ", format!( "{}", qt!{ #field_typ } ), ); - let subformer_definition_types = if let Some( ref _subformer_definition ) = subformer_definition + let subformer_definition_types = if let Some( _subformer_definition ) = subformer_definition { let subformer_definition_types_string = format!( "{}Types", qt!{ #subformer_definition } ); let subformer_definition_types : syn::Type = syn::parse_str( &subformer_definition_types_string )?; @@ -854,8 +853,8 @@ with the new content generated during the subforming process. /// /// See `tests/inc/former_tests/subform_entry_manual.rs` for example of generated code. /// - #[ inline ] + #[ allow( clippy::format_in_format_args, clippy::too_many_lines, clippy::too_many_arguments ) ] pub fn subform_entry_setter ( &self, @@ -882,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` @@ -899,7 +904,7 @@ with the new content generated during the subforming process. let doc = format! ( - r#" + r" Initiates the addition of {field_ident} to the `{item}` entity using a dedicated subformer. @@ -913,7 +918,7 @@ parent's structure once formed. Returns an instance of `Former2`, a subformer ready to begin the formation process for `{0}` entities, allowing for dynamic and flexible construction of the `{item}` entity's {field_ident}. - "#, + ", format!( "{}", qt!{ #field_typ } ), ); @@ -954,7 +959,7 @@ allowing for dynamic and flexible construction of the `{item}` entity's {field_i let doc = format! ( - r#" + r" Provides a user-friendly interface to add an instancce of {field_ident} to the {item}. # Returns @@ -962,7 +967,7 @@ Provides a user-friendly interface to add an instancce of {field_ident} to the { Returns an instance of `Former2`, a subformer ready to begin the formation process for `{0}` entities, allowing for dynamic and flexible construction of the `{item}` entity's {field_ident}. - "#, + ", format!( "{}", qt!{ #field_typ } ), ); @@ -1006,7 +1011,7 @@ allowing for dynamic and flexible construction of the `{item}` entity's {field_i { let debug = format! ( - r#" + r" /// Initializes and configures a subformer for adding named child entities. This method leverages an internal function /// to create and return a configured subformer instance. It allows for the dynamic addition of children with specific names, /// integrating them into the formation process of the parent entity. @@ -1024,21 +1029,21 @@ where // Replace {0} with name of type of entry value. }} - "#, + ", format!( "{}", qt!{ #entry_typ } ), ); let about = format! ( -r#"derive : Former +r"derive : Former item : {item} -field : {field_ident}"#, +field : {field_ident}", ); diag::report_print( about, original_input, debug ); } let doc = format! ( - r#" + r" Implements the `FormingEnd` trait for `{subform_entry_end}` to handle the final stage of the forming process for a `{item}` collection that contains `{0}` elements. @@ -1066,7 +1071,7 @@ preformed elements to this storage. Returns the updated `{former}` instance with newly added {field_ident}, completing the formation process of the `{item}`. - "#, + ", format!( "{}", qt!{ #field_typ } ), ); @@ -1144,8 +1149,8 @@ formation process of the `{item}`. /// Generates setter functions to subform scalar and all corresponding helpers. /// /// See `tests/inc/former_tests/subform_scalar_manual.rs` for example of generated code. - #[ inline ] + #[ allow( clippy::format_in_format_args, clippy::unnecessary_wraps, clippy::too_many_lines, clippy::too_many_arguments ) ] pub fn subform_scalar_setter ( &self, @@ -1170,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` @@ -1187,7 +1198,7 @@ formation process of the `{item}`. let doc = format! ( - r#" + r" Initiates the scalar subformer for a `{0}` entity within a `{item}`. @@ -1211,7 +1222,7 @@ is properly initialized with all necessary configurations, including the default This function is typically called internally by a more user-friendly method that abstracts away the complex generics, providing a cleaner interface for initiating subform operations on scalar fields. - "#, + ", format!( "{}", qt!{ #field_typ } ), ); @@ -1275,7 +1286,7 @@ generics, providing a cleaner interface for initiating subform operations on sca let doc = format! ( - r#" + r" Provides a user-friendly interface to begin subforming a scalar `{0}` field within a `{item}`. This method abstracts the underlying complex generics involved in setting up the former, simplifying the @@ -1285,7 +1296,7 @@ This method utilizes the more generic `{subform_scalar}` method to set up and re providing a straightforward and type-safe interface for client code. It encapsulates details about the specific former and end action types, ensuring a seamless developer experience when forming parts of a `{item}`. - "#, + ", format!( "{}", qt!{ #field_typ } ), ); @@ -1328,7 +1339,7 @@ former and end action types, ensuring a seamless developer experience when formi { let debug = format! ( - r#" + r" /// Extends `{former}` to include a method that initializes and configures a subformer for the '{field_ident}' field. /// This function demonstrates the dynamic addition of a named {field_ident}, leveraging a subformer to specify detailed properties. @@ -1342,21 +1353,21 @@ where self._{field_ident}_subform_scalar::< {0}Former< _ >, _, >().name( name ) }} }} - "#, + ", format!( "{}", qt!{ #field_typ } ), ); let about = format! ( -r#"derive : Former +r"derive : Former item : {item} -field : {field_ident}"#, +field : {field_ident}", ); diag::report_print( about, original_input, debug ); } let doc = format! ( - r#" + r" Represents the endpoint for the forming process of a scalar field managed by a subformer within a `{item}` entity. @@ -1374,7 +1385,7 @@ Essentially, this end action integrates the individually formed scalar value bac - `super_former`: An optional context of the `{former}`, which will receive the value. The function ensures that this context is not `None` and inserts the formed value into the designated field within `{item}`'s storage. - "#, + ", format!( "{}", qt!{ #field_typ } ), ); @@ -1490,12 +1501,12 @@ Essentially, this end action integrates the individually formed scalar value bac { if let Some( ref attr ) = self.attrs.scalar { - if let Some( ref name ) = attr.name.ref_internal() + if let Some( name ) = attr.name.ref_internal() { return name } } - return &self.ident; + self.ident } /// Get name of setter for subform scalar if such setter should be generated. @@ -1505,17 +1516,14 @@ Essentially, this end action integrates the individually formed scalar value bac { if attr.setter() { - if let Some( ref name ) = attr.name.ref_internal() - { - return Some( &name ) - } - else + if let Some( name ) = attr.name.ref_internal() { - return Some( &self.ident ) + return Some( name ) } + return Some( self.ident ) } } - return None; + None } /// Get name of setter for collection if such setter should be generated. @@ -1525,17 +1533,14 @@ Essentially, this end action integrates the individually formed scalar value bac { if attr.setter() { - if let Some( ref name ) = attr.name.ref_internal() - { - return Some( &name ) - } - else + if let Some( name ) = attr.name.ref_internal() { - return Some( &self.ident ) + return Some( name ) } + return Some( self.ident ) } } - return None; + None } /// Get name of setter for subform if such setter should be generated. @@ -1547,15 +1552,12 @@ Essentially, this end action integrates the individually formed scalar value bac { if let Some( ref name ) = attr.name.as_ref() { - return Some( &name ) - } - else - { - return Some( &self.ident ) + return Some( name ) } + return Some( self.ident ) } } - return None; + None } /// Is scalar setter required. Does not if collection of subformer setter requested. @@ -1567,13 +1569,13 @@ Essentially, this end action integrates the individually formed scalar value bac { if let Some( setter ) = attr.setter.internal() { - if setter == false + if !setter { return false } explicit = true; } - if let Some( ref _name ) = attr.name.ref_internal() + if let Some( _name ) = attr.name.ref_internal() { explicit = true; } @@ -1594,7 +1596,7 @@ Essentially, this end action integrates the individually formed scalar value bac return false; } - return true; + true } -} +} \ No newline at end of file diff --git a/module/core/former_meta/src/derive_former/field_attrs.rs b/module/core/former_meta/src/derive_former/field_attrs.rs index a662fe20ae..d5e02f70ed 100644 --- a/module/core/former_meta/src/derive_former/field_attrs.rs +++ b/module/core/former_meta/src/derive_former/field_attrs.rs @@ -1,5 +1,5 @@ //! Attributes of a field. - +#[ allow( clippy::wildcard_imports ) ] use super::*; use macro_tools:: { @@ -34,6 +34,9 @@ pub struct FieldAttributes /// Subform entry setter attribute for a field. pub subform_entry : Option< AttributeSubformEntrySetter >, + + /// Marks a field as a required argument for standalone constructors. + pub arg_for_constructor : AttributePropertyArgForConstructor, } impl FieldAttributes @@ -59,13 +62,14 @@ impl FieldAttributes // Known attributes for error reporting let known_attributes = ct::concatcp! ( - "Known attributes are : ", - "debug", + "Known field attributes are : ", + "debug", // Assuming debug might be handled elsewhere ", ", AttributeConfig::KEYWORD, ", ", AttributeScalarSetter::KEYWORD, ", ", AttributeSubformScalarSetter::KEYWORD, ", ", AttributeSubformCollectionSetter::KEYWORD, ", ", AttributeSubformEntrySetter::KEYWORD, + ", ", AttributePropertyArgForConstructor::KEYWORD, ".", ); @@ -85,14 +89,13 @@ impl FieldAttributes { // Get the attribute key as a string let key_ident = attr.path().get_ident().ok_or_else( || error( attr ) )?; - let key_str = format!( "{}", key_ident ); + let key_str = format!( "{key_ident}" ); - // // Skip standard attributes + // attributes does not have to be known // if attr::is_standard( &key_str ) // { // continue; // } - // attributes does not have to be known // Match the attribute key and assign to the appropriate field match key_str.as_ref() @@ -102,10 +105,10 @@ impl FieldAttributes AttributeSubformScalarSetter::KEYWORD => result.assign( AttributeSubformScalarSetter::from_meta( attr )? ), AttributeSubformCollectionSetter::KEYWORD => result.assign( AttributeSubformCollectionSetter::from_meta( attr )? ), AttributeSubformEntrySetter::KEYWORD => result.assign( AttributeSubformEntrySetter::from_meta( attr )? ), - "debug" => {}, - _ => {}, + AttributePropertyArgForConstructor::KEYWORD => result.assign( AttributePropertyArgForConstructor::from( true ) ), + // "debug" => {}, // Assuming debug is handled elsewhere or implicitly + _ => {}, // Allow unknown attributes // _ => return Err( error( attr ) ), - // attributes does not have to be known } } @@ -114,6 +117,81 @@ impl FieldAttributes } +// = Assign implementations for FieldAttributes = + +impl< IntoT > Assign< AttributeConfig, IntoT > for FieldAttributes +where + IntoT : Into< AttributeConfig >, +{ + #[ inline( always ) ] + fn assign( &mut self, component : IntoT ) + { + let component : AttributeConfig = component.into(); + self.config.option_assign( component ); + } +} + +impl< IntoT > Assign< AttributeScalarSetter, IntoT > for FieldAttributes +where + IntoT : Into< AttributeScalarSetter >, +{ + #[ inline( always ) ] + fn assign( &mut self, component : IntoT ) + { + let component = component.into(); + self.scalar.option_assign( component ); + } +} + +impl< IntoT > Assign< AttributeSubformScalarSetter, IntoT > for FieldAttributes +where + IntoT : Into< AttributeSubformScalarSetter >, +{ + #[ inline( always ) ] + fn assign( &mut self, component : IntoT ) + { + let component = component.into(); + self.subform_scalar.option_assign( component ); + } +} + +impl< IntoT > Assign< AttributeSubformCollectionSetter, IntoT > for FieldAttributes +where + IntoT : Into< AttributeSubformCollectionSetter >, +{ + #[ inline( always ) ] + fn assign( &mut self, component : IntoT ) + { + let component = component.into(); + self.subform_collection.option_assign( component ); + } +} + +impl< IntoT > Assign< AttributeSubformEntrySetter, IntoT > for FieldAttributes +where + IntoT : Into< AttributeSubformEntrySetter >, +{ + #[ inline( always ) ] + fn assign( &mut self, component : IntoT ) + { + let component = component.into(); + self.subform_entry.option_assign( component ); + } +} + +impl< IntoT > Assign< AttributePropertyArgForConstructor, IntoT > for FieldAttributes +where + IntoT : Into< AttributePropertyArgForConstructor >, +{ + #[ inline( always ) ] + fn assign( &mut self, component : IntoT ) + { + let component = component.into(); + self.arg_for_constructor.assign( component ); + } +} + + /// /// Attribute to hold configuration information about the field such as default value. /// @@ -134,6 +212,7 @@ impl AttributeComponent for AttributeConfig const KEYWORD : &'static str = "former"; + #[ allow( clippy::match_wildcard_for_single_variants ) ] fn from_meta( attr : &syn::Attribute ) -> Result< Self > { match attr.meta @@ -144,7 +223,7 @@ impl AttributeComponent for AttributeConfig }, syn::Meta::Path( ref _path ) => { - syn::parse2::< AttributeConfig >( Default::default() ) + syn::parse2::< AttributeConfig >( TokenStream::default() ) }, _ => return_syn_err!( attr, "Expects an attribute of format #[ former( default = 13 ) ].\nGot: {}", qt!{ #attr } ), } @@ -152,18 +231,6 @@ impl AttributeComponent for AttributeConfig } -impl< IntoT > Assign< AttributeConfig, IntoT > for FieldAttributes -where - IntoT : Into< AttributeConfig >, -{ - #[ inline( always ) ] - fn assign( &mut self, component : IntoT ) - { - let component : AttributeConfig = component.into(); - self.config.option_assign( component ); - } -} - impl< IntoT > Assign< AttributeConfig, IntoT > for AttributeConfig where IntoT : Into< AttributeConfig >, @@ -183,7 +250,6 @@ where #[ inline( always ) ] fn assign( &mut self, component : IntoT ) { - // panic!( "" ); self.default.assign( component.into() ); } } @@ -205,10 +271,10 @@ impl syn::parse::Parse for AttributeConfig syn_err! ( ident, - r#"Expects an attribute of format '#[ former( default = 13 ) ]' + r"Expects an attribute of format '#[ former( default = 13 ) ]' {known} But got: '{}' -"#, +", qt!{ #ident } ) }; @@ -241,6 +307,7 @@ impl syn::parse::Parse for AttributeConfig } } +/// Attribute for scalar setters. #[ derive( Debug, Default ) ] pub struct AttributeScalarSetter { @@ -270,6 +337,7 @@ impl AttributeComponent for AttributeScalarSetter const KEYWORD : &'static str = "scalar"; + #[ allow( clippy::match_wildcard_for_single_variants ) ] fn from_meta( attr : &syn::Attribute ) -> Result< Self > { match attr.meta @@ -280,7 +348,7 @@ impl AttributeComponent for AttributeScalarSetter }, syn::Meta::Path( ref _path ) => { - syn::parse2::< AttributeScalarSetter >( Default::default() ) + syn::parse2::< AttributeScalarSetter >( TokenStream::default() ) }, _ => return_syn_err!( attr, "Expects an attribute of format `#[ scalar( setter = false ) ]` or `#[ scalar( setter = true, name = my_name ) ]`. \nGot: {}", qt!{ #attr } ), } @@ -288,18 +356,6 @@ impl AttributeComponent for AttributeScalarSetter } -impl< IntoT > Assign< AttributeScalarSetter, IntoT > for FieldAttributes -where - IntoT : Into< AttributeScalarSetter >, -{ - #[ inline( always ) ] - fn assign( &mut self, component : IntoT ) - { - let component = component.into(); - self.scalar.option_assign( component ); - } -} - impl< IntoT > Assign< AttributeScalarSetter, IntoT > for AttributeScalarSetter where IntoT : Into< AttributeScalarSetter >, @@ -366,10 +422,10 @@ impl syn::parse::Parse for AttributeScalarSetter syn_err! ( ident, - r#"Expects an attribute of format '#[ scalar( name = myName, setter = true ) ]' + r"Expects an attribute of format '#[ scalar( name = myName, setter = true ) ]' {known} But got: '{}' -"#, +", qt!{ #ident } ) }; @@ -404,20 +460,8 @@ impl syn::parse::Parse for AttributeScalarSetter } } -/// -/// Attribute to enable/disable scalar setter generation. -/// -/// ## Example Input -/// -/// A typical input to parse might look like the following: -/// -/// ```ignore -/// name = field_name, setter = true -/// ``` -/// - +/// Attribute for subform scalar setters. #[ derive( Debug, Default ) ] - pub struct AttributeSubformScalarSetter { /// Optional identifier for naming the setter. @@ -445,6 +489,7 @@ impl AttributeComponent for AttributeSubformScalarSetter const KEYWORD : &'static str = "subform_scalar"; + #[ allow( clippy::match_wildcard_for_single_variants ) ] fn from_meta( attr : &syn::Attribute ) -> Result< Self > { match attr.meta @@ -455,7 +500,7 @@ impl AttributeComponent for AttributeSubformScalarSetter }, syn::Meta::Path( ref _path ) => { - syn::parse2::< AttributeSubformScalarSetter >( Default::default() ) + syn::parse2::< AttributeSubformScalarSetter >( TokenStream::default() ) }, _ => return_syn_err!( attr, "Expects an attribute of format `#[ subform_scalar( setter = false ) ]` or `#[ subform_scalar( setter = true, name = my_name ) ]`. \nGot: {}", qt!{ #attr } ), } @@ -463,18 +508,6 @@ impl AttributeComponent for AttributeSubformScalarSetter } -impl< IntoT > Assign< AttributeSubformScalarSetter, IntoT > for FieldAttributes -where - IntoT : Into< AttributeSubformScalarSetter >, -{ - #[ inline( always ) ] - fn assign( &mut self, component : IntoT ) - { - let component = component.into(); - self.subform_scalar.option_assign( component ); - } -} - impl< IntoT > Assign< AttributeSubformScalarSetter, IntoT > for AttributeSubformScalarSetter where IntoT : Into< AttributeSubformScalarSetter >, @@ -541,10 +574,10 @@ impl syn::parse::Parse for AttributeSubformScalarSetter syn_err! ( ident, - r#"Expects an attribute of format '#[ subform_scalar( name = myName, setter = true ) ]' + r"Expects an attribute of format '#[ subform_scalar( name = myName, setter = true ) ]' {known} But got: '{}' -"#, +", qt!{ #ident } ) }; @@ -579,19 +612,7 @@ impl syn::parse::Parse for AttributeSubformScalarSetter } } -/// Represents an attribute for configuring collection setter generation. -/// -/// This struct is part of a meta-programming approach to enable detailed configuration of nested structs or collections such as `Vec< E >, HashMap< K, E >` and so on. -/// It allows the customization of setter methods and the specification of the collection's behavior through meta attributes. -/// -/// ## Example Input -/// -/// The following is an example of a token stream that this struct can parse: -/// ```ignore -/// name = "custom_setter", setter = true, definition = former::VectorDefinition -/// ``` -/// - +/// Attribute for subform collection setters. #[ derive( Debug, Default ) ] pub struct AttributeSubformCollectionSetter { @@ -622,6 +643,7 @@ impl AttributeComponent for AttributeSubformCollectionSetter const KEYWORD : &'static str = "subform_collection"; + #[ allow( clippy::match_wildcard_for_single_variants ) ] fn from_meta( attr : &syn::Attribute ) -> Result< Self > { match attr.meta @@ -632,7 +654,7 @@ impl AttributeComponent for AttributeSubformCollectionSetter }, syn::Meta::Path( ref _path ) => { - syn::parse2::< AttributeSubformCollectionSetter >( Default::default() ) + syn::parse2::< AttributeSubformCollectionSetter >( TokenStream::default() ) }, _ => return_syn_err!( attr, "Expects an attribute of format `#[ subform_collection ]` or `#[ subform_collection( definition = former::VectorDefinition ) ]` if you want to use default collection defition. \nGot: {}", qt!{ #attr } ), } @@ -640,18 +662,6 @@ impl AttributeComponent for AttributeSubformCollectionSetter } -impl< IntoT > Assign< AttributeSubformCollectionSetter, IntoT > for FieldAttributes -where - IntoT : Into< AttributeSubformCollectionSetter >, -{ - #[ inline( always ) ] - fn assign( &mut self, component : IntoT ) - { - let component = component.into(); - self.subform_collection.option_assign( component ); - } -} - impl< IntoT > Assign< AttributeSubformCollectionSetter, IntoT > for AttributeSubformCollectionSetter where IntoT : Into< AttributeSubformCollectionSetter >, @@ -731,10 +741,10 @@ impl syn::parse::Parse for AttributeSubformCollectionSetter syn_err! ( ident, - r#"Expects an attribute of format '#[ subform_collection( name = myName, setter = true, debug, definition = MyDefinition ) ]' + r"Expects an attribute of format '#[ subform_collection( name = myName, setter = true, debug, definition = MyDefinition ) ]' {known} But got: '{}' -"#, +", qt!{ #ident } ) }; @@ -770,24 +780,7 @@ impl syn::parse::Parse for AttributeSubformCollectionSetter } } -/// Represents a subform attribute to control subform setter generation. -/// Used to specify extra options for using one former as subformer of another one. -/// For example name of setter could be customized. -/// -/// ## Example Input -/// -/// A typical input to parse might look like the following: -/// -/// ```ignore -/// name = field_name, setter = true -/// ``` -/// -/// or simply: -/// -/// ```ignore -/// mame = field_name -/// ``` - +/// Attribute for subform entry setters. #[ derive( Debug, Default ) ] pub struct AttributeSubformEntrySetter { @@ -818,6 +811,7 @@ impl AttributeComponent for AttributeSubformEntrySetter const KEYWORD : &'static str = "subform_entry"; + #[ allow( clippy::match_wildcard_for_single_variants ) ] fn from_meta( attr : &syn::Attribute ) -> Result< Self > { match attr.meta @@ -828,7 +822,7 @@ impl AttributeComponent for AttributeSubformEntrySetter }, syn::Meta::Path( ref _path ) => { - syn::parse2::< AttributeSubformEntrySetter >( Default::default() ) + syn::parse2::< AttributeSubformEntrySetter >( TokenStream::default() ) }, _ => return_syn_err!( attr, "Expects an attribute of format `#[ subform_entry ]` or `#[ subform_entry( name : child )` ], \nGot: {}", qt!{ #attr } ), } @@ -836,18 +830,6 @@ impl AttributeComponent for AttributeSubformEntrySetter } -impl< IntoT > Assign< AttributeSubformEntrySetter, IntoT > for FieldAttributes -where - IntoT : Into< AttributeSubformEntrySetter >, -{ - #[ inline( always ) ] - fn assign( &mut self, component : IntoT ) - { - let component = component.into(); - self.subform_entry.option_assign( component ); - } -} - impl< IntoT > Assign< AttributeSubformEntrySetter, IntoT > for AttributeSubformEntrySetter where IntoT : Into< AttributeSubformEntrySetter >, @@ -914,10 +896,10 @@ impl syn::parse::Parse for AttributeSubformEntrySetter syn_err! ( ident, - r#"Expects an attribute of format '#[ subform( name = myName, setter = true ) ]' + r"Expects an attribute of format '#[ subform( name = myName, setter = true ) ]' {known} But got: '{}' -"#, +", qt!{ #ident } ) }; @@ -952,7 +934,7 @@ impl syn::parse::Parse for AttributeSubformEntrySetter } } -// == attribute properties +// == attribute properties == // = @@ -961,8 +943,6 @@ impl syn::parse::Parse for AttributeSubformEntrySetter #[ derive( Debug, Default, Clone, Copy ) ] pub struct DebugMarker; -/// Specifies whether to provide a sketch as a hint. -/// Defaults to `false`, which means no hint is provided unless explicitly requested. impl AttributePropertyComponent for DebugMarker { const KEYWORD : &'static str = "debug"; @@ -1032,3 +1012,19 @@ impl AttributePropertyComponent for DefinitionMarker /// Definition of the collection former to use, e.g., `former::VectorFormer`. pub type AttributePropertyDefinition = AttributePropertyOptionalSyn< syn::Type, DefinitionMarker >; + +// = + +/// Marker type for attribute property marking a field as a constructor argument. +/// Defaults to `false`. +#[ derive( Debug, Default, Clone, Copy ) ] +pub struct ArgForConstructorMarker; + +impl AttributePropertyComponent for ArgForConstructorMarker +{ + const KEYWORD : &'static str = "arg_for_constructor"; +} + +/// Indicates whether a field should be an argument for standalone constructors. +/// Defaults to `false`. Parsed as a singletone attribute (`#[arg_for_constructor]`). +pub type AttributePropertyArgForConstructor = AttributePropertyOptionalSingletone< ArgForConstructorMarker >; \ 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..09615049c5 --- /dev/null +++ b/module/core/former_meta/src/derive_former/former_enum.rs @@ -0,0 +1,1287 @@ +// 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 +}; +#[ 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. +// +// ================================== + +/// Generate the Former ecosystem for an enum. +#[ allow( clippy::too_many_lines ) ] +pub(super) fn former_for_enum +( + 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 ); + + // Parse struct-level attributes + let struct_attrs = ItemAttributes::from_attrs( ast.attrs.iter() )?; + + // Initialize vectors to collect generated code pieces + let mut methods = Vec::new(); + let mut end_impls = Vec::new(); + let mut standalone_constructors = Vec::new(); // <<< Vector to store standalone constructors + + // 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(); + 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(); + + // --- Prepare merged where clause for this variant's generated impls --- + let merged_where_clause = enum_generics_where.clone(); + + // Generate method based on the variant's fields + match &variant.fields + { + // Case 1: Unit variant + syn::Fields::Unit => + { + // --- Standalone Constructor (Unit) --- + if struct_attrs.standalone_constructors.value( false ) + { + if variant_attrs.arg_for_constructor.value( false ) + { + return Err( syn::Error::new_spanned( variant, "#[arg_for_constructor] cannot be applied to a unit enum variant." ) ); + } + let constructor = quote! + { + /// Standalone constructor for the #variant_ident unit variant. + #[ inline( always ) ] + #vis fn #method_name < #enum_generics_impl >() + -> // Return type on new line + #enum_name< #enum_generics_ty > + where // Where clause on new line + #enum_generics_where + { // Brace on new line + #enum_name::#variant_ident + } // Brace on new line + }; + standalone_constructors.push( constructor ); + } + // --- End Standalone Constructor --- + + // Associated method + let static_method = quote! + { + /// Constructor for the #variant_ident unit variant. + #[ inline( always ) ] + #vis fn #method_name() -> Self + { + Self::#variant_ident + } + }; + methods.push( static_method ); + }, + // Case 2: Tuple variant + syn::Fields::Unnamed( fields ) => + { + if variant_attrs.arg_for_constructor.value( false ) + { + return Err( syn::Error::new_spanned( variant, "#[arg_for_constructor] cannot be applied directly to an enum variant identifier. Apply it to the fields *within* the variant instead, e.g., `MyVariant( #[arg_for_constructor] i32 )`." ) ); + } + + // Sub-case: Single field tuple variant + if fields.unnamed.len() == 1 + { + let field = fields.unnamed.first().unwrap(); + let inner_type = &field.ty; + let field_attrs = FieldAttributes::from_attrs( field.attrs.iter() )?; + + // Determine if the inner type likely has its own Former (heuristic) + let inner_former_exists = if let syn::Type::Path( tp ) = inner_type { tp.path.segments.last().is_some_and( | seg | !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 }; + + if wants_scalar || ( !wants_subform_scalar && !inner_former_exists ) + { + // --- Scalar Tuple(1) Variant --- + // Generate implicit former infrastructure for this scalar variant + 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 ); + + // Generate the implicit former components (Storage, Defs, Former, End) + let ( implicit_former_components, _ ) = generate_implicit_former_for_variant + ( + vis, + enum_name, + variant_ident, + &variant.fields, // Pass fields here + generics, + &implicit_former_name, + &implicit_storage_name, + &implicit_def_name, + &implicit_def_types_name, + &end_struct_name, + original_input, + )?; + end_impls.push( implicit_former_components ); // Add generated components + + // --- Standalone Constructor (Scalar Tuple(1) - Returns Implicit Former) --- + if struct_attrs.standalone_constructors.value( false ) + { + let constructor_params = if field_attrs.arg_for_constructor.value( false ) + { + let param_name = format_ident!( "_0" ); + vec![ quote!{ #param_name : impl Into< #inner_type > } ] + } else { vec![] }; + + let initial_storage_code = if field_attrs.arg_for_constructor.value( false ) + { + let param_name = format_ident!( "_0" ); + quote! + { + ::core::option::Option::Some + ( + #implicit_storage_name :: < #enum_generics_ty > // Add generics + { + _0 : ::core::option::Option::Some( #param_name.into() ), + _phantom : ::core::marker::PhantomData // Add phantom if needed + } + ) + } + } else { quote! { ::core::option::Option::None } }; + + let return_type = quote! + { + #implicit_former_name + < + #enum_generics_ty // Enum generics + #implicit_def_name // Implicit definition + < + #enum_generics_ty // Enum generics + (), // Context + #enum_name< #enum_generics_ty >, // Formed + #end_struct_name < #enum_generics_ty > // End + > + > + }; + + let constructor = quote! + { + /// Standalone constructor for the #variant_ident variant (scalar style, returns former). + #[ inline( always ) ] + #vis fn #method_name < #enum_generics_impl > + ( // Paren on new line + #( #constructor_params ),* + ) // Paren on new line + -> // Return type on new line + #return_type + where // Where clause on new line + #enum_generics_where + { // Brace on new line + #implicit_former_name::begin + ( + #initial_storage_code, + None, // Context + #end_struct_name::< #enum_generics_ty >::default() // End + ) + } // Brace on new line + }; + standalone_constructors.push( constructor ); + } + // --- End Standalone Constructor --- + + // Associated method (still returns Self directly for scalar) + let param_name = format_ident!( "_0" ); + let static_method = quote! + { + /// Constructor for the #variant_ident variant (scalar style). + #[ inline( always ) ] + #vis fn #method_name( #param_name : impl Into< #inner_type > ) -> Self + { + Self::#variant_ident( #param_name.into() ) + } + }; + methods.push( static_method ); + } + else // Default or explicit subform_scalar -> Generate Subformer + { + // --- Subform Tuple(1) Variant --- + let end_struct_name = format_ident!( "{}{}End", enum_name, variant_ident ); + let ( inner_type_name, inner_generics ) = match inner_type { syn::Type::Path( tp ) => { let s = tp.path.segments.last().unwrap(); ( s.ident.clone(), s.arguments.clone() ) }, _ => return Err( syn::Error::new_spanned( inner_type, "Inner variant type must be a path type" ) ) }; + 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 ); + let inner_generics_ty : syn::punctuated::Punctuated<_,_> = match &inner_generics { syn::PathArguments::AngleBracketed( args ) => args.args.clone(), _ => syn::punctuated::Punctuated::default() }; + let inner_generics_ty_comma = if inner_generics_ty.is_empty() { quote!{} } else { quote!{ #inner_generics_ty, } }; + + // --- Standalone Constructor (Subform Tuple(1)) --- + if struct_attrs.standalone_constructors.value( false ) + { + // Check if the inner field is a constructor argument + let constructor_params = if field_attrs.arg_for_constructor.value( false ) + { + let param_name = format_ident!( "_0" ); // Tuple field index + vec![ quote!{ #param_name : impl Into< #inner_type > } ] + } else { vec![] }; + + // Initialize storage only if there's an argument + let initial_storage_code = if field_attrs.arg_for_constructor.value( false ) + { + let param_name = format_ident!( "_0" ); + // Assume storage field is also named _0 for tuple variants + quote! + { + ::core::option::Option::Some + ( + #inner_storage_name :: < #inner_generics_ty > // Add generics if inner type has them + { + _0 : ::core::option::Option::Some( #param_name.into() ), + // Add _phantom if needed by storage + } + ) + } + } else { quote! { ::core::option::Option::None } }; + + // Define the return type (inner former specialized) + let return_type = quote! + { + #inner_former_name + < + #inner_generics_ty_comma // Inner type generics + #inner_def_name // Inner definition + < + #inner_generics_ty_comma // Inner type generics + (), // Context + #enum_name< #enum_generics_ty >, // Formed + #end_struct_name < #enum_generics_ty > // End + > + > + }; + + let constructor = quote! + { + /// Standalone constructor for the #variant_ident subform variant. + #[ inline( always ) ] + #vis fn #method_name < #enum_generics_impl > + ( // Paren on new line + #( #constructor_params ),* + ) // Paren on new line + -> // Return type on new line + #return_type + where // Where clause on new line + #enum_generics_where + { // Brace on new line + #inner_former_name::begin + ( + #initial_storage_code, + None, // Context + #end_struct_name::< #enum_generics_ty >::default() // End + ) + } // Brace on new line + }; + standalone_constructors.push( constructor ); + } + // --- End Standalone Constructor --- + + // Associated method logic (remains the same) + let phantom_field_type = phantom::tuple( &enum_generics_ty ); + let end_struct_def = quote! + { + #[ derive( Default, Debug ) ] + #vis struct #end_struct_name < #enum_generics_impl > + where // Where clause on new line + #merged_where_clause + { // Brace on new line + _phantom : #phantom_field_type, + } // Brace on new line + }; + let end_impl = quote! + { + #[ automatically_derived ] + impl< #enum_generics_impl > former::FormingEnd + < + #inner_def_types_name< #inner_generics_ty_comma (), #enum_name< #enum_generics_ty > > + > + for #end_struct_name < #enum_generics_ty > + where // Where clause on new line + #merged_where_clause + { // Brace on new line + #[ inline( always ) ] + fn call + ( // Paren on new line + &self, + sub_storage : #inner_storage_name< #inner_generics_ty >, + _context : Option< () >, + ) // Paren on new line + -> // Return type on new line + #enum_name< #enum_generics_ty > + { // Brace on new line + let data = former::StoragePreform::preform( sub_storage ); + #enum_name::#variant_ident( data ) + } // Brace on new line + } // Brace on new line + }; + let static_method = quote! + { + /// Starts forming the #variant_ident variant using a subformer. + #[ inline( always ) ] + #vis fn #method_name () + -> // Return type on new line + #inner_former_name + < + #inner_generics_ty_comma + #inner_def_name + < + #inner_generics_ty_comma (), #enum_name< #enum_generics_ty >, #end_struct_name < #enum_generics_ty > + > + > + { // Brace on new line + #inner_former_name::begin( None, None, #end_struct_name::< #enum_generics_ty >::default() ) + } // Brace on new line + }; + methods.push( static_method ); + end_impls.push( quote!{ #end_struct_def #end_impl } ); + } + } + // Sub-case: Multi-field tuple variant + else + if wants_scalar + { + // --- Scalar Tuple(N) Variant --- + // Generate implicit former infrastructure for this scalar variant + 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 ); + + // Generate the implicit former components (Storage, Defs, Former, End) + let ( implicit_former_components, _ ) = generate_implicit_former_for_variant + ( + vis, + enum_name, + variant_ident, + &variant.fields, // Pass fields here + generics, + &implicit_former_name, + &implicit_storage_name, + &implicit_def_name, + &implicit_def_types_name, + &end_struct_name, + original_input, + )?; + end_impls.push( implicit_former_components ); // Add generated components + + // --- Standalone Constructor (Scalar Tuple(N) - Returns Implicit Former) --- + if struct_attrs.standalone_constructors.value( false ) + { + let mut constructor_params = Vec::new(); + let mut initial_storage_assignments = Vec::new(); + for ( i, field ) in fields.unnamed.iter().enumerate() + { + let field_attrs = FieldAttributes::from_attrs( field.attrs.iter() )?; + if field_attrs.arg_for_constructor.value( false ) + { + return Err( syn::Error::new_spanned( field, "#[arg_for_constructor] cannot be used on fields within a variant marked #[scalar]. All fields of a scalar variant are implicitly constructor arguments." ) ); + } + let param_name = format_ident!( "_{}", i ); + let field_type = &field.ty; + constructor_params.push( quote! { #param_name : impl Into< #field_type > } ); + initial_storage_assignments.push( quote! { #param_name : ::core::option::Option::Some( #param_name.into() ) } ); + } + + let initial_storage_code = quote! + { + ::core::option::Option::Some + ( + #implicit_storage_name :: < #enum_generics_ty > // Add generics + { + #( #initial_storage_assignments ),* , + _phantom : ::core::marker::PhantomData // Add phantom if needed + } + ) + }; + + let return_type = quote! + { + #implicit_former_name + < + #enum_generics_ty // Enum generics + #implicit_def_name // Implicit definition + < + #enum_generics_ty // Enum generics + (), // Context + #enum_name< #enum_generics_ty >, // Formed + #end_struct_name < #enum_generics_ty > // End + > + > + }; + + let constructor = quote! + { + /// Standalone constructor for the #variant_ident variant with multiple fields (scalar style, returns former). + #[ inline( always ) ] + #vis fn #method_name < #enum_generics_impl > + ( // Paren on new line + #( #constructor_params ),* + ) // Paren on new line + -> // Return type on new line + #return_type + where // Where clause on new line + #enum_generics_where + { // Brace on new line + #implicit_former_name::begin + ( + #initial_storage_code, + None, // Context + #end_struct_name::< #enum_generics_ty >::default() // End + ) + } // Brace on new line + }; + standalone_constructors.push( constructor ); + } + // --- End Standalone Constructor --- + + // Associated method (returns Self directly) + let mut params = Vec::new(); + let mut args = Vec::new(); + for ( i, field ) in fields.unnamed.iter().enumerate() + { + let param_name = format_ident!( "_{}", i ); + let field_type = &field.ty; + params.push( quote! { #param_name : impl Into< #field_type > } ); + args.push( quote! { #param_name.into() } ); + } + let static_method = quote! + { + /// Constructor for the #variant_ident variant with multiple fields (scalar style). + #[ inline( always ) ] + #vis fn #method_name + ( // Paren on new line + #( #params ),* + ) // Paren on new line + -> Self + { // Brace on new line + Self::#variant_ident( #( #args ),* ) + } // Brace on new line + }; + methods.push( static_method ); + } + else // Default: Subformer (unsupported) + { + 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..." ) ); + } + }, + // Case 3: Struct variant + syn::Fields::Named( fields ) => + { + if variant_attrs.arg_for_constructor.value( false ) + { + return Err( syn::Error::new_spanned( variant, "#[arg_for_constructor] cannot be applied directly to an enum variant identifier. Apply it to the fields *within* the variant instead, e.g., `MyVariant { #[arg_for_constructor] field : i32 }`." ) ); + } + + if wants_scalar + { + // --- Scalar Struct Variant --- + // Generate implicit former infrastructure for this scalar variant + 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 ); + + // Generate the implicit former components (Storage, Defs, Former, End) + let ( implicit_former_components, _ ) = generate_implicit_former_for_variant + ( + vis, + enum_name, + variant_ident, + &variant.fields, // Pass fields here + generics, + &implicit_former_name, + &implicit_storage_name, + &implicit_def_name, + &implicit_def_types_name, + &end_struct_name, + original_input, + )?; + end_impls.push( implicit_former_components ); // Add generated components + + // --- Standalone Constructor (Scalar Struct - Returns Implicit Former) --- + if struct_attrs.standalone_constructors.value( false ) + { + let mut constructor_params = Vec::new(); + let mut initial_storage_assignments = Vec::new(); + for field in &fields.named + { + let field_attrs = FieldAttributes::from_attrs( field.attrs.iter() )?; + if field_attrs.arg_for_constructor.value( false ) + { + return Err( syn::Error::new_spanned( field, "#[arg_for_constructor] cannot be used on fields within a variant marked #[scalar]. All fields of a scalar variant are implicitly constructor arguments." ) ); + } + let field_ident = field.ident.as_ref().unwrap(); + let param_name = ident::ident_maybe_raw( field_ident ); + let field_type = &field.ty; + constructor_params.push( quote! { #param_name : impl Into< #field_type > } ); + initial_storage_assignments.push( quote! { #field_ident : ::core::option::Option::Some( #param_name.into() ) } ); + } + + let initial_storage_code = quote! + { + ::core::option::Option::Some + ( + #implicit_storage_name :: < #enum_generics_ty > // Add generics + { + #( #initial_storage_assignments ),* , + _phantom : ::core::marker::PhantomData // Add phantom if needed + } + ) + }; + + let return_type = quote! + { + #implicit_former_name + < + #enum_generics_ty // Enum generics + #implicit_def_name // Implicit definition + < + #enum_generics_ty // Enum generics + (), // Context + #enum_name< #enum_generics_ty >, // Formed + #end_struct_name < #enum_generics_ty > // End + > + > + }; + + let constructor = quote! + { + /// Standalone constructor for the #variant_ident struct variant (scalar style, returns former). + #[ inline( always ) ] + #vis fn #method_name < #enum_generics_impl > + ( // Paren on new line + #( #constructor_params ),* + ) // Paren on new line + -> // Return type on new line + #return_type + where // Where clause on new line + #enum_generics_where + { // Brace on new line + #implicit_former_name::begin + ( + #initial_storage_code, + None, // Context + #end_struct_name::< #enum_generics_ty >::default() // End + ) + } // Brace on new line + }; + standalone_constructors.push( constructor ); + } + // --- End Standalone Constructor --- + + // Associated method (returns Self directly) + let mut params = Vec::new(); + let mut args = Vec::new(); + for field in &fields.named + { + let field_ident = field.ident.as_ref().unwrap(); + let param_name = ident::ident_maybe_raw( field_ident ); + let field_type = &field.ty; + params.push( quote! { #param_name : impl Into< #field_type > } ); + args.push( quote! { #field_ident : #param_name.into() } ); + } + let static_method = quote! + { + /// Constructor for the #variant_ident struct variant (scalar style). + #[ inline( always ) ] + #vis fn #method_name + ( // Paren on new line + #( #params ),* + ) // Paren on new line + -> Self + { // Brace on new line + Self::#variant_ident { #( #args ),* } + } // Brace on new line + }; + methods.push( static_method ); + } + else // Default: Subformer + { + // --- Subform Struct Variant --- + 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 ); + + // Generate the implicit former components (Storage, Defs, Former, End) + let ( implicit_former_components, _ ) = generate_implicit_former_for_variant + ( + vis, + enum_name, + variant_ident, + &variant.fields, // Pass fields here + generics, + &implicit_former_name, + &implicit_storage_name, + &implicit_def_name, + &implicit_def_types_name, + &end_struct_name, + original_input, + )?; + end_impls.push( implicit_former_components ); // Add generated components + + // --- Standalone Constructor (Subform Struct - Returns Implicit Former) --- + if struct_attrs.standalone_constructors.value( false ) + { + // Identify constructor arguments based on field attributes + let constructor_args_fields : Vec<_> = fields.named.iter() + .map( |f| Ok(( f, FieldAttributes::from_attrs( f.attrs.iter() )? )) ) + .collect::>>()? + .into_iter() + .filter( |( _f, attrs )| attrs.arg_for_constructor.value( false ) ) + .map( |( f, _attrs )| f ) + .collect(); + + // Generate constructor parameters + let constructor_params = constructor_args_fields + .iter() + .map( | f | + { + let ident = f.ident.as_ref().unwrap(); + let ty = &f.ty; + let param_name = ident::ident_maybe_raw( ident ); + quote! { #param_name : impl Into< #ty > } + }); + + // Generate initial storage assignments for constructor arguments + let constructor_storage_assignments = constructor_args_fields + .iter() + .map( | f | + { + let ident = f.ident.as_ref().unwrap(); + let param_name = ident::ident_maybe_raw( ident ); + quote! { #ident : ::core::option::Option::Some( #param_name.into() ) } + }); + + let non_constructor_storage_assignments = fields.named + .iter() + .filter( | f | + { + // Filter out constructor args + !FieldAttributes::from_attrs( f.attrs.iter() ).is_ok_and( |a| a.arg_for_constructor.value( false ) ) + }) + .map( | f | + { + let ident = f.ident.as_ref().unwrap(); + quote! { #ident : ::core::option::Option::None } + }); + + let all_storage_assignments = constructor_storage_assignments + .chain( non_constructor_storage_assignments ); + + let initial_storage_code = if constructor_args_fields.is_empty() + { + quote! { ::core::option::Option::None } + } + else + { + quote! + { + ::core::option::Option::Some + ( + #implicit_storage_name :: < #enum_generics_ty > // Add generics to storage type + { + #( #all_storage_assignments ),* , + _phantom : ::core::marker::PhantomData // Add phantom if needed by storage + } + ) + } + }; + + // Define the return type (implicit former specialized) + let return_type = quote! + { + #implicit_former_name + < + #enum_generics_ty // Enum generics + #implicit_def_name // Implicit definition + < + #enum_generics_ty // Enum generics + (), // Context + #enum_name< #enum_generics_ty >, // Formed + #end_struct_name < #enum_generics_ty > // End + > + > + }; + + let constructor = quote! + { + /// Standalone constructor for the #variant_ident subform variant. + #[ inline( always ) ] + #vis fn #method_name < #enum_generics_impl > + ( // Paren on new line + #( #constructor_params ),* + ) // Paren on new line + -> // Return type on new line + #return_type + where // Where clause on new line + #enum_generics_where + { // Brace on new line + #implicit_former_name::begin + ( + #initial_storage_code, + None, // Context + #end_struct_name::< #enum_generics_ty >::default() // End + ) + } // Brace on new line + }; + standalone_constructors.push( constructor ); + } + // --- End Standalone Constructor --- + + // Associated method (returns implicit former) + let static_method = quote! + { + /// Starts forming the #variant_ident variant using its implicit subformer. + #[ inline( always ) ] + #vis fn #method_name () + -> // Return type on new line + #implicit_former_name + < + #enum_generics_ty + #implicit_def_name + < + #enum_generics_ty (), #enum_name< #enum_generics_ty >, #end_struct_name < #enum_generics_ty > + > + > + { // Brace on new line + #implicit_former_name::begin( None, None, #end_struct_name::< #enum_generics_ty >::default() ) + } // Brace on new line + }; + methods.push( static_method ); + // Implicit former components are already pushed to end_impls by the helper function + } + } // 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 // Where clause on new line + #enum_generics_where + { // Brace on new line + #( #methods )* // Splice the collected methods here + } // Brace on new line + + // Define the End structs, implicit formers, etc., outside the enum impl block. + #( #end_impls )* + + // <<< Added: Splice standalone constructors here >>> + #( #standalone_constructors )* + }; + + 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 function to generate the implicit former infrastructure for a variant. +/// Returns a tuple: (`TokenStream` for all components`TokenStream`am for setters only) +#[allow(clippy::too_many_arguments, clippy::too_many_lines)] +fn generate_implicit_former_for_variant +( + vis : &syn::Visibility, + enum_name : &syn::Ident, + variant_ident : &syn::Ident, + fields : &syn::Fields, + generics : &syn::Generics, + implicit_former_name : &syn::Ident, + implicit_storage_name : &syn::Ident, + implicit_def_name : &syn::Ident, + implicit_def_types_name : &syn::Ident, + end_struct_name : &syn::Ident, + _original_input : &proc_macro::TokenStream, +) -> Result< ( TokenStream, TokenStream ) > +{ + // --- Extract field data into owned structures first --- + struct FieldData + { + ident : syn::Ident, + ty : syn::Type, + attrs : FieldAttributes, + is_optional : bool, + non_optional_ty : syn::Type, + } + let ( _enum_generics_with_defaults, enum_generics_impl, enum_generics_ty, enum_generics_where ) = generic_params::decompose( generics ); + + + + let field_data_vec : Vec< FieldData > = match fields + { + syn::Fields::Named( f ) => f.named.iter() + .map( | field | + { + let ident = field.ident.clone().ok_or_else( || syn::Error::new_spanned( field, "Named field requires an identifier" ) )?; + let ty = field.ty.clone(); + let attrs = FieldAttributes::from_attrs( field.attrs.iter() )?; + let is_optional = typ::is_optional( &ty ); + let non_optional_ty = if is_optional { typ::parameter_first( &ty )?.clone() } else { ty.clone() }; + Ok( FieldData { ident, ty, attrs, is_optional, non_optional_ty } ) + } ) + .collect::< Result< _ > >()?, + syn::Fields::Unnamed(f) => f.unnamed.iter().enumerate() + .map( | ( index, field ) | + { + let ident = format_ident!( "_{}", index ); // Synthesize identifier + let ty = field.ty.clone(); + let attrs = FieldAttributes::from_attrs( field.attrs.iter() )?; + let is_optional = typ::is_optional( &ty ); + let non_optional_ty = if is_optional { typ::parameter_first( &ty )?.clone() } else { ty.clone() }; + Ok( FieldData { ident, ty, attrs, is_optional, non_optional_ty } ) + } ) + .collect::< Result< _ > >()?, + syn::Fields::Unit => vec![], // No fields for unit variants + }; + // --- End of data extraction --- + + + // --- Generate code snippets using the owned FieldData --- + let storage_field_definitions = field_data_vec.iter().map( |f_data| { + let ident = &f_data.ident; + let ty = &f_data.ty; + let ty2 = if f_data.is_optional { quote! { #ty } } else { quote! { ::core::option::Option< #ty > } }; + quote! { pub #ident : #ty2 } + }); + + let storage_field_defaults = field_data_vec.iter().map( |f_data| { + let ident = &f_data.ident; + quote! { #ident : ::core::option::Option::None } + }); + + 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 + { // Brace on new line + #( #storage_field_definitions, )* + _phantom : #phantom_field_type_storage, + } // Brace on new line + impl< #enum_generics_impl > ::core::default::Default + for #implicit_storage_name < #enum_generics_ty > + where #enum_generics_where + { // Brace on new line + #[ inline( always ) ] + fn default() -> Self + { // Brace on new line + Self { #( #storage_field_defaults, )* _phantom: ::core::marker::PhantomData } + } // Brace on new line + } // Brace on new line + }; + + let storage_preform_fields = field_data_vec.iter().map( |f_data| { + let ident = &f_data.ident; + let ty = &f_data.ty; + let default : Option< &syn::Expr > = f_data.attrs.config.as_ref() + .and_then( | attr | attr.default.ref_internal() ); + + if f_data.is_optional { + let _else = match default { + None => quote! { ::core::option::Option::None }, + Some( default_val ) => quote! { ::core::option::Option::Some( ::core::convert::Into::into( #default_val ) ) }, + }; + quote! { + let #ident = if self.#ident.is_some() { + ::core::option::Option::Some( self.#ident.take().unwrap() ) + } else { + #_else + }; + } + } else { + let _else = match default { + None => { + let panic_msg = format!( "Field '{ident}' isn't initialized" ); + quote! { + { + trait MaybeDefault< T > { fn maybe_default( self : &Self ) -> T { panic!( #panic_msg ) } } + 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::< #ty > ).maybe_default() + } + } + }, + Some( default_val ) => quote! { ::core::convert::Into::into( #default_val ) }, + }; + quote! { + let #ident = if self.#ident.is_some() { + self.#ident.take().unwrap() + } else { + #_else + }; + } + } + }); // Removed collect here, handle Result later if needed + + let storage_preform_field_names_vec : Vec<_> = field_data_vec.iter().map( |f| &f.ident ).collect(); + + // Determine the preformed type and variant construction based on field kind + let ( preformed_type, variant_construction ) = match fields + { + syn::Fields::Named( _ ) => // Use _ as we use field_data_vec now + { + let preformed_tuple_types = field_data_vec.iter().map( |f| &f.ty ); + ( + quote!{ ( #( #preformed_tuple_types ),* ) }, // Preformed is a tuple for named fields + quote!{ #enum_name::#variant_ident { #( #storage_preform_field_names_vec ),* } } + ) + }, + syn::Fields::Unnamed( _ ) => // Use _ as we use field_data_vec now + { + let field_types = field_data_vec.iter().map( |f| &f.ty ); + ( + quote!{ ( #( #field_types ),* ) }, // Preformed is a tuple for unnamed fields + quote!{ #enum_name::#variant_ident( #( #storage_preform_field_names_vec ),* ) } + ) + }, + syn::Fields::Unit => unreachable!(), + }; + + + let implicit_storage_preform = quote! + { + impl< #enum_generics_impl > former::Storage + for #implicit_storage_name < #enum_generics_ty > + where #enum_generics_where + { // Brace on new line + type Preformed = #preformed_type; + } // Brace on new line + impl< #enum_generics_impl > former::StoragePreform + for #implicit_storage_name < #enum_generics_ty > + where #enum_generics_where + { // Brace on new line + fn preform( mut self ) -> Self::Preformed + { // Brace on new line + #( #storage_preform_fields )* + ( #( #storage_preform_field_names_vec ),* ) + } // Brace on new line + } // Brace on new line + }; + + 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 + { // Brace on new line + _phantom : #former_definition_types_phantom + } // Brace on new line + 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 + { // Brace on new line + fn default() -> Self { Self { _phantom : ::core::marker::PhantomData } } + } // Brace on new line + impl < #former_definition_types_generics_impl > former::FormerDefinitionTypes + for #implicit_def_types_name < #former_definition_types_generics_ty > + where #former_definition_types_generics_where + { // Brace on new line + type Storage = #implicit_storage_name < #enum_generics_ty >; + type Formed = Formed2; + type Context = Context2; + } // Brace on new line + impl< #former_definition_types_generics_impl > former::FormerMutator + for #implicit_def_types_name < #former_definition_types_generics_ty > + where #former_definition_types_generics_where {} // Brace on new line + }; + + 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 + { // Brace on new line + _phantom : #former_definition_phantom + } // Brace on new line + impl < #former_definition_generics_impl > ::core::default::Default + for #implicit_def_name < #former_definition_generics_ty > + where #former_definition_generics_where + { // Brace on new line + fn default() -> Self { Self { _phantom : ::core::marker::PhantomData } } + } // Brace on new line + impl < #former_definition_generics_impl > former::FormerDefinition + for #implicit_def_name < #former_definition_generics_ty > + where // Where clause on new line + End2 : former::FormingEnd< #implicit_def_types_name < #former_definition_types_generics_ty > >, + #former_definition_generics_where + { // Brace on new line + type Types = #implicit_def_types_name < #former_definition_types_generics_ty >; + type End = End2; + type Storage = #implicit_storage_name < #enum_generics_ty >; + type Formed = Formed2; + type Context = Context2; + } // Brace on new line + }; + + 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 ); + + // --- Generate setters using owned FieldData --- + let former_field_setters = field_data_vec.iter().map(|f_data| { + let field_ident = &f_data.ident; + let typ = &f_data.non_optional_ty; + let setter_name = &f_data.ident; // Use field ident as setter name for implicit former + let doc = format!("Setter for the '{field_ident}' field."); + + quote! { + #[ doc = #doc ] + #[ inline ] + pub fn #setter_name< Src >( mut self, src : Src ) -> Self + where + Src : ::core::convert::Into< #typ >, + { + debug_assert!( self.storage.#field_ident.is_none() ); + self.storage.#field_ident = ::core::option::Option::Some( ::core::convert::Into::into( src ) ); + self + } + } + }).collect::>(); + // --- End setter generation --- + + 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 + { // Brace on new line + storage : Definition::Storage, + context : ::core::option::Option< Definition::Context >, + on_end : ::core::option::Option< Definition::End >, + } // Brace on new line + #[ automatically_derived ] + impl < #former_generics_impl > #implicit_former_name < #former_generics_ty > + where #former_generics_where + { // Brace on new line + #[ 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 + { // Brace on new line + 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 ) + } // Brace on new line + #[ inline( always ) ] pub fn begin + ( // Paren on new line + storage : ::core::option::Option< Definition::Storage >, + context : ::core::option::Option< Definition::Context >, + on_end : Definition::End + ) // Paren on new line + -> Self + { // Brace on new line + Self { storage : storage.unwrap_or_default(), context, on_end : ::core::option::Option::Some( on_end ) } + } // Brace on new line + #[ inline( always ) ] pub fn new( on_end : Definition::End ) -> Self + { // Brace on new line + Self::begin( None, None, on_end ) + } // Brace on new line + #( #former_field_setters )* + } // Brace on new line + }; + + 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 #enum_generics_where // Use original enum where clause + { // Brace on new line + _phantom : #phantom_field_type_end, + } // Brace on new line + }; + + let end_impl = quote! + { + #[ automatically_derived ] + impl< #enum_generics_impl > former::FormingEnd + < + #implicit_def_types_name< #enum_generics_ty (), #enum_name< #enum_generics_ty > > + > + for #end_struct_name < #enum_generics_ty > + where // Where clause on new line + #enum_generics_where // Use original enum where clause + { // Brace on new line + #[ inline( always ) ] + fn call + ( // Paren on new line + &self, + sub_storage : #implicit_storage_name< #enum_generics_ty >, + _context : Option< () >, + ) // Paren on new line + -> // Return type on new line + #enum_name< #enum_generics_ty > + { // Brace on new line + let ( #( #storage_preform_field_names_vec ),* ) = former::StoragePreform::preform( sub_storage ); + #variant_construction + } // Brace on new line + } // Brace on new line + }; + + let all_components = quote! + { + #implicit_storage_struct + #implicit_storage_preform + #implicit_def_types + #implicit_def + #implicit_former_struct + #end_struct_def + #end_impl + }; + + Ok( ( all_components, quote!( #( #former_field_setters )* ) ) ) +} + + +// 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 >, +) -> syn::Generics +{ + // Use Context2, Formed2 + let extra : macro_tools::GenericsWithWhere = syn::parse_quote! + { + < Context2 = (), Formed2 = #_enum_name < #enum_generics_ty > > + }; + 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, +) -> 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 > > + }; + 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 +) -> 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 // Where clause on new line + Definition : former::FormerDefinition< Storage = #implicit_storage_name < #enum_generics_ty > >, + Definition::Types : former::FormerDefinitionTypes< Storage = #implicit_storage_name < #enum_generics_ty > >, + }; + 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..4e1b0871ef --- /dev/null +++ b/module/core/former_meta/src/derive_former/former_struct.rs @@ -0,0 +1,686 @@ +// 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 }, + ident, // Added for ident_maybe_raw +}; + +/// 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; + use convert_case::{ Case, Casing }; // Added for snake_case naming + + // 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< _ > >()?; + + // <<< Start of changes for constructor arguments >>> + // Identify fields marked as constructor arguments + let constructor_args_fields : Vec< _ > = formed_fields + .iter() + .filter( | f | f.attrs.arg_for_constructor.value( false ) ) // Use the parsed attribute + .collect(); + + // Generate constructor function parameters + let constructor_params = constructor_args_fields + .iter() + .map( | f | + { + let ident = f.ident; + let ty = f.non_optional_ty; // Use non-optional type for the argument + // Use raw identifier for parameter name if needed + let param_name = ident::ident_maybe_raw( ident ); + quote! { #param_name : impl ::core::convert::Into< #ty > } + }); + + // Generate initial storage assignments for constructor arguments + let constructor_storage_assignments = constructor_args_fields + .iter() + .map( | f | + { + let ident = f.ident; + // Use raw identifier for parameter name if needed + let param_name = ident::ident_maybe_raw( ident ); + quote! { #ident : ::core::option::Option::Some( #param_name.into() ) } + }); + + // Generate initial storage assignments for non-constructor arguments (set to None) + let non_constructor_storage_assignments = formed_fields + .iter() + .chain( storage_fields.iter() ) // Include storage-only fields + .filter( | f | !f.attrs.arg_for_constructor.value( false ) ) // Filter out constructor args + .map( | f | + { + let ident = f.ident; + quote! { #ident : ::core::option::Option::None } + }); + + // Combine all storage assignments + let all_storage_assignments = constructor_storage_assignments + .chain( non_constructor_storage_assignments ); + + // Determine if we need to initialize storage (if there are args) + let initial_storage_code = if constructor_args_fields.is_empty() + { + // No args, begin with None storage + quote! { ::core::option::Option::None } + } + else + { + // Has args, create initial storage instance + quote! + { + ::core::option::Option::Some + ( + #former_storage :: < #struct_generics_ty > // Add generics to storage type + { + #( #all_storage_assignments ),* + } + ) + } + }; + // <<< End of changes for constructor arguments >>> + + + // 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 )?; + + // <<< Start of updated code for standalone constructor >>> + let standalone_constructor_code = if struct_attrs.standalone_constructors.value( false ) + { + // Generate constructor name (snake_case) + let constructor_name_str = item.to_string().to_case( Case::Snake ); + let constructor_name_ident_temp = format_ident!( "{}", constructor_name_str, span = item.span() ); + let constructor_name = ident::ident_maybe_raw( &constructor_name_ident_temp ); + + // Define the return type for the constructor + let return_type = quote! + { + #former < #struct_generics_ty #former_definition< #former_definition_args > > + }; + + // Generate the constructor function + quote! + { + /// Standalone constructor function for #item. + #[ inline( always ) ] + #vis fn #constructor_name < #struct_generics_impl > + ( + // <<< Insert constructor parameters >>> + #( #constructor_params ),* + ) + -> // Return type on new line + #return_type + where + #struct_generics_where // Use original struct where clause + { + // <<< Use initial_storage_code >>> + #former::begin( #initial_storage_code, None, former::ReturnPreformed ) + } + } + } + else + { + // If #[standalone_constructors] is not present, generate nothing. + quote!{} + }; + // <<< End of updated code for standalone constructor >>> + + + // 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 ) + } + } + + // <<< Added Standalone Constructor Function >>> + #standalone_constructor_code + + // = 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 + { + // qqq : This debug_assert should be enabled by default. How to do that? + // Maybe always generate code with debug_assert and remove it if release build? + // Or rely on optimizer to remove it? + // 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 ) +} \ No newline at end of file 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 31d6ab3491..d1b1acdb3a 100644 --- a/module/core/former_meta/src/derive_former/struct_attrs.rs +++ b/module/core/former_meta/src/derive_former/struct_attrs.rs @@ -1,7 +1,7 @@ //! //! Attributes of the whole item. //! - +#[ allow( clippy::wildcard_imports ) ] use super::*; use macro_tools:: @@ -15,8 +15,7 @@ use macro_tools:: use former_types::{ Assign, OptionExt }; -/// Represents the attributes of a struct, including storage fields, mutator, and perform attributes. - +/// Represents the attributes of a struct, including storage fields, mutator, perform, and standalone constructor attributes. // <<< Updated doc #[ derive( Debug, Default ) ] pub struct ItemAttributes { @@ -31,11 +30,15 @@ pub struct ItemAttributes /// Optional attribute for specifying a method to call after forming. /// This attribute can hold information about a method that should be invoked after the form operation is complete. pub perform : Option< AttributePerform >, + + /// Optional attribute to enable generation of standalone constructor functions. // <<< Added field + pub standalone_constructors : AttributePropertyStandaloneConstructors, } impl ItemAttributes { + /// Parses attributes from an iterator. pub fn from_attrs< 'a >( attrs : impl Iterator< Item = &'a syn::Attribute > ) -> Result< Self > { let mut result = Self::default(); @@ -49,6 +52,7 @@ impl ItemAttributes ", ", AttributeStorageFields::KEYWORD, ", ", AttributeMutator::KEYWORD, ", ", AttributePerform::KEYWORD, + ", ", AttributePropertyStandaloneConstructors::KEYWORD, // <<< Added keyword ".", ); syn_err! @@ -63,7 +67,7 @@ impl ItemAttributes { let key_ident = attr.path().get_ident().ok_or_else( || error( attr ) )?; - let key_str = format!( "{}", key_ident ); + let key_str = format!( "{key_ident}" ); // attributes does not have to be known // if attr::is_standard( &key_str ) @@ -76,10 +80,11 @@ impl ItemAttributes AttributeStorageFields::KEYWORD => result.assign( AttributeStorageFields::from_meta( attr )? ), AttributeMutator::KEYWORD => result.assign( AttributeMutator::from_meta( attr )? ), AttributePerform::KEYWORD => result.assign( AttributePerform::from_meta( attr )? ), - "debug" => {} + // <<< Added case for standalone_constructors + AttributePropertyStandaloneConstructors::KEYWORD => result.assign( AttributePropertyStandaloneConstructors::from( true ) ), + // "debug" => {} // Assuming debug is handled elsewhere or implicitly _ => {}, - // _ => return Err( error( attr ) ), - // attributes does not have to be known + // _ => return Err( error( attr ) ), // Allow unknown attributes } } @@ -87,7 +92,7 @@ impl ItemAttributes } /// - /// Generate parts, used for generating `perform()`` method. + /// Generate parts, used for generating `perform()` method. /// /// Similar to `form()`, but will also invoke function from `perform` attribute, if specified. /// @@ -96,13 +101,13 @@ impl ItemAttributes /// ## perform : /// return result; /// - /// ## perform_output : - /// < T : ::core::default::Default > + /// ## `perform_output` : + /// < T : `::core::default::Default` > /// - /// ## perform_generics : + /// ## `perform_generics` : /// Vec< T > /// - + #[ allow( clippy::unnecessary_wraps ) ] pub fn performer( &self ) -> Result< ( TokenStream, TokenStream, TokenStream ) > { @@ -142,30 +147,73 @@ impl ItemAttributes /// This function provides an iterator that yields `syn::Field` objects. If `storage_fields` is set, /// it clones and iterates over its fields. If `storage_fields` is `None`, it returns an empty iterator. /// - // pub fn storage_fields( &self ) -> impl Iterator< Item = syn::Field > pub fn storage_fields( &self ) -> &syn::punctuated::Punctuated< syn::Field, syn::token::Comma > { self.storage_fields.as_ref().map_or_else ( + // qqq : find better solutioin. avoid leaking || &*Box::leak( Box::new( syn::punctuated::Punctuated::new() ) ), | attr | &attr.fields ) - // qqq : find better solutioin + } + +} + +// = Assign implementations for ItemAttributes = - // self.storage_fields - // .as_ref() - // .map_or_else( - // || syn::punctuated::Punctuated::< syn::Field, syn::token::Comma >::new().into_iter(), - // | attr | attr.fields.clone().into_iter() - // // Clone and create an iterator when storage_fields is Some - // ) +impl< IntoT > Assign< AttributeStorageFields, IntoT > for ItemAttributes +where + IntoT : Into< AttributeStorageFields >, +{ + #[ inline( always ) ] + fn assign( &mut self, component : IntoT ) + { + let component = component.into(); + self.storage_fields.option_assign( component ); + } +} + +impl< IntoT > Assign< AttributeMutator, IntoT > for ItemAttributes +where + IntoT : Into< AttributeMutator >, +{ + #[ inline( always ) ] + fn assign( &mut self, component : IntoT ) + { + let component = component.into(); + self.mutator.assign( component ); } +} +impl< IntoT > Assign< AttributePerform, IntoT > for ItemAttributes +where + IntoT : Into< AttributePerform >, +{ + #[ inline( always ) ] + fn assign( &mut self, component : IntoT ) + { + let component = component.into(); + self.perform.option_assign( component ); + } } +// <<< Added Assign impl for the new property +impl< IntoT > Assign< AttributePropertyStandaloneConstructors, IntoT > for ItemAttributes +where + IntoT : Into< AttributePropertyStandaloneConstructors >, +{ + #[ inline( always ) ] + fn assign( &mut self, component : IntoT ) + { + let component = component.into(); + self.standalone_constructors.assign( component ); + } +} + + /// /// Attribute to hold storage-specific fields. /// Useful if formed structure should not have such fields. @@ -190,7 +238,7 @@ impl AttributeComponent for AttributeStorageFields { syn::Meta::List( ref meta_list ) => { - return syn::parse2::< AttributeStorageFields >( meta_list.tokens.clone() ); + syn::parse2::< AttributeStorageFields >( meta_list.tokens.clone() ) }, _ => return_syn_err!( attr, "Expects an attribute of format #[ storage_fields( a : i32, b : Option< String > ) ] .\nGot: {}", qt!{ #attr } ), @@ -199,17 +247,7 @@ impl AttributeComponent for AttributeStorageFields } -impl< IntoT > Assign< AttributeStorageFields, IntoT > for ItemAttributes -where - IntoT : Into< AttributeStorageFields >, -{ - #[ inline( always ) ] - fn assign( &mut self, component : IntoT ) - { - let component = component.into(); - self.storage_fields.option_assign( component ); - } -} +// Assign impl for AttributeStorageFields remains the same impl< IntoT > Assign< AttributeStorageFields, IntoT > for AttributeStorageFields where @@ -260,6 +298,7 @@ pub struct AttributeMutator pub debug : AttributePropertyDebug, } +#[ allow( clippy::match_wildcard_for_single_variants ) ] impl AttributeComponent for AttributeMutator { const KEYWORD : &'static str = "mutator"; @@ -270,11 +309,11 @@ impl AttributeComponent for AttributeMutator { syn::Meta::List( ref meta_list ) => { - return syn::parse2::< AttributeMutator >( meta_list.tokens.clone() ); + syn::parse2::< AttributeMutator >( meta_list.tokens.clone() ) }, syn::Meta::Path( ref _path ) => { - return Ok( Default::default() ) + Ok( AttributeMutator::default() ) }, _ => return_syn_err!( attr, "Expects an attribute of format `#[ mutator( custom ) ]`. \nGot: {}", qt!{ #attr } ), } @@ -282,17 +321,7 @@ impl AttributeComponent for AttributeMutator } -impl< IntoT > Assign< AttributeMutator, IntoT > for ItemAttributes -where - IntoT : Into< AttributeMutator >, -{ - #[ inline( always ) ] - fn assign( &mut self, component : IntoT ) - { - let component = component.into(); - self.mutator.assign( component ); - } -} +// Assign impls for AttributeMutator remain the same impl< IntoT > Assign< AttributeMutator, IntoT > for AttributeMutator where @@ -347,10 +376,10 @@ impl syn::parse::Parse for AttributeMutator syn_err! ( ident, - r#"Expects an attribute of format '#[ mutator( custom ) ]' + r"Expects an attribute of format '#[ mutator( custom ) ]' {known} But got: '{}' -"#, +", qt!{ #ident } ) }; @@ -407,7 +436,7 @@ impl AttributeComponent for AttributePerform { syn::Meta::List( ref meta_list ) => { - return syn::parse2::< AttributePerform >( meta_list.tokens.clone() ); + syn::parse2::< AttributePerform >( meta_list.tokens.clone() ) }, _ => return_syn_err!( attr, "Expects an attribute of format #[ perform( fn parse( mut self ) -> Request ) ] .\nGot: {}", qt!{ #attr } ), @@ -427,17 +456,7 @@ impl syn::parse::Parse for AttributePerform } } -impl< IntoT > Assign< AttributePerform, IntoT > for ItemAttributes -where - IntoT : Into< AttributePerform >, -{ - #[ inline( always ) ] - fn assign( &mut self, component : IntoT ) - { - let component = component.into(); - self.perform.option_assign( component ); - } -} +// Assign impl for AttributePerform remains the same impl< IntoT > Assign< AttributePerform, IntoT > for AttributePerform where @@ -451,7 +470,7 @@ where } } -// == attribute properties +// == attribute properties == /// Marker type for attribute property to specify whether to provide a sketch as a hint. /// Defaults to `false`, which means no hint is provided unless explicitly requested. @@ -482,3 +501,19 @@ impl AttributePropertyComponent for CustomMarker /// Indicates whether a custom code should be generated. /// Defaults to `false`, meaning no custom code is generated unless explicitly requested. pub type AttributePropertyCustom = AttributePropertyOptionalSingletone< CustomMarker >; + +// = <<< Added marker and type for standalone_constructors + +/// Marker type for attribute property to enable standalone constructors. +/// Defaults to `false`. +#[ derive( Debug, Default, Clone, Copy ) ] +pub struct StandaloneConstructorsMarker; + +impl AttributePropertyComponent for StandaloneConstructorsMarker +{ + const KEYWORD : &'static str = "standalone_constructors"; +} + +/// Indicates whether standalone constructors should be generated. +/// Defaults to `false`. Parsed as a singletone attribute (`#[standalone_constructors]`). +pub type AttributePropertyStandaloneConstructors = AttributePropertyOptionalSingletone< StandaloneConstructorsMarker >; \ No newline at end of file diff --git a/module/core/former_meta/src/lib.rs b/module/core/former_meta/src/lib.rs index e1fdae8504..c615d61cad 100644 --- a/module/core/former_meta/src/lib.rs +++ b/module/core/former_meta/src/lib.rs @@ -44,6 +44,7 @@ mod component /// - `perform`: Specifies a custom method to be invoked automatically at the end of the build process. /// - `storage_fields`: Specifies fields that should be treated as part of the storage for the former. /// - `mutator`: Defines a custom mutator class or function to manipulate the data just before the object is finalized. +/// - `standalone_constructors`: Generates top-level standalone constructor functions. // <<< Added doc /// /// # Field Attributes /// @@ -51,6 +52,7 @@ mod component /// - `scalar`: Indicates that the field is a scalar value, enabling direct assignment without the need for a sub-former. /// - `collection`: Marks the field as a collection that can use specific former methods to manage its contents. /// - `subform`: Specifies that the field should utilize a nested former, facilitating the construction of complex nested structures. +/// - `arg_for_constructor`: Marks a field as a required argument for standalone constructors. // <<< Added doc /// /// # Usage Example /// @@ -93,17 +95,19 @@ mod component /// ``` /// /// This pattern enables fluent and customizable construction of `UserProfile` instances, allowing for easy setting and modification of its fields. - #[ cfg( feature = "enabled" ) ] #[ cfg( feature = "derive_former" ) ] #[ proc_macro_derive ( Former, - attributes + attributes // This list defines attributes the derive macro processes ( debug, perform, storage_fields, mutator, // struct attributes former, scalar, subform_scalar, subform_collection, subform_entry, // field attributes + // <<< Added the new attributes here >>> + standalone_constructors, // Add struct-level attribute + arg_for_constructor // Add field-level attribute ) ) ] @@ -117,6 +121,8 @@ pub fn former( input : proc_macro::TokenStream ) -> proc_macro::TokenStream } } +// ... (rest of the component derives remain the same) ... + /// /// Macro to implement `From` for each component (field) of a structure. /// This macro simplifies the creation of `From` trait implementations for struct fields, @@ -158,7 +164,6 @@ pub fn former( input : proc_macro::TokenStream ) -> proc_macro::TokenStream /// # } /// ``` /// - #[ cfg( feature = "enabled" ) ] #[ cfg( feature = "derive_component_from" ) ] #[ proc_macro_derive( ComponentFrom, attributes( debug ) ) ] @@ -251,7 +256,6 @@ pub fn component_from( input : proc_macro::TokenStream ) -> proc_macro::TokenStr /// ``` /// This allows any type that can be converted into an `i32` or `String` to be set as /// the value of the `age` or `name` fields of `Person` instances, respectively. - #[ cfg( feature = "enabled" ) ] #[ cfg( feature = "derive_component_assign" ) ] #[ proc_macro_derive( Assign, attributes( debug ) ) ] @@ -269,7 +273,7 @@ pub fn component_assign( input : proc_macro::TokenStream ) -> proc_macro::TokenS /// Derives the `ComponentsAssign` trait for a struct, enabling `components_assign` which set all fields at once. /// /// This will work only if every field can be acquired from the passed value. -/// In other words, the type passed as an argument to `components_assign` must implement Into for each field type. +/// In other words, the type passed as an argument to `components_assign` must implement `Into` for each field type. /// /// # Attributes /// @@ -504,7 +508,6 @@ pub fn component_assign( input : proc_macro::TokenStream ) -> proc_macro::TokenS /// take_smaller_opts( &options2 ); /// ``` /// - #[ cfg( feature = "enabled" ) ] #[ cfg( all( feature = "derive_component_assign", feature = "derive_components_assign" ) ) ] #[ proc_macro_derive( ComponentsAssign, attributes( debug ) ) ] @@ -605,7 +608,6 @@ pub fn components_assign( input : proc_macro::TokenStream ) -> proc_macro::Token /// automatically generating the necessary `From< &Options1 >` implementation for `Options2`, facilitating /// an easy conversion between these types based on their compatible fields. /// - #[ cfg( feature = "enabled" ) ] #[ cfg( feature = "derive_from_components" ) ] #[ proc_macro_derive( FromComponents, attributes( debug ) ) ] @@ -617,4 +619,4 @@ pub fn from_components( input : proc_macro::TokenStream ) -> proc_macro::TokenSt Ok( stream ) => stream.into(), Err( err ) => err.to_compile_error().into(), } -} +} \ No newline at end of file diff --git a/module/core/former_meta/tests/smoke_test.rs b/module/core/former_meta/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/core/former_meta/tests/smoke_test.rs +++ b/module/core/former_meta/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/core/former_types/Cargo.toml b/module/core/former_types/Cargo.toml index 75ae5bce43..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.7.0" +version = "2.15.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -50,4 +50,4 @@ collection_tools = { workspace = true, features = [ "collection_constructors" ] [dev-dependencies] -test_tools = { workspace = true, features = [ "full" ] } +test_tools = { workspace = true } diff --git a/module/core/former_types/License b/module/core/former_types/License index e3e9e057cf..a23529f45b 100644 --- a/module/core/former_types/License +++ b/module/core/former_types/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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_types/Readme.md b/module/core/former_types/Readme.md index 30e14aaf08..0d1635fab1 100644 --- a/module/core/former_types/Readme.md +++ b/module/core/former_types/Readme.md @@ -1,9 +1,9 @@ -# Module :: former_types +# Module :: `former_types` - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_former_types_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_former_types_push.yml) [![docs.rs](https://img.shields.io/docsrs/former_types?color=e3e8f0&logo=docs.rs)](https://docs.rs/former_types) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fformer_types%2Fexamples%2Fformer_types_trivial.rs,RUN_POSTFIX=--example%20former_types_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_former_types_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_former_types_push.yml) [![docs.rs](https://img.shields.io/docsrs/former_types?color=e3e8f0&logo=docs.rs)](https://docs.rs/former_types) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fformer_types%2Fexamples%2Fformer_types_trivial.rs,RUN_POSTFIX=--example%20module%2Fcore%2Fformer_types%2Fexamples%2Fformer_types_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) A flexible implementation of the Builder pattern supporting nested builders and collection-specific subformers. Its compile-time structures and traits that are not generated but reused. diff --git a/module/core/former_types/src/collection.rs b/module/core/former_types/src/collection.rs index c740510fb3..767d86aa6a 100644 --- a/module/core/former_types/src/collection.rs +++ b/module/core/former_types/src/collection.rs @@ -5,10 +5,11 @@ //! such as vectors, hash maps, and custom collection implementations. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; /// Facilitates the conversion of collection entries to their corresponding value representations. @@ -47,7 +48,6 @@ mod private /// It is especially crucial in complex data structures, such as `HashMap`s, where entries /// often involve a key-value pair, and simple values need to be restructured to fit this model /// for operations like insertion or update. - pub trait CollectionValToEntry< Val > { /// The specific type of entry that corresponds to the value within the collection. @@ -141,7 +141,6 @@ mod private /// such as `HashMap`s. It not only identifies what constitutes an entry and a value in the context of the collection /// but also provides utility for converting between these two, which is critical in operations involving entry manipulation /// and value retrieval. - pub trait Collection { /// The type of entries that can be added to the collection. This type can differ from `Val` in collections like `HashMap`, @@ -347,6 +346,8 @@ mod private { /// Begins the construction process of a collection with optional initial storage and context, /// setting up an `on_end` completion handler to finalize the collection's construction. + /// # Panics + /// qqq: doc #[ inline( always ) ] pub fn begin ( @@ -370,6 +371,8 @@ mod private /// Provides a variation of the `begin` method allowing for coercion of the end handler, /// facilitating ease of integration with different end conditions. + /// # Panics + /// qqq: docs #[ inline( always ) ] pub fn begin_coercing< IntoEnd > ( @@ -394,6 +397,8 @@ mod private } /// Finalizes the building process, returning the formed or a context incorporating it. + /// # Panics + /// qqq: doc #[ inline( always ) ] pub fn end( mut self ) -> Definition::Formed { @@ -412,6 +417,7 @@ mod private /// Replaces the current storage with a provided storage, allowing for resetting or /// redirection of the building process. #[ inline( always ) ] + #[ must_use ] pub fn replace( mut self, storage : Definition::Storage ) -> Self { self.storage = storage; @@ -462,6 +468,8 @@ mod private /// Appends an entry to the end of the storage, expanding the internal collection. #[ inline( always ) ] + #[ must_use ] + #[ allow( clippy::should_implement_trait ) ] pub fn add< IntoElement >( mut self, entry : IntoElement ) -> Self where IntoElement : core::convert::Into< E >, { @@ -521,6 +529,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use orphan::*; @@ -530,6 +539,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; @@ -539,6 +549,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] diff --git a/module/core/former_types/src/collection/binary_heap.rs b/module/core/former_types/src/collection/binary_heap.rs index ae76f5e4f8..027607fd01 100644 --- a/module/core/former_types/src/collection/binary_heap.rs +++ b/module/core/former_types/src/collection/binary_heap.rs @@ -5,6 +5,7 @@ //! as subformer, enabling fluid and intuitive manipulation of binary heaps via builder patterns. //! +#[ allow( clippy::wildcard_imports ) ] use crate::*; #[ allow( unused ) ] use collection_tools::BinaryHeap; @@ -216,7 +217,6 @@ where /// It is particularly useful in scenarios where binary heaps are repeatedly used or configured in similar ways across different /// parts of an application. /// - pub type BinaryHeapFormer< E, Context, Formed, End > = CollectionFormer::< E, BinaryHeapDefinition< E, Context, Formed, End > >; @@ -241,6 +241,7 @@ impl< E > BinaryHeapExt< E > for BinaryHeap< E > where E : Ord { + #[ allow( clippy::default_constructed_unit_structs ) ] fn former() -> BinaryHeapFormer< E, (), BinaryHeap< E >, ReturnStorage > { BinaryHeapFormer::< E, (), BinaryHeap< E >, ReturnStorage >::new( ReturnStorage::default() ) diff --git a/module/core/former_types/src/collection/btree_map.rs b/module/core/former_types/src/collection/btree_map.rs index d1d97bfde8..fcf7db6879 100644 --- a/module/core/former_types/src/collection/btree_map.rs +++ b/module/core/former_types/src/collection/btree_map.rs @@ -4,7 +4,7 @@ //! this module abstracts the operations on binary tree map-like data structures, making them more flexible and easier to integrate as //! as subformer, enabling fluid and intuitive manipulation of binary tree maps via builder patterns. //! - +#[ allow( clippy::wildcard_imports ) ] use crate::*; use collection_tools::BTreeMap; @@ -212,7 +212,6 @@ where /// /// The alias helps reduce boilerplate code and enhances readability, making the construction of hash maps in /// a builder pattern both efficient and expressive. - pub type BTreeMapFormer< K, E, Context, Formed, End > = CollectionFormer::< ( K, E ), BTreeMapDefinition< K, E, Context, Formed, End > >; @@ -225,7 +224,6 @@ CollectionFormer::< ( K, E ), BTreeMapDefinition< K, E, Context, Formed, End > > /// with the builder pattern provided by the `former` framework. It's a convenience trait that simplifies /// creating configured hash map builders with default settings. /// - pub trait BTreeMapExt< K, E > : sealed::Sealed where K : Ord, @@ -238,6 +236,7 @@ impl< K, E > BTreeMapExt< K, E > for BTreeMap< K, E > where K : Ord, { + #[ allow( clippy::default_constructed_unit_structs ) ] fn former() -> BTreeMapFormer< K, E, (), BTreeMap< K, E >, ReturnStorage > { BTreeMapFormer::< K, E, (), BTreeMap< K, E >, ReturnStorage >::new( ReturnStorage::default() ) diff --git a/module/core/former_types/src/collection/btree_set.rs b/module/core/former_types/src/collection/btree_set.rs index 360c9484ae..42aeaf9adb 100644 --- a/module/core/former_types/src/collection/btree_set.rs +++ b/module/core/former_types/src/collection/btree_set.rs @@ -4,7 +4,7 @@ //! this module abstracts the operations on binary tree set-like data structures, making them more flexible and easier to integrate as //! as subformer, enabling fluid and intuitive manipulation of binary tree sets via builder patterns. //! - +#[ allow( clippy::wildcard_imports ) ] use crate::*; #[ allow( unused ) ] use collection_tools::BTreeSet; @@ -205,7 +205,6 @@ for BTreeSet< E > /// It is particularly useful in scenarios where binary tree sets are repeatedly used or configured in similar ways across different /// parts of an application. /// - pub type BTreeSetFormer< E, Context, Formed, End > = CollectionFormer::< E, BTreeSetDefinition< E, Context, Formed, End > >; @@ -230,6 +229,7 @@ impl< E > BTreeSetExt< E > for BTreeSet< E > where E : Ord { + #[ allow( clippy::default_constructed_unit_structs ) ] fn former() -> BTreeSetFormer< E, (), BTreeSet< E >, ReturnStorage > { BTreeSetFormer::< E, (), BTreeSet< E >, ReturnStorage >::new( ReturnStorage::default() ) diff --git a/module/core/former_types/src/collection/hash_map.rs b/module/core/former_types/src/collection/hash_map.rs index f6d6f1b58d..c204bcd361 100644 --- a/module/core/former_types/src/collection/hash_map.rs +++ b/module/core/former_types/src/collection/hash_map.rs @@ -5,9 +5,11 @@ //! as subformer, enabling fluid and intuitive manipulation of hashmaps via builder patterns. //! +#[ allow( clippy::wildcard_imports ) ] use crate::*; use collection_tools::HashMap; +#[ allow( clippy::implicit_hasher ) ] impl< K, V > Collection for HashMap< K, V > where K : core::cmp::Eq + core::hash::Hash, @@ -23,6 +25,7 @@ where } +#[ allow( clippy::implicit_hasher ) ] impl< K, V > CollectionAdd for HashMap< K, V > where K : core::cmp::Eq + core::hash::Hash, @@ -36,6 +39,7 @@ where } +#[ allow( clippy::implicit_hasher ) ] impl< K, V > CollectionAssign for HashMap< K, V > where K : core::cmp::Eq + core::hash::Hash, @@ -53,6 +57,7 @@ where // = storage +#[ allow( clippy::implicit_hasher ) ] impl< K, E > Storage for HashMap< K, E > where @@ -61,6 +66,7 @@ where type Preformed = HashMap< K, E >; } +#[ allow( clippy::implicit_hasher ) ] impl< K, E > StoragePreform for HashMap< K, E > where @@ -155,6 +161,7 @@ where // = Entity To +#[ allow( clippy::implicit_hasher ) ] impl< K, E, Definition > EntityToFormer< Definition > for HashMap< K, E > where K : ::core::cmp::Eq + ::core::hash::Hash, @@ -174,6 +181,7 @@ where type Former = HashMapFormer< K, E, Definition::Context, Definition::Formed, Definition::End >; } +#[ allow( clippy::implicit_hasher ) ] impl< K, E > crate::EntityToStorage for HashMap< K, E > where @@ -182,6 +190,7 @@ where type Storage = HashMap< K, E >; } +#[ allow( clippy::implicit_hasher ) ] impl< K, E, Context, Formed, End > crate::EntityToDefinition< Context, Formed, End > for HashMap< K, E > where @@ -192,6 +201,7 @@ where type Types = HashMapDefinitionTypes< K, E, Context, Formed >; } +#[ allow( clippy::implicit_hasher ) ] impl< K, E, Context, Formed > crate::EntityToDefinitionTypes< Context, Formed > for HashMap< K, E > where @@ -212,7 +222,6 @@ where /// /// The alias helps reduce boilerplate code and enhances readability, making the construction of hash maps in /// a builder pattern both efficient and expressive. - pub type HashMapFormer< K, E, Context, Formed, End > = CollectionFormer::< ( K, E ), HashMapDefinition< K, E, Context, Formed, End > >; @@ -225,7 +234,6 @@ CollectionFormer::< ( K, E ), HashMapDefinition< K, E, Context, Formed, End > >; /// with the builder pattern provided by the `former` framework. It's a convenience trait that simplifies /// creating configured hash map builders with default settings. /// - pub trait HashMapExt< K, E > : sealed::Sealed where K : ::core::cmp::Eq + ::core::hash::Hash, @@ -234,6 +242,7 @@ where fn former() -> HashMapFormer< K, E, (), HashMap< K, E >, ReturnStorage >; } +#[ allow( clippy::default_constructed_unit_structs, clippy::implicit_hasher ) ] impl< K, E > HashMapExt< K, E > for HashMap< K, E > where K : ::core::cmp::Eq + ::core::hash::Hash, diff --git a/module/core/former_types/src/collection/hash_set.rs b/module/core/former_types/src/collection/hash_set.rs index 16d5dec6c0..38228296db 100644 --- a/module/core/former_types/src/collection/hash_set.rs +++ b/module/core/former_types/src/collection/hash_set.rs @@ -1,8 +1,9 @@ //! This module provides a builder pattern implementation (`HashSetFormer`) for `HashSet`-like collections. It is designed to extend the builder pattern, allowing for fluent and dynamic construction of sets within custom data structures. - +#[ allow( clippy::wildcard_imports ) ] use crate::*; use collection_tools::HashSet; +#[ allow( clippy::implicit_hasher ) ] impl< K > Collection for HashSet< K > where K : core::cmp::Eq + core::hash::Hash, @@ -18,6 +19,7 @@ where } +#[ allow( clippy::implicit_hasher ) ] impl< K > CollectionAdd for HashSet< K > where K : core::cmp::Eq + core::hash::Hash, @@ -33,6 +35,7 @@ where } +#[ allow( clippy::implicit_hasher ) ] impl< K > CollectionAssign for HashSet< K > where K : core::cmp::Eq + core::hash::Hash, @@ -49,6 +52,7 @@ where } } +#[ allow( clippy::implicit_hasher ) ] impl< K > CollectionValToEntry< K > for HashSet< K > where K : core::cmp::Eq + core::hash::Hash, @@ -91,6 +95,7 @@ where // = storage +#[ allow( clippy::implicit_hasher ) ] impl< K > Storage for HashSet< K > where @@ -100,6 +105,7 @@ where type Preformed = HashSet< K >; } +#[ allow( clippy::implicit_hasher ) ] impl< K > StoragePreform for HashSet< K > where @@ -187,6 +193,7 @@ where // = entity to +#[ allow( clippy::implicit_hasher ) ] impl< K, Definition > EntityToFormer< Definition > for HashSet< K > where K : ::core::cmp::Eq + ::core::hash::Hash, @@ -205,6 +212,7 @@ where type Former = HashSetFormer< K, Definition::Context, Definition::Formed, Definition::End >; } +#[ allow( clippy::implicit_hasher ) ] impl< K > crate::EntityToStorage for HashSet< K > where @@ -213,6 +221,7 @@ where type Storage = HashSet< K >; } +#[ allow( clippy::implicit_hasher ) ] impl< K, Context, Formed, End > crate::EntityToDefinition< Context, Formed, End > for HashSet< K > where @@ -223,6 +232,7 @@ where type Types = HashSetDefinitionTypes< K, Context, Formed >; } +#[ allow( clippy::implicit_hasher ) ] impl< K, Context, Formed > crate::EntityToDefinitionTypes< Context, Formed > for HashSet< K > where @@ -239,7 +249,6 @@ where /// the `CollectionFormer` with predefined settings. This approach minimizes boilerplate code and enhances /// readability, making it ideal for fluent and expressive construction of set collections within custom data structures. /// - pub type HashSetFormer< K, Context, Formed, End > = CollectionFormer::< K, HashSetDefinition< K, Context, Formed, End > >; @@ -251,7 +260,6 @@ CollectionFormer::< K, HashSetDefinition< K, Context, Formed, End > >; /// set construction. It simplifies the process of building `HashSet` instances by providing a straightforward /// way to start the builder pattern with default context and termination behavior. /// - pub trait HashSetExt< K > : sealed::Sealed where K : ::core::cmp::Eq + ::core::hash::Hash, @@ -260,10 +268,12 @@ where fn former() -> HashSetFormer< K, (), HashSet< K >, ReturnStorage >; } +#[ allow( clippy::implicit_hasher ) ] impl< K > HashSetExt< K > for HashSet< K > where K : ::core::cmp::Eq + ::core::hash::Hash, { + #[ allow( clippy::default_constructed_unit_structs ) ] fn former() -> HashSetFormer< K, (), HashSet< K >, ReturnStorage > { HashSetFormer::< K, (), HashSet< K >, ReturnStorage >::new( ReturnStorage::default() ) diff --git a/module/core/former_types/src/collection/linked_list.rs b/module/core/former_types/src/collection/linked_list.rs index abdb327074..07b0c80674 100644 --- a/module/core/former_types/src/collection/linked_list.rs +++ b/module/core/former_types/src/collection/linked_list.rs @@ -4,7 +4,7 @@ //! this module abstracts the operations on list-like data structures, making them more flexible and easier to integrate as //! as subformer, enabling fluid and intuitive manipulation of lists via builder patterns. //! - +#[ allow( clippy::wildcard_imports ) ] use crate::*; #[ allow( unused ) ] use collection_tools::LinkedList; @@ -200,7 +200,6 @@ for LinkedList< E > /// It is particularly useful in scenarios where lists are repeatedly used or configured in similar ways across different /// parts of an application. /// - pub type LinkedListFormer< E, Context, Formed, End > = CollectionFormer::< E, LinkedListDefinition< E, Context, Formed, End > >; @@ -221,6 +220,7 @@ pub trait LinkedListExt< E > : sealed::Sealed impl< E > LinkedListExt< E > for LinkedList< E > { + #[ allow( clippy::default_constructed_unit_structs ) ] fn former() -> LinkedListFormer< E, (), LinkedList< E >, ReturnStorage > { LinkedListFormer::< E, (), LinkedList< E >, ReturnStorage >::new( ReturnStorage::default() ) diff --git a/module/core/former_types/src/collection/vector.rs b/module/core/former_types/src/collection/vector.rs index 96f7e577f1..0cef9fabfd 100644 --- a/module/core/former_types/src/collection/vector.rs +++ b/module/core/former_types/src/collection/vector.rs @@ -4,7 +4,7 @@ //! this module abstracts the operations on vector-like data structures, making them more flexible and easier to integrate as //! as subformer, enabling fluid and intuitive manipulation of vectors via builder patterns. //! - +#[ allow( clippy::wildcard_imports ) ] use crate::*; #[ allow( unused ) ] use collection_tools::Vec; @@ -200,7 +200,6 @@ for Vec< E > /// It is particularly useful in scenarios where vectors are repeatedly used or configured in similar ways across different /// parts of an application. /// - pub type VectorFormer< E, Context, Formed, End > = CollectionFormer::< E, VectorDefinition< E, Context, Formed, End > >; @@ -221,6 +220,7 @@ pub trait VecExt< E > : sealed::Sealed impl< E > VecExt< E > for Vec< E > { + #[ allow( clippy::default_constructed_unit_structs ) ] fn former() -> VectorFormer< E, (), Vec< E >, ReturnStorage > { VectorFormer::< E, (), Vec< E >, ReturnStorage >::new( ReturnStorage::default() ) diff --git a/module/core/former_types/src/collection/vector_deque.rs b/module/core/former_types/src/collection/vector_deque.rs index f3b08c6c01..72203567ed 100644 --- a/module/core/former_types/src/collection/vector_deque.rs +++ b/module/core/former_types/src/collection/vector_deque.rs @@ -4,7 +4,7 @@ //! this module abstracts the operations on vector deque-like data structures, making them more flexible and easier to integrate as //! as subformer, enabling fluid and intuitive manipulation of vector deques via builder patterns. //! - +#[ allow( clippy::wildcard_imports ) ] use crate::*; #[ allow( unused ) ] use collection_tools::VecDeque; @@ -200,7 +200,6 @@ for VecDeque< E > /// It is particularly useful in scenarios where vector deques are repeatedly used or configured in similar ways across different /// parts of an application. /// - pub type VecDequeFormer< E, Context, Formed, End > = CollectionFormer::< E, VecDequeDefinition< E, Context, Formed, End > >; @@ -221,6 +220,7 @@ pub trait VecDequeExt< E > : sealed::Sealed impl< E > VecDequeExt< E > for VecDeque< E > { + #[ allow( clippy::default_constructed_unit_structs ) ] fn former() -> VecDequeFormer< E, (), VecDeque< E >, ReturnStorage > { VecDequeFormer::< E, (), VecDeque< E >, ReturnStorage >::new( ReturnStorage::default() ) diff --git a/module/core/former_types/src/component.rs b/module/core/former_types/src/component.rs index 9e846e2673..001619ad1e 100644 --- a/module/core/former_types/src/component.rs +++ b/module/core/former_types/src/component.rs @@ -37,7 +37,7 @@ /// obj.assign( "New Name" ); /// assert_eq!( obj.name, "New Name" ); /// ``` -#[ cfg( any( feature = "types_component_assign" ) ) ] +#[ cfg( feature = "types_component_assign" ) ] pub trait Assign< T, IntoT > where IntoT : Into< T >, @@ -51,6 +51,7 @@ where /// Sets or replaces the component on the object with the given value. /// Unlike function (`assing`) function (`impute`) also consumes self and return it what is useful for builder pattern. #[ inline( always ) ] + #[ must_use ] fn impute( mut self, component : IntoT ) -> Self where Self : Sized, @@ -94,7 +95,7 @@ where /// opt_struct.option_assign( MyStruct { name: "New Name".to_string() } ); /// assert_eq!( opt_struct.unwrap().name, "New Name" ); /// ``` -#[ cfg( any( feature = "types_component_assign" ) ) ] +#[ cfg( feature = "types_component_assign" ) ] pub trait OptionExt< T > : sealed::Sealed where T : Sized + Assign< T, T >, @@ -109,7 +110,7 @@ where fn option_assign( & mut self, src : T ); } -#[ cfg( any( feature = "types_component_assign" ) ) ] +#[ cfg( feature = "types_component_assign" ) ] impl< T > OptionExt< T > for Option< T > where T : Sized + Assign< T, T >, @@ -125,7 +126,7 @@ where } } -#[ cfg( any( feature = "types_component_assign" ) ) ] +#[ cfg( feature = "types_component_assign" ) ] mod sealed { pub trait Sealed {} @@ -172,7 +173,7 @@ mod sealed /// /// assert_eq!( user_profile.username, "john_doe" ); /// ``` -#[ cfg( any( feature = "types_component_assign" ) ) ] +#[ cfg( feature = "types_component_assign" ) ] pub trait AssignWithType { /// Sets the value of a component by its type. @@ -196,7 +197,7 @@ pub trait AssignWithType Self : Assign< T, IntoT >; } -#[ cfg( any( feature = "types_component_assign" ) ) ] +#[ cfg( feature = "types_component_assign" ) ] impl< S > AssignWithType for S { #[ inline( always ) ] diff --git a/module/core/former_types/src/forming.rs b/module/core/former_types/src/forming.rs index d95bea8666..e1835296b7 100644 --- a/module/core/former_types/src/forming.rs +++ b/module/core/former_types/src/forming.rs @@ -26,7 +26,6 @@ /// - Storage-specific fields which are not present in formed structure. /// /// Look example `former_custom_mutator.rs` - pub trait FormerMutator where Self : crate::FormerDefinitionTypes, @@ -59,7 +58,6 @@ where /// # Parameters /// - `Storage`: The type of the collection being processed. /// - `Context`: The type of the context that might be altered or returned upon completion. - pub trait FormingEnd< Definition : crate::FormerDefinitionTypes > { /// Called at the end of the subforming process to return the modified or original context. @@ -163,6 +161,7 @@ use alloc::boxed::Box; /// a closure needs to be stored or passed around as an object implementing /// `FormingEnd`. #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +#[ allow( clippy::type_complexity ) ] pub struct FormingEndClosure< Definition : crate::FormerDefinitionTypes > { closure : Box< dyn Fn( Definition::Storage, Option< Definition::Context > ) -> Definition::Formed >, @@ -194,8 +193,8 @@ impl< Definition : crate::FormerDefinitionTypes > FormingEndClosure< Definition /// # Parameters /// /// * `closure` - A closure that matches the expected signature for transforming a collection - /// and context into a new context. This closure is stored and called by the - /// `call` method of the `FormingEnd` trait implementation. + /// and context into a new context. This closure is stored and called by the + /// `call` method of the `FormingEnd` trait implementation. /// /// # Returns /// @@ -250,7 +249,6 @@ for FormingEndClosure< Definition > /// are aligned from the onset, particularly when one former is nested within another, facilitating the creation /// of complex hierarchical data structures. /// - pub trait FormerBegin< Definition : > where Definition : crate::FormerDefinition, diff --git a/module/core/former_types/src/lib.rs b/module/core/former_types/src/lib.rs index 39196a30e7..f4c1ac346c 100644 --- a/module/core/former_types/src/lib.rs +++ b/module/core/former_types/src/lib.rs @@ -29,7 +29,7 @@ mod collection; /// Component-based forming. #[ cfg( feature = "enabled" ) ] -#[ cfg( any( feature = "types_component_assign" ) ) ] +#[ cfg( feature = "types_component_assign" ) ] mod component; /// Namespace with dependencies. @@ -49,6 +49,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use orphan::*; @@ -59,6 +60,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] @@ -76,6 +78,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] @@ -103,10 +106,11 @@ pub mod exposed #[ allow( unused_imports ) ] pub mod prelude { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] - #[ cfg( any( feature = "types_component_assign" ) ) ] + #[ cfg( feature = "types_component_assign" ) ] pub use component::*; #[ doc( inline ) ] diff --git a/module/core/former_types/tests/smoke_test.rs b/module/core/former_types/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/core/former_types/tests/smoke_test.rs +++ b/module/core/former_types/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/core/fs_tools/License b/module/core/fs_tools/License index e3e9e057cf..a23529f45b 100644 --- a/module/core/fs_tools/License +++ b/module/core/fs_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/src/fs/fs.rs b/module/core/fs_tools/src/fs/fs.rs index 25f60b2592..a10288843c 100644 --- a/module/core/fs_tools/src/fs/fs.rs +++ b/module/core/fs_tools/src/fs/fs.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { diff --git a/module/core/fs_tools/tests/inc/mod.rs b/module/core/fs_tools/tests/inc/mod.rs index dde9de6f94..7c40be710f 100644 --- a/module/core/fs_tools/tests/inc/mod.rs +++ b/module/core/fs_tools/tests/inc/mod.rs @@ -1,4 +1,4 @@ -#[ allow( unused_imports ) ] use super::*; +use test_tools::exposed::*; mod basic_test; diff --git a/module/core/fs_tools/tests/smoke_test.rs b/module/core/fs_tools/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/core/fs_tools/tests/smoke_test.rs +++ b/module/core/fs_tools/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/core/implements/Cargo.toml b/module/core/implements/Cargo.toml index 44a51ad680..30d51da328 100644 --- a/module/core/implements/Cargo.toml +++ b/module/core/implements/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "implements" -version = "0.8.0" +version = "0.12.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -24,8 +24,6 @@ workspace = true features = [ "full" ] all-features = false - - [features] default = [ "enabled" ] full = [ "enabled" ] @@ -36,4 +34,5 @@ enabled = [] [dependencies] [dev-dependencies] -test_tools = { workspace = true } +# this crate should not rely on test_tools to exclude cyclic dependencies +# test_tools = { workspace = true } diff --git a/module/core/implements/License b/module/core/implements/License index e3e9e057cf..a23529f45b 100644 --- a/module/core/implements/License +++ b/module/core/implements/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/core/implements/Readme.md index 8fe784a119..7ebc582300 100644 --- a/module/core/implements/Readme.md +++ b/module/core/implements/Readme.md @@ -2,7 +2,7 @@ # Module :: implements - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_implements_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_implements_push.yml) [![docs.rs](https://img.shields.io/docsrs/implements?color=e3e8f0&logo=docs.rs)](https://docs.rs/implements) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fimplements%2Fexamples%2Fimplements_trivial.rs,RUN_POSTFIX=--example%20implements_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_implements_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_implements_push.yml) [![docs.rs](https://img.shields.io/docsrs/implements?color=e3e8f0&logo=docs.rs)](https://docs.rs/implements) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fimplements%2Fexamples%2Fimplements_trivial.rs,RUN_POSTFIX=--example%20module%2Fcore%2Fimplements%2Fexamples%2Fimplements_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Macro to answer the question: does it implement a trait? diff --git a/module/core/implements/src/lib.rs b/module/core/implements/src/lib.rs index 7bdfba2035..989d5e528e 100644 --- a/module/core/implements/src/lib.rs +++ b/module/core/implements/src/lib.rs @@ -16,7 +16,7 @@ #[ cfg( feature = "enabled" ) ] mod implements_impl; -/// Internal namespace. +/// Define a private namespace for all its items. #[ cfg( feature = "enabled" ) ] mod private { diff --git a/module/core/implements/tests/inc/implements_test.rs b/module/core/implements/tests/inc/implements_test.rs index 24f39c32d7..e552165c41 100644 --- a/module/core/implements/tests/inc/implements_test.rs +++ b/module/core/implements/tests/inc/implements_test.rs @@ -3,221 +3,216 @@ use super::*; // -tests_impls! +#[ test ] +fn implements_basic() { - #[ test ] - fn implements_basic() - { + trait Trait1 {} + fn impl_trait1( _ : &impl Trait1 ) -> bool { true } - trait Trait1 {} - fn impl_trait1( _ : &impl Trait1 ) -> bool { true } + impl< T : Sized > Trait1 for &[ T ] {} + impl< T : Sized, const N : usize > Trait1 for [ T; N ] {} + impl< T : Sized, const N : usize > Trait1 for &[ T; N ] {} + let src : &[ i32 ] = &[ 1, 2, 3 ]; + assert_eq!( the_module::implements!( src => Trait1 ), true ); + assert_eq!( impl_trait1( &src ), true ); + assert_eq!( the_module::implements!( &[ 1, 2, 3 ] => Trait1 ), true ); + assert_eq!( impl_trait1( &[ 1, 2, 3 ] ), true ); + assert_eq!( the_module::implements!( [ 1, 2, 3 ] => Trait1 ), true ); - impl< T : Sized > Trait1 for &[ T ] {} - impl< T : Sized, const N : usize > Trait1 for [ T; N ] {} - impl< T : Sized, const N : usize > Trait1 for &[ T; N ] {} - let src : &[ i32 ] = &[ 1, 2, 3 ]; - a_id!( the_module::implements!( src => Trait1 ), true ); - a_id!( impl_trait1( &src ), true ); - a_id!( the_module::implements!( &[ 1, 2, 3 ] => Trait1 ), true ); - a_id!( impl_trait1( &[ 1, 2, 3 ] ), true ); - a_id!( the_module::implements!( [ 1, 2, 3 ] => Trait1 ), true ); + impl< T : Sized > Trait1 for Vec< T > {} + assert_eq!( the_module::implements!( vec!( 1, 2, 3 ) => Trait1 ), true ); - impl< T : Sized > Trait1 for Vec< T > {} - a_id!( the_module::implements!( vec!( 1, 2, 3 ) => Trait1 ), true ); + impl Trait1 for f32 {} + assert_eq!( the_module::implements!( 13_f32 => Trait1 ), true ); - impl Trait1 for f32 {} - a_id!( the_module::implements!( 13_f32 => Trait1 ), true ); + assert_eq!( the_module::implements!( true => Copy ), true ); + assert_eq!( the_module::implements!( true => Clone ), true ); - a_id!( the_module::implements!( true => Copy ), true ); - a_id!( the_module::implements!( true => Clone ), true ); + let src = true; + assert_eq!( the_module::implements!( src => Copy ), true ); + assert_eq!( the_module::implements!( src => Clone ), true ); - let src = true; - a_id!( the_module::implements!( src => Copy ), true ); - a_id!( the_module::implements!( src => Clone ), true ); + let src = Box::new( true ); + assert_eq!( the_module::implements!( src => Copy ), false ); + assert_eq!( the_module::implements!( src => Clone ), true ); - let src = Box::new( true ); - a_id!( the_module::implements!( src => Copy ), false ); - a_id!( the_module::implements!( src => Clone ), true ); + assert_eq!( the_module::implements!( Box::new( true ) => std::marker::Copy ), false ); + assert_eq!( the_module::implements!( Box::new( true ) => std::clone::Clone ), true ); - a_id!( the_module::implements!( Box::new( true ) => std::marker::Copy ), false ); - a_id!( the_module::implements!( Box::new( true ) => std::clone::Clone ), true ); +} - } +// - // +#[ test ] +fn instance_of_basic() +{ - #[ test ] - fn instance_of_basic() - { + let src = Box::new( true ); + assert_eq!( the_module::instance_of!( src => Copy ), false ); + assert_eq!( the_module::instance_of!( src => Clone ), true ); - let src = Box::new( true ); - a_id!( the_module::instance_of!( src => Copy ), false ); - a_id!( the_module::instance_of!( src => Clone ), true ); +} - } +// - // +#[ test ] +fn implements_functions() +{ - #[ test ] - fn implements_functions() + let _f = || { + println!( "hello" ); + }; - let _f = || - { - println!( "hello" ); - }; - - let fn_context = vec!( 1, 2, 3 ); - let _fn = || - { - println!( "hello {:?}", fn_context ); - }; - - let mut fn_mut_context = vec!( 1, 2, 3 ); - let _fn_mut = || - { - fn_mut_context[ 0 ] = 3; - println!( "{:?}", fn_mut_context ); - }; - - let mut fn_once_context = vec!( 1, 2, 3 ); - let _fn_once = || - { - fn_once_context[ 0 ] = 3; - let x = fn_once_context; - println!( "{:?}", x ); - }; - - /* */ - - a_id!( the_module::implements!( _fn => Copy ), true ); - a_id!( the_module::implements!( _fn => Clone ), true ); - a_id!( the_module::implements!( _fn => core::ops::Not ), false ); - let _ = _fn.clone(); - - /* */ - - // a_id!( the_module::implements!( function1 => fn() -> () ), true ); - // a_id!( the_module::implements!( &function1 => Fn() -> () ), true ); - // a_id!( the_module::implements!( &function1 => FnMut() -> () ), true ); - // a_id!( the_module::implements!( &function1 => FnOnce() -> () ), true ); - - // a_id!( the_module::implements!( _fn => fn() -> () ), true ); - a_id!( the_module::implements!( _fn => Fn() -> () ), true ); - a_id!( the_module::implements!( _fn => FnMut() -> () ), true ); - a_id!( the_module::implements!( _fn => FnOnce() -> () ), true ); - - // a_id!( the_module::implements!( _fn_mut => fn() -> () ), false ); - // a_id!( the_module::implements!( _fn_mut => Fn() -> () ), false ); - a_id!( the_module::implements!( _fn_mut => FnMut() -> () ), true ); - a_id!( the_module::implements!( _fn_mut => FnOnce() -> () ), true ); - - // a_id!( the_module::implements!( _fn_once => fn() -> () ), false ); - // a_id!( the_module::implements!( _fn_once => Fn() -> () ), false ); - // a_id!( the_module::implements!( _fn_once => FnMut() -> () ), false ); - a_id!( the_module::implements!( _fn_once => FnOnce() -> () ), true ); - - // fn is_f < R > ( _x : fn() -> R ) -> bool { true } - // fn is_fn < R, F : Fn() -> R > ( _x : &F ) -> bool { true } - // fn is_fn_mut < R, F : FnMut() -> R > ( _x : &F ) -> bool { true } - // fn is_fn_once < R, F : FnOnce() -> R > ( _x : &F ) -> bool { true } - // fn function1() -> bool { true } - - } - - // - - #[ test ] - fn pointer_experiment() + let fn_context = vec!( 1, 2, 3 ); + let _fn = || { + println!( "hello {:?}", fn_context ); + }; - let pointer_size = std::mem::size_of::< &u8 >(); - dbg!( &pointer_size ); - a_id!( 2 * pointer_size, std::mem::size_of::< &[ u8 ] >() ); - a_id!( 2 * pointer_size, std::mem::size_of::< *const [ u8 ] >() ); - a_id!( 2 * pointer_size, std::mem::size_of::< Box< [ u8 ] > >() ); - a_id!( 2 * pointer_size, std::mem::size_of::< std::rc::Rc< [ u8 ] > >() ); - a_id!( 1 * pointer_size, std::mem::size_of::< &[ u8 ; 20 ] >() ); + let mut fn_mut_context = vec!( 1, 2, 3 ); + let _fn_mut = || + { + fn_mut_context[ 0 ] = 3; + println!( "{:?}", fn_mut_context ); + }; - } + let mut fn_once_context = vec!( 1, 2, 3 ); + let _fn_once = || + { + fn_once_context[ 0 ] = 3; + let x = fn_once_context; + println!( "{:?}", x ); + }; + + /* */ + + assert_eq!( the_module::implements!( _fn => Copy ), true ); + assert_eq!( the_module::implements!( _fn => Clone ), true ); + assert_eq!( the_module::implements!( _fn => core::ops::Not ), false ); + let _ = _fn.clone(); + + /* */ + + // assert_eq!( the_module::implements!( function1 => fn() -> () ), true ); + // assert_eq!( the_module::implements!( &function1 => Fn() -> () ), true ); + // assert_eq!( the_module::implements!( &function1 => FnMut() -> () ), true ); + // assert_eq!( the_module::implements!( &function1 => FnOnce() -> () ), true ); + + // assert_eq!( the_module::implements!( _fn => fn() -> () ), true ); + assert_eq!( the_module::implements!( _fn => Fn() -> () ), true ); + assert_eq!( the_module::implements!( _fn => FnMut() -> () ), true ); + assert_eq!( the_module::implements!( _fn => FnOnce() -> () ), true ); + + // assert_eq!( the_module::implements!( _fn_mut => fn() -> () ), false ); + // assert_eq!( the_module::implements!( _fn_mut => Fn() -> () ), false ); + assert_eq!( the_module::implements!( _fn_mut => FnMut() -> () ), true ); + assert_eq!( the_module::implements!( _fn_mut => FnOnce() -> () ), true ); + + // assert_eq!( the_module::implements!( _fn_once => fn() -> () ), false ); + // assert_eq!( the_module::implements!( _fn_once => Fn() -> () ), false ); + // assert_eq!( the_module::implements!( _fn_once => FnMut() -> () ), false ); + assert_eq!( the_module::implements!( _fn_once => FnOnce() -> () ), true ); + + // fn is_f < R > ( _x : fn() -> R ) -> bool { true } + // fn is_fn < R, F : Fn() -> R > ( _x : &F ) -> bool { true } + // fn is_fn_mut < R, F : FnMut() -> R > ( _x : &F ) -> bool { true } + // fn is_fn_once < R, F : FnOnce() -> R > ( _x : &F ) -> bool { true } + // fn function1() -> bool { true } - // +} - #[ test ] - fn fn_experiment() - { +// - fn function1() -> bool { true } - - let _f = || - { - println!( "hello" ); - }; - - let fn_context = vec!( 1, 2, 3 ); - let _fn = || - { - println!( "hello {:?}", fn_context ); - }; - - let mut fn_mut_context = vec!( 1, 2, 3 ); - let _fn_mut = || - { - fn_mut_context[ 0 ] = 3; - println!( "{:?}", fn_mut_context ); - }; - - let mut fn_once_context = vec!( 1, 2, 3 ); - let _fn_once = || - { - fn_once_context[ 0 ] = 3; - let x = fn_once_context; - println!( "{:?}", x ); - }; - - a_id!( is_f( function1 ), true ); - a_id!( is_fn( &function1 ), true ); - a_id!( is_fn_mut( &function1 ), true ); - a_id!( is_fn_once( &function1 ), true ); - - a_id!( is_f( _f ), true ); - a_id!( is_fn( &_f ), true ); - a_id!( is_fn_mut( &_f ), true ); - a_id!( is_fn_once( &_f ), true ); - - // a_id!( is_f( _fn ), true ); - a_id!( is_fn( &_fn ), true ); - a_id!( is_fn_mut( &_fn ), true ); - a_id!( is_fn_once( &_fn ), true ); - - // a_id!( is_f( _fn_mut ), true ); - // a_id!( is_fn( &_fn_mut ), true ); - a_id!( is_fn_mut( &_fn_mut ), true ); - a_id!( is_fn_once( &_fn_mut ), true ); - - // a_id!( is_f( _fn_once ), true ); - // a_id!( is_fn( &_fn_once ), true ); - // a_id!( is_fn_mut( &_fn_once ), true ); - a_id!( is_fn_once( &_fn_once ), true ); - - // type Routine< R > = fn() -> R; - fn is_f < R > ( _x : fn() -> R ) -> bool { true } - // fn is_f < R > ( _x : Routine< R > ) -> bool { true } - fn is_fn < R, F : Fn() -> R > ( _x : &F ) -> bool { true } - fn is_fn_mut < R, F : FnMut() -> R > ( _x : &F ) -> bool { true } - fn is_fn_once < R, F : FnOnce() -> R > ( _x : &F ) -> bool { true } - } +#[ test ] +fn pointer_experiment() +{ + + let pointer_size = std::mem::size_of::< &u8 >(); + dbg!( &pointer_size ); + assert_eq!( 2 * pointer_size, std::mem::size_of::< &[ u8 ] >() ); + assert_eq!( 2 * pointer_size, std::mem::size_of::< *const [ u8 ] >() ); + assert_eq!( 2 * pointer_size, std::mem::size_of::< Box< [ u8 ] > >() ); + assert_eq!( 2 * pointer_size, std::mem::size_of::< std::rc::Rc< [ u8 ] > >() ); + assert_eq!( 1 * pointer_size, std::mem::size_of::< &[ u8 ; 20 ] >() ); } // -tests_index! +#[ test ] +fn fn_experiment() { - implements_basic, - instance_of_basic, - implements_functions, - pointer_experiment, - fn_experiment, + + fn function1() -> bool { true } + + let _f = || + { + println!( "hello" ); + }; + + let fn_context = vec!( 1, 2, 3 ); + let _fn = || + { + println!( "hello {:?}", fn_context ); + }; + + let mut fn_mut_context = vec!( 1, 2, 3 ); + let _fn_mut = || + { + fn_mut_context[ 0 ] = 3; + println!( "{:?}", fn_mut_context ); + }; + + let mut fn_once_context = vec!( 1, 2, 3 ); + let _fn_once = || + { + fn_once_context[ 0 ] = 3; + let x = fn_once_context; + println!( "{:?}", x ); + }; + + assert_eq!( is_f( function1 ), true ); + assert_eq!( is_fn( &function1 ), true ); + assert_eq!( is_fn_mut( &function1 ), true ); + assert_eq!( is_fn_once( &function1 ), true ); + + assert_eq!( is_f( _f ), true ); + assert_eq!( is_fn( &_f ), true ); + assert_eq!( is_fn_mut( &_f ), true ); + assert_eq!( is_fn_once( &_f ), true ); + + // assert_eq!( is_f( _fn ), true ); + assert_eq!( is_fn( &_fn ), true ); + assert_eq!( is_fn_mut( &_fn ), true ); + assert_eq!( is_fn_once( &_fn ), true ); + + // assert_eq!( is_f( _fn_mut ), true ); + // assert_eq!( is_fn( &_fn_mut ), true ); + assert_eq!( is_fn_mut( &_fn_mut ), true ); + assert_eq!( is_fn_once( &_fn_mut ), true ); + + // assert_eq!( is_f( _fn_once ), true ); + // assert_eq!( is_fn( &_fn_once ), true ); + // assert_eq!( is_fn_mut( &_fn_once ), true ); + assert_eq!( is_fn_once( &_fn_once ), true ); + + // type Routine< R > = fn() -> R; + fn is_f < R > ( _x : fn() -> R ) -> bool { true } + // fn is_f < R > ( _x : Routine< R > ) -> bool { true } + fn is_fn < R, F : Fn() -> R > ( _x : &F ) -> bool { true } + fn is_fn_mut < R, F : FnMut() -> R > ( _x : &F ) -> bool { true } + fn is_fn_once < R, F : FnOnce() -> R > ( _x : &F ) -> bool { true } } + +// + +// tests_index! +// { +// implements_basic, +// instance_of_basic, +// implements_functions, +// pointer_experiment, +// fn_experiment, +// } diff --git a/module/core/implements/tests/smoke_test.rs b/module/core/implements/tests/smoke_test.rs index 828e9b016b..ee06731048 100644 --- a/module/core/implements/tests/smoke_test.rs +++ b/module/core/implements/tests/smoke_test.rs @@ -1,14 +1,13 @@ - - -#[ test ] -fn local_smoke_test() -{ - ::test_tools::smoke_test_for_local_run(); -} - - -#[ test ] -fn published_smoke_test() -{ - ::test_tools::smoke_test_for_published_run(); -} +//! Smoke testing of the package. + +// #[ 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/core/implements/tests/implements_tests.rs b/module/core/implements/tests/tests.rs similarity index 50% rename from module/core/implements/tests/implements_tests.rs rename to module/core/implements/tests/tests.rs index d51c4b2b7d..9ee09a1d8c 100644 --- a/module/core/implements/tests/implements_tests.rs +++ b/module/core/implements/tests/tests.rs @@ -1,10 +1,11 @@ -// #![cfg_attr(docsrs, feature(doc_cfg))] +// #![ cfg_attr( docsrs, feature( doc_cfg ) ) ] // #![ cfg_attr( feature = "nightly", feature( type_name_of_val ) ) ] #![ cfg_attr( feature = "nightly", feature( trace_macros ) ) ] #![ cfg_attr( feature = "nightly", feature( meta_idents_concat ) ) ] +// qqq : this feature is generated by build.rs file, but chec does it work properly. should wanring be silented? +// explain how you verify that solution is correct -use test_tools::exposed::*; +// use test_tools::exposed::*; use implements as the_module; - mod inc; diff --git a/module/core/impls_index/Cargo.toml b/module/core/impls_index/Cargo.toml index ea67dfa107..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.7.0" +version = "0.10.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -27,8 +27,6 @@ all-features = false [features] default = [ "enabled" ] full = [ "enabled" ] -no_std = [] -use_alloc = [ "no_std" ] enabled = [ "impls_index_meta/enabled" ] [dependencies] @@ -36,4 +34,4 @@ impls_index_meta = { workspace = true } [dev-dependencies] test_tools = { workspace = true } -tempdir = { version = "0.3.7" } +#tempdir = { version = "0.3.7" } diff --git a/module/core/impls_index/License b/module/core/impls_index/License index 6d5ef8559f..72c80c1308 100644 --- a/module/core/impls_index/License +++ b/module/core/impls_index/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/core/impls_index/Readme.md index 39573c49bd..7a92991717 100644 --- a/module/core/impls_index/Readme.md +++ b/module/core/impls_index/Readme.md @@ -2,7 +2,7 @@ # Module :: impls_index - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_impls_index_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_impls_index_push.yml) [![docs.rs](https://img.shields.io/docsrs/impls_index?color=e3e8f0&logo=docs.rs)](https://docs.rs/impls_index) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fimpls_index%2Fexamples%2Fimpls_index_trivial.rs,RUN_POSTFIX=--example%20impls_index_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_impls_index_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_impls_index_push.yml) [![docs.rs](https://img.shields.io/docsrs/impls_index?color=e3e8f0&logo=docs.rs)](https://docs.rs/impls_index) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fimpls_index%2Fexamples%2Fimpls_index_trivial.rs,RUN_POSTFIX=--example%20module%2Fcore%2Fimpls_index%2Fexamples%2Fimpls_index_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Several of macros to put each function under a named macro to index every function in a class. diff --git a/module/core/impls_index/src/impls_index/func.rs b/module/core/impls_index/src/impls_index/func.rs index 21448b2ef8..da33da0127 100644 --- a/module/core/impls_index/src/impls_index/func.rs +++ b/module/core/impls_index/src/impls_index/func.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { diff --git a/module/core/impls_index/src/impls_index/impls.rs b/module/core/impls_index/src/impls_index/impls.rs index 18d81346a8..b10b4498e3 100644 --- a/module/core/impls_index/src/impls_index/impls.rs +++ b/module/core/impls_index/src/impls_index/impls.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { @@ -382,14 +382,7 @@ 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::*; #[ doc( inline ) ] pub use private:: { @@ -403,9 +396,15 @@ pub mod prelude _impls_callback, }; #[ doc( inline ) ] - #[ allow( unused_imports ) ] pub use ::impls_index_meta::impls3; #[ doc( inline ) ] - #[ allow( unused_imports ) ] pub use impls3 as impls; + +} + +/// Prelude to use essentials: `use my_module::prelude::*`. +#[ allow( unused_imports ) ] +pub mod prelude +{ + use super::*; } diff --git a/module/core/impls_index/src/impls_index/mod.rs b/module/core/impls_index/src/impls_index/mod.rs index f0d3a5f74f..484e43dd3f 100644 --- a/module/core/impls_index/src/impls_index/mod.rs +++ b/module/core/impls_index/src/impls_index/mod.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { @@ -31,6 +31,8 @@ pub mod own use super::*; #[ doc( inline ) ] pub use orphan::*; + #[ doc( inline ) ] + pub use ::impls_index_meta::*; } /// Shared with parent namespace of the module @@ -40,7 +42,6 @@ pub mod orphan use super::*; #[ doc( inline ) ] pub use exposed::*; - // pub use super::dependency; } /// Exposed namespace of the module. @@ -48,14 +49,13 @@ pub mod orphan pub mod exposed { use super::*; + pub use super::super::impls_index; #[ doc( inline ) ] pub use prelude::*; #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use super::impls::exposed::*; + pub use impls::exposed::*; #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use super::func::exposed::*; + pub use func::exposed::*; } /// Prelude to use essentials: `use my_module::prelude::*`. @@ -64,13 +64,7 @@ pub mod prelude { use super::*; #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use super::impls::prelude::*; + pub use impls::prelude::*; #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use super::func::prelude::*; - // #[ cfg( any( feature = "meta", feature = "impls_index_meta" ) ) ] - #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use ::impls_index_meta::*; + pub use func::prelude::*; } diff --git a/module/core/impls_index/src/lib.rs b/module/core/impls_index/src/lib.rs index ec229443d8..ac0f33af0a 100644 --- a/module/core/impls_index/src/lib.rs +++ b/module/core/impls_index/src/lib.rs @@ -1,4 +1,4 @@ -#![ cfg_attr( feature = "no_std", no_std ) ] +#![ no_std ] #![ doc( html_logo_url = "https://raw.githubusercontent.com/Wandalen/wTools/master/asset/img/logo_v3_trans_square.png" ) ] #![ doc( html_favicon_url = "https://raw.githubusercontent.com/Wandalen/wTools/alpha/asset/img/logo_v3_trans_square_icon_small_v2.ico" ) ] #![ doc( html_root_url = "https://docs.rs/impls_index/latest/impls_index/" ) ] @@ -30,7 +30,6 @@ pub mod own #[ doc( inline ) ] pub use orphan::*; #[ doc( inline ) ] - #[ allow( unused_imports ) ] pub use super::impls_index::orphan::*; } @@ -53,7 +52,6 @@ pub mod exposed #[ doc( inline ) ] pub use prelude::*; #[ doc( inline ) ] - #[ allow( unused_imports ) ] pub use super::impls_index::exposed::*; } @@ -64,6 +62,5 @@ pub mod prelude { use super::*; #[ doc( inline ) ] - #[ allow( unused_imports ) ] pub use super::impls_index::prelude::*; } diff --git a/module/core/impls_index/tests/experiment.rs b/module/core/impls_index/tests/experiment.rs index 85e51cf468..bcb1aca749 100644 --- a/module/core/impls_index/tests/experiment.rs +++ b/module/core/impls_index/tests/experiment.rs @@ -1,10 +1,11 @@ +//! Experimenting. include!( "../../../../module/step/meta/src/module/terminal.rs" ); #[ allow( unused_imports ) ] use impls_index as the_module; #[ allow( unused_imports ) ] -use test_tools::exposed::*; +use test_tools::exposed::{ a_id }; #[ path = "inc/impls3_test.rs" ] mod inc; diff --git a/module/core/impls_index/tests/inc/func_test.rs b/module/core/impls_index/tests/inc/func_test.rs index 7408b5b3ff..ebe6126f51 100644 --- a/module/core/impls_index/tests/inc/func_test.rs +++ b/module/core/impls_index/tests/inc/func_test.rs @@ -1,9 +1,10 @@ #![ deny( unused_imports ) ] use super::*; -#[ allow ( unused_imports ) ] -use the_module::prelude::*; +// #[ allow ( unused_imports ) ] +// use the_module::exposed::*; // use test_tools::exposed::*; +// use test_tools::a_id; // @@ -12,7 +13,7 @@ fn fn_name() { let f1 = 13; - let f2 = fn_name! + let f2 = the_module::exposed::fn_name! { fn f1() { @@ -29,7 +30,7 @@ fn fn_name() fn fn_rename() { - fn_rename! + the_module::exposed::fn_rename! { @Name { f2 } @Fn @@ -100,7 +101,7 @@ fn fns() }; } - fns! + the_module::exposed::fns! { @Callback { count } @Fns @@ -135,7 +136,7 @@ fn fns() }; } - fns! + the_module::exposed::fns! { @Callback { count } @Fns @@ -172,7 +173,7 @@ fn fns() }; } - fns! + the_module::exposed::fns! { @Callback { count } @Fns @@ -204,7 +205,7 @@ fn fns() }; } - fns! + the_module::exposed::fns! { @Callback { count } @Fns @@ -235,7 +236,7 @@ fn fns() }; } - fns! + the_module::exposed::fns! { @Callback { count } @Fns @@ -268,7 +269,7 @@ fn fns() }; } - fns! + the_module::exposed::fns! { @Callback { count } @Fns @@ -300,7 +301,7 @@ fn fns() // }; // } // -// fns! +// the_module::exposed::fns! // { // @Callback { count } // @Fns @@ -333,7 +334,7 @@ fn fns() } // trace_macros!( true ); - fns! + the_module::exposed::fns! { @Callback { count } @Fns diff --git a/module/core/impls_index/tests/inc/impls1_test.rs b/module/core/impls_index/tests/inc/impls1_test.rs index c8df2ca220..b49e010b01 100644 --- a/module/core/impls_index/tests/inc/impls1_test.rs +++ b/module/core/impls_index/tests/inc/impls1_test.rs @@ -1,120 +1,118 @@ // use test_tools::exposed::*; use super::*; -use the_module::prelude::impls1; +use the_module::exposed::impls1; +// use the_module::exposed::{ index }; // -tests_impls! +#[ test ] +fn impls_basic() { - fn impls_basic() + // test.case( "impls1 basic" ); { - // test.case( "impls1 basic" ); + impls1! { - - impls1! + fn f1() { - fn f1() - { - println!( "f1" ); - } - pub fn f2() - { - println!( "f2" ); - } - }; + println!( "f1" ); + } + pub fn f2() + { + println!( "f2" ); + } + }; - // trace_macros!( true ); - f1!(); - f2!(); - // trace_macros!( false ); + // trace_macros!( true ); + f1!(); + f2!(); + // trace_macros!( false ); - f1(); - f2(); + f1(); + f2(); - } + } + +// // test.case( "impls1 as" ); +// { +// +// impls1! +// { +// fn f1() +// { +// println!( "f1" ); +// } +// pub fn f2() +// { +// println!( "f2" ); +// } +// }; +// +// // trace_macros!( true ); +// f1!( as f1b ); +// f2!( as f2b ); +// // trace_macros!( false ); +// +// f1b(); +// f2b(); +// +// } +// +// // test.case( "impls1 as index" ); +// { +// +// impls1! +// { +// fn f1() +// { +// println!( "f1" ); +// } +// pub fn f2() +// { +// println!( "f2" ); +// } +// }; +// +// // trace_macros!( true ); +// index! +// { +// f1, +// f2 as f2b, +// } +// // trace_macros!( false ); +// +// f1(); +// f2b(); +// +// } - // // test.case( "impls1 as" ); - // { - // - // impls1! - // { - // fn f1() - // { - // println!( "f1" ); - // } - // pub fn f2() - // { - // println!( "f2" ); - // } - // }; - // - // // trace_macros!( true ); - // f1!( as f1b ); - // f2!( as f2b ); - // // trace_macros!( false ); - // - // f1b(); - // f2b(); - // - // } - // - // // test.case( "impls1 as index" ); - // { - // - // impls1! - // { - // fn f1() - // { - // println!( "f1" ); - // } - // pub fn f2() - // { - // println!( "f2" ); - // } - // }; - // - // // trace_macros!( true ); - // index! - // { - // f1, - // f2 as f2b, - // } - // // trace_macros!( false ); - // - // f1(); - // f2b(); - // - // } + // test.case( "macro" ); + { - // test.case( "macro" ); + impls1! { - - impls1! + fn f1() { - fn f1() + macro_rules! macro1 { - macro_rules! macro1 - { - ( $( $Arg : tt )* ) => { }; - } - macro1!(); + ( $( $Arg : tt )* ) => { }; } + macro1!(); } - - // trace_macros!( true ); - f1!(); - // trace_macros!( false ); - } + // trace_macros!( true ); + f1!(); + // trace_macros!( false ); + } + } // -tests_index! -{ - impls_basic, -} +// tests_index! +// { +// impls_basic, +// } diff --git a/module/core/impls_index/tests/inc/impls2_test.rs b/module/core/impls_index/tests/inc/impls2_test.rs index bb5d16eaab..20b83f0731 100644 --- a/module/core/impls_index/tests/inc/impls2_test.rs +++ b/module/core/impls_index/tests/inc/impls2_test.rs @@ -1,121 +1,119 @@ // use test_tools::exposed::*; use super::*; -use the_module::prelude::impls2; +use the_module::exposed::impls2; +use the_module::exposed::{ index }; // -tests_impls! +#[ test ] +fn impls_basic() { - fn impls_basic() + // test.case( "impls2 basic" ); { - // test.case( "impls2 basic" ); + impls2! { - - impls2! + fn f1() { - fn f1() - { - println!( "f1" ); - } - pub fn f2() - { - println!( "f2" ); - } - }; + println!( "f1" ); + } + pub fn f2() + { + println!( "f2" ); + } + }; - // trace_macros!( true ); - f1!(); - f2!(); - // trace_macros!( false ); + // trace_macros!( true ); + f1!(); + f2!(); + // trace_macros!( false ); - f1(); - f2(); + f1(); + f2(); - } + } - // test.case( "impls2 as" ); - { + // test.case( "impls2 as" ); + { - impls2! + impls2! + { + fn f1() { - fn f1() - { - println!( "f1" ); - } - pub fn f2() - { - println!( "f2" ); - } - }; + println!( "f1" ); + } + pub fn f2() + { + println!( "f2" ); + } + }; - // trace_macros!( true ); - f1!( as f1b ); - f2!( as f2b ); - // trace_macros!( false ); + // trace_macros!( true ); + f1!( as f1b ); + f2!( as f2b ); + // trace_macros!( false ); - f1b(); - f2b(); + f1b(); + f2b(); - } + } - // test.case( "impls2 as index" ); - { + // test.case( "impls2 as index" ); + { - impls2! + impls2! + { + fn f1() { - fn f1() - { - println!( "f1" ); - } - pub fn f2() - { - println!( "f2" ); - } - }; - - // trace_macros!( true ); - index! + println!( "f1" ); + } + pub fn f2() { - f1, - f2 as f2b, + println!( "f2" ); } - // trace_macros!( false ); - - f1(); - f2b(); + }; + // trace_macros!( true ); + index! + { + f1, + f2 as f2b, } + // trace_macros!( false ); - // test.case( "macro" ); - { + f1(); + f2b(); - impls2! + } + + // test.case( "macro" ); + { + + impls2! + { + fn f1() { - fn f1() + macro_rules! macro1 { - macro_rules! macro1 - { - ( $( $Arg : tt )* ) => { }; - } - macro1!(); + ( $( $Arg : tt )* ) => { }; } + macro1!(); } - - // trace_macros!( true ); - f1!(); - // trace_macros!( false ); - } + // trace_macros!( true ); + f1!(); + // trace_macros!( false ); + } + } // -tests_index! -{ - // fns, - impls_basic, -} +// tests_index! +// { +// // fns, +// impls_basic, +// } diff --git a/module/core/impls_index/tests/inc/impls3_test.rs b/module/core/impls_index/tests/inc/impls3_test.rs index 860acd126a..350a692a1f 100644 --- a/module/core/impls_index/tests/inc/impls3_test.rs +++ b/module/core/impls_index/tests/inc/impls3_test.rs @@ -1,5 +1,5 @@ use super::*; -use the_module::prelude::impls3; +use the_module::exposed::{ impls3, index, impls_index }; // @@ -7,7 +7,7 @@ use the_module::prelude::impls3; fn basic() { - impls! + impls3! { fn f1() { diff --git a/module/core/impls_index/tests/inc/impls_basic_test.rs b/module/core/impls_index/tests/inc/impls_basic_test.rs index c488aec5a2..64ca19ceac 100644 --- a/module/core/impls_index/tests/inc/impls_basic_test.rs +++ b/module/core/impls_index/tests/inc/impls_basic_test.rs @@ -1,9 +1,8 @@ use super::*; -#[ allow( unused_imports ) ] -use the_module::prelude::*; +// use the_module::exposed::*; // trace_macros!( true ); -tests_impls! +the_module::exposed::tests_impls! { fn pass1_test() @@ -41,7 +40,7 @@ tests_impls! // trace_macros!( false ); // trace_macros!( true ); -tests_index! +the_module::exposed::tests_index! { pass1_test, fail1_test, diff --git a/module/core/impls_index/tests/inc/index_test.rs b/module/core/impls_index/tests/inc/index_test.rs index de1ed0d9be..561e1ba8ac 100644 --- a/module/core/impls_index/tests/inc/index_test.rs +++ b/module/core/impls_index/tests/inc/index_test.rs @@ -1,155 +1,151 @@ // use test_tools::exposed::*; use super::*; -use the_module::prelude::impls1; +use the_module::exposed::impls1; +use the_module::exposed::{ index }; // -tests_impls! +#[ test ] +fn empty_with_comma() { - - fn empty_with_comma() + // test.case( "impls1 basic" ); { - // test.case( "impls1 basic" ); - { - - impls1!(); - index!(); - - } + impls1!(); + index!(); } +} + +#[ test ] +fn empty_without_comma() +{ - fn empty_without_comma() + // test.case( "impls1 basic" ); { - // test.case( "impls1 basic" ); + impls1! { + }; - impls1! - { - }; - - index! - { - } - + index! + { } } +} + +#[ test ] +fn with_comma() +{ - fn with_comma() + // test.case( "impls1 basic" ); { - // test.case( "impls1 basic" ); + impls1! { - - impls1! + fn f1() -> i32 { - fn f1() -> i32 - { - println!( "f1" ); - 13 - } - }; - - index! - { - f1, + println!( "f1" ); + 13 } + }; - a_id!( f1(), 13 ); + index! + { + f1, } + a_id!( f1(), 13 ); } +} + +#[ test ] +fn without_comma() +{ - fn without_comma() + // test.case( "impls1 basic" ); { - // test.case( "impls1 basic" ); + impls1! { - - impls1! - { - fn f1() -> i32 - { - println!( "f1" ); - 13 - } - }; - - index! + fn f1() -> i32 { - f1 + println!( "f1" ); + 13 } + }; - a_id!( f1(), 13 ); + index! + { + f1 } + a_id!( f1(), 13 ); } +} + +#[ test ] +fn parentheses_with_comma() +{ - fn parentheses_with_comma() + // test.case( "impls1 basic" ); { - // test.case( "impls1 basic" ); + impls1! { - - impls1! + fn f1() -> i32 { - fn f1() -> i32 - { - println!( "f1" ); - 13 - } - }; - - index!( f1, ); + println!( "f1" ); + 13 + } + }; - a_id!( f1(), 13 ); - } + index!( f1, ); + a_id!( f1(), 13 ); } +} - fn parentheses_without_comma() +#[ test ] +fn parentheses_without_comma() +{ + + // test.case( "impls1 basic" ); { - // test.case( "impls1 basic" ); + impls1! { - - impls1! + fn f1() -> i32 { - fn f1() -> i32 - { - println!( "f1" ); - 13 - } - }; - - index!( f1 ); + println!( "f1" ); + 13 + } + }; - a_id!( f1(), 13 ); - } + index!( f1 ); + a_id!( f1(), 13 ); } } // -tests_index! -{ - - empty_with_comma, - empty_without_comma, - with_comma, - without_comma, - parentheses_with_comma, - parentheses_without_comma, - -} +// tests_index! +// { +// +// empty_with_comma, +// empty_without_comma, +// with_comma, +// without_comma, +// parentheses_with_comma, +// parentheses_without_comma, +// +// } diff --git a/module/core/impls_index/tests/inc/mod.rs b/module/core/impls_index/tests/inc/mod.rs index d7b9687e2f..624fc01ab8 100644 --- a/module/core/impls_index/tests/inc/mod.rs +++ b/module/core/impls_index/tests/inc/mod.rs @@ -1,5 +1,13 @@ -use super::*; +// To avoid conflicts with test_tools it's important to import only those names which are needed. +use test_tools::a_id; + +use super:: +{ + the_module, + // only_for_terminal_module, + // a_id, +}; mod func_test; mod impls_basic_test; diff --git a/module/core/impls_index/tests/inc/tests_index_test.rs b/module/core/impls_index/tests/inc/tests_index_test.rs index 9c684d5a68..d6cbf4e3c6 100644 --- a/module/core/impls_index/tests/inc/tests_index_test.rs +++ b/module/core/impls_index/tests/inc/tests_index_test.rs @@ -1,155 +1,151 @@ // use test_tools::exposed::*; use super::*; -use the_module::prelude::impls1; +use the_module::exposed::impls1; +use the_module::exposed::{ tests_index }; // -tests_impls! +#[ test ] +fn empty_with_comma() { - - fn empty_with_comma() + // test.case( "impls1 basic" ); { - // test.case( "impls1 basic" ); - { - - impls1!(); - tests_index!(); - - } + impls1!(); + tests_index!(); } +} + +#[ test ] +fn empty_without_comma() +{ - fn empty_without_comma() + // test.case( "impls1 basic" ); { - // test.case( "impls1 basic" ); + impls1! { + }; - impls1! - { - }; - - tests_index! - { - } - + tests_index! + { } } +} + +#[ test ] +fn with_comma() +{ - fn with_comma() + // test.case( "impls1 basic" ); { - // test.case( "impls1 basic" ); + impls1! { - - impls1! + fn f1() -> i32 { - fn f1() -> i32 - { - println!( "f1" ); - 13 - } - }; - - tests_index! - { - f1, + println!( "f1" ); + 13 } + }; - a_id!( f1(), 13 ); + tests_index! + { + f1, } + a_id!( f1(), 13 ); } +} + +#[ test ] +fn without_comma() +{ - fn without_comma() + // test.case( "impls1 basic" ); { - // test.case( "impls1 basic" ); + impls1! { - - impls1! - { - fn f1() -> i32 - { - println!( "f1" ); - 13 - } - }; - - tests_index! + fn f1() -> i32 { - f1 + println!( "f1" ); + 13 } + }; - a_id!( f1(), 13 ); + tests_index! + { + f1 } + a_id!( f1(), 13 ); } +} + +#[ test ] +fn parentheses_with_comma() +{ - fn parentheses_with_comma() + // test.case( "impls1 basic" ); { - // test.case( "impls1 basic" ); + impls1! { - - impls1! + fn f1() -> i32 { - fn f1() -> i32 - { - println!( "f1" ); - 13 - } - }; - - tests_index!( f1, ); + println!( "f1" ); + 13 + } + }; - a_id!( f1(), 13 ); - } + tests_index!( f1, ); + a_id!( f1(), 13 ); } +} - fn parentheses_without_comma() +#[ test ] +fn parentheses_without_comma() +{ + + // test.case( "impls1 basic" ); { - // test.case( "impls1 basic" ); + impls1! { - - impls1! + fn f1() -> i32 { - fn f1() -> i32 - { - println!( "f1" ); - 13 - } - }; - - tests_index!( f1 ); + println!( "f1" ); + 13 + } + }; - a_id!( f1(), 13 ); - } + tests_index!( f1 ); + a_id!( f1(), 13 ); } } // -tests_index! -{ - - empty_with_comma, - empty_without_comma, - with_comma, - without_comma, - parentheses_with_comma, - parentheses_without_comma, - -} +// tests_index! +// { +// +// empty_with_comma, +// empty_without_comma, +// with_comma, +// without_comma, +// parentheses_with_comma, +// parentheses_without_comma, +// +// } diff --git a/module/core/impls_index/tests/smoke_test.rs b/module/core/impls_index/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/core/impls_index/tests/smoke_test.rs +++ b/module/core/impls_index/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/core/impls_index/tests/tests.rs b/module/core/impls_index/tests/tests.rs index 7d4038e715..7cee7cbf9b 100644 --- a/module/core/impls_index/tests/tests.rs +++ b/module/core/impls_index/tests/tests.rs @@ -1,9 +1,9 @@ +//! All tests. + +#![ allow( unused_imports ) ] include!( "../../../../module/step/meta/src/module/terminal.rs" ); #[ allow( unused_imports ) ] use impls_index as the_module; -#[ allow( unused_imports ) ] -use test_tools::exposed::*; - mod inc; diff --git a/module/core/impls_index_meta/Cargo.toml b/module/core/impls_index_meta/Cargo.toml index ce629d78b3..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.7.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 e3e9e057cf..a23529f45b 100644 --- a/module/core/impls_index_meta/License +++ b/module/core/impls_index_meta/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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 1ae6c3ee9b..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 ) => { - ::impls_index::fn_rename! + // 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 6d5ef8559f..72c80c1308 100644 --- a/module/core/include_md/License +++ b/module/core/include_md/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/include_md/tests/smoke_test.rs b/module/core/include_md/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/core/include_md/tests/smoke_test.rs +++ b/module/core/include_md/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/core/inspect_type/Cargo.toml b/module/core/inspect_type/Cargo.toml index cebba6d857..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.10.0" +version = "0.14.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -32,7 +32,8 @@ enabled = [] [dependencies] [dev-dependencies] -test_tools = { workspace = true } +# this crate should not rely on test_tools to exclude cyclic dependencies +# test_tools = { workspace = true } [build-dependencies] rustc_version = "0.4" diff --git a/module/core/inspect_type/License b/module/core/inspect_type/License index 6d5ef8559f..72c80c1308 100644 --- a/module/core/inspect_type/License +++ b/module/core/inspect_type/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/core/inspect_type/Readme.md index e45df40b25..c123b0cc19 100644 --- a/module/core/inspect_type/Readme.md +++ b/module/core/inspect_type/Readme.md @@ -2,7 +2,7 @@ # Module :: inspect_type - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_inspect_type_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_inspect_type_push.yml) [![docs.rs](https://img.shields.io/docsrs/inspect_type?color=e3e8f0&logo=docs.rs)](https://docs.rs/inspect_type) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Finspect_type%2Fexamples%2Finspect_type_trivial.rs,RUN_POSTFIX=--example%20inspect_type_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_inspect_type_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_inspect_type_push.yml) [![docs.rs](https://img.shields.io/docsrs/inspect_type?color=e3e8f0&logo=docs.rs)](https://docs.rs/inspect_type) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Finspect_type%2Fexamples%2Finspect_type_trivial.rs,RUN_POSTFIX=--example%20module%2Fcore%2Finspect_type%2Fexamples%2Finspect_type_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Diagnostic-purpose tools to inspect type of a variable and its size. diff --git a/module/core/inspect_type/build.rs b/module/core/inspect_type/build.rs index e1ddc05383..006a6376e7 100644 --- a/module/core/inspect_type/build.rs +++ b/module/core/inspect_type/build.rs @@ -1,35 +1,35 @@ //! To have information about channel of Rust compiler. -use rustc_version::{ version, version_meta, Channel }; +// use rustc_version::{ version, version_meta, Channel }; fn main() { // Assert we haven't travelled back in time - assert!( version().unwrap().major >= 1 ); + assert!( rustc_version::version().unwrap().major >= 1 ); - // Set cfg flags depending on release channel - match version_meta().unwrap().channel - { - Channel::Stable => - { - println!("cargo:rustc-cfg=RUSTC_IS_STABLE"); - println!("cargo:rustc-check-cfg=cfg(RUSTC_IS_STABLE)"); - } - Channel::Beta => - { - println!("cargo:rustc-cfg=RUSTC_IS_BETA"); - println!("cargo:rustc-check-cfg=cfg(RUSTC_IS_BETA)"); - } - Channel::Nightly => - { - println!("cargo:rustc-cfg=RUSTC_IS_NIGHTLY"); - println!("cargo:rustc-check-cfg=cfg(RUSTC_IS_NIGHTLY)"); - } - Channel::Dev => - { - println!("cargo:rustc-cfg=RUSTC_IS_DEV"); - println!("cargo:rustc-check-cfg=cfg(RUSTC_IS_DEV)"); - } - } + // // Set cfg flags depending on release channel + // match version_meta().unwrap().channel + // { + // Channel::Stable => + // { + // println!("cargo:rustc-cfg=RUSTC_IS_STABLE"); + // println!("cargo:rustc-check-cfg=cfg(RUSTC_IS_STABLE)"); + // } + // Channel::Beta => + // { + // println!("cargo:rustc-cfg=RUSTC_IS_BETA"); + // println!("cargo:rustc-check-cfg=cfg(RUSTC_IS_BETA)"); + // } + // Channel::Nightly => + // { + // println!("cargo:rustc-cfg=RUSTC_IS_NIGHTLY"); + // println!("cargo:rustc-check-cfg=cfg(RUSTC_IS_NIGHTLY)"); + // } + // Channel::Dev => + // { + // println!("cargo:rustc-cfg=RUSTC_IS_DEV"); + // println!("cargo:rustc-check-cfg=cfg(RUSTC_IS_DEV)"); + // } + // } } diff --git a/module/core/inspect_type/examples/inspect_type_trivial.rs b/module/core/inspect_type/examples/inspect_type_trivial.rs index 9f616a8204..260687cf71 100644 --- a/module/core/inspect_type/examples/inspect_type_trivial.rs +++ b/module/core/inspect_type/examples/inspect_type_trivial.rs @@ -1,5 +1,5 @@ //! qqq : write proper description -#![ cfg_attr( feature = "type_name_of_val", feature( type_name_of_val ) ) ] +// #![ cfg_attr( feature = "type_name_of_val", feature( type_name_of_val ) ) ] // // #![ cfg_attr( feature = "nightly", feature( type_name_of_val ) ) ] // #![ rustversion::attr( nightly, feature( type_name_of_val ) ) ] diff --git a/module/core/inspect_type/src/lib.rs b/module/core/inspect_type/src/lib.rs index 27e60ce850..06ede7e31c 100644 --- a/module/core/inspect_type/src/lib.rs +++ b/module/core/inspect_type/src/lib.rs @@ -2,9 +2,12 @@ #![ 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/inspect_type/latest/inspect_type/" ) ] #![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] +#![ allow( unexpected_cfgs ) ] +// xxx : qqq : no need in nightly anymore +// #[ allow( unexpected_cfgs ) ] // #[ cfg( RUSTC_IS_NIGHTLY ) ] -#[ cfg( not( RUSTC_IS_STABLE ) ) ] +// #[ cfg( not( RUSTC_IS_STABLE ) ) ] mod nightly { @@ -94,7 +97,7 @@ pub mod prelude // #[ rustversion::nightly ] // #[ cfg( feature = "type_name_of_val" ) ] // #[ cfg( RUSTC_IS_NIGHTLY ) ] - #[ cfg( not( RUSTC_IS_STABLE ) ) ] + // #[ cfg( not( RUSTC_IS_STABLE ) ) ] #[ doc( inline ) ] pub use super::nightly::*; } diff --git a/module/core/inspect_type/tests/inc/inspect_type_test.rs b/module/core/inspect_type/tests/inc/inspect_type_test.rs index 58eb0b82b1..78bb0ecc2f 100644 --- a/module/core/inspect_type/tests/inc/inspect_type_test.rs +++ b/module/core/inspect_type/tests/inc/inspect_type_test.rs @@ -1,53 +1,35 @@ + #[ allow( unused_imports ) ] use super::*; // -// #[ test_tools::nightly ] -// #[ cfg( feature = "nightly" ) ] -// #[ cfg( RUSTC_IS_NIGHTLY ) ] -#[ cfg( not( RUSTC_IS_STABLE ) ) ] -tests_impls! +#[ test ] +fn inspect_to_str_type_of_test() { - fn inspect_to_str_type_of_test() - { - - let exp = "sizeof( &[1, 2, 3][..] : &[i32] ) = 16".to_string(); - let got = the_module::inspect_to_str_type_of!( &[ 1, 2, 3 ][ .. ] ); - a_id!( got, exp ); - - let exp = "sizeof( &[1, 2, 3] : &[i32; 3] ) = 8".to_string(); - let got = the_module::inspect_to_str_type_of!( &[ 1, 2, 3 ] ); - a_id!( got, exp ); - - } - - // - - fn inspect_type_of_macro() - { + let exp = "sizeof( &[1, 2, 3][..] : &[i32] ) = 16".to_string(); + let got = the_module::inspect_to_str_type_of!( &[ 1, 2, 3 ][ .. ] ); + assert_eq!( got, exp ); - let exp = "sizeof( &[1, 2, 3][..] : &[i32] ) = 16".to_string(); - let got = the_module::inspect_type_of!( &[ 1, 2, 3 ][ .. ] ); - a_id!( got, exp ); - - let exp = "sizeof( &[1, 2, 3] : &[i32; 3] ) = 8".to_string(); - let got = the_module::inspect_type_of!( &[ 1, 2, 3 ] ); - a_id!( got, exp ); - - } + let exp = "sizeof( &[1, 2, 3] : &[i32; 3] ) = 8".to_string(); + let got = the_module::inspect_to_str_type_of!( &[ 1, 2, 3 ] ); + assert_eq!( got, exp ); } // -// #[ test_tools::nightly ] -// #[ cfg( feature = "nightly" ) ] -// #[ cfg( RUSTC_IS_NIGHTLY ) ] -#[ cfg( not( RUSTC_IS_STABLE ) ) ] -tests_index! +#[ test ] +fn inspect_type_of_macro() { - inspect_to_str_type_of_test, - inspect_type_of_macro, + + let exp = "sizeof( &[1, 2, 3][..] : &[i32] ) = 16".to_string(); + let got = the_module::inspect_type_of!( &[ 1, 2, 3 ][ .. ] ); + assert_eq!( got, exp ); + + let exp = "sizeof( &[1, 2, 3] : &[i32; 3] ) = 8".to_string(); + let got = the_module::inspect_type_of!( &[ 1, 2, 3 ] ); + assert_eq!( got, exp ); + } diff --git a/module/core/inspect_type/tests/inc/mod.rs b/module/core/inspect_type/tests/inc/mod.rs index d8be619a97..9e35103f83 100644 --- a/module/core/inspect_type/tests/inc/mod.rs +++ b/module/core/inspect_type/tests/inc/mod.rs @@ -1,4 +1,4 @@ -#[ allow( unused_imports ) ] use super::*; +// use test_tools::exposed::*; mod inspect_type_test; diff --git a/module/core/inspect_type/tests/smoke_test.rs b/module/core/inspect_type/tests/smoke_test.rs index 828e9b016b..ee06731048 100644 --- a/module/core/inspect_type/tests/smoke_test.rs +++ b/module/core/inspect_type/tests/smoke_test.rs @@ -1,14 +1,13 @@ - - -#[ test ] -fn local_smoke_test() -{ - ::test_tools::smoke_test_for_local_run(); -} - - -#[ test ] -fn published_smoke_test() -{ - ::test_tools::smoke_test_for_published_run(); -} +//! Smoke testing of the package. + +// #[ 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/core/inspect_type/tests/tests.rs b/module/core/inspect_type/tests/tests.rs index 1b4624edf8..4ac3f797ab 100644 --- a/module/core/inspect_type/tests/tests.rs +++ b/module/core/inspect_type/tests/tests.rs @@ -1,3 +1,7 @@ +//! All Tests +#![ allow( unused_imports ) ] + +// #![ allow( unexpected_cfgs ) ] // #![ no_std ] // #![ cfg_attr( feature = "no_std", no_std ) ] @@ -5,16 +9,12 @@ // #![ test_tools::nightly ] // #![ cfg_attr( feature = "type_name_of_val", feature( type_name_of_val ) ) ] // #![ cfg_attr( rustversion::nightly, feature( type_name_of_val ) ) ] -// #![cfg_attr(docsrs, feature(doc_cfg))] +// #![ cfg_attr( docsrs, feature( doc_cfg ) ) ] // // #![ cfg_attr( feature = "nightly", feature( type_name_of_val ) ) ] // #![ cfg_attr( feature = "nightly", feature( trace_macros ) ) ] // #![ cfg_attr( feature = "nightly", feature( meta_idents_concat ) ) ] // #![ cfg_attr( RUSTC_IS_NIGHTLY, feature( type_name_of_val ) ) ] -#[ allow( unused_imports ) ] use inspect_type as the_module; -#[ allow( unused_imports ) ] -use test_tools::exposed::*; - mod inc; diff --git a/module/core/interval_adapter/Cargo.toml b/module/core/interval_adapter/Cargo.toml index 40b7639448..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.23.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 6d5ef8559f..72c80c1308 100644 --- a/module/core/interval_adapter/License +++ b/module/core/interval_adapter/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/core/interval_adapter/Readme.md index 19cfc05f9e..4ad064b2fc 100644 --- a/module/core/interval_adapter/Readme.md +++ b/module/core/interval_adapter/Readme.md @@ -1,11 +1,11 @@ -# Module :: interval_adapter +# Module :: `interval_adapter` - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_interval_adapter_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_interval_adapter_push.yml) [![docs.rs](https://img.shields.io/docsrs/interval_adapter?color=e3e8f0&logo=docs.rs)](https://docs.rs/interval_adapter) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Finterval_adapter%2Fexamples%2Finterval_adapter_trivial.rs,RUN_POSTFIX=--example%20interval_adapter_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_interval_adapter_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_interval_adapter_push.yml) [![docs.rs](https://img.shields.io/docsrs/interval_adapter?color=e3e8f0&logo=docs.rs)](https://docs.rs/interval_adapter) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Finterval_adapter%2Fexamples%2Finterval_adapter_trivial.rs,RUN_POSTFIX=--example%20module%2Fcore%2Finterval_adapter%2Fexamples%2Finterval_adapter_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) -Integer interval adapter for both Range and RangeInclusive. +Integer interval adapter for both Range and `RangeInclusive`. Let's assume you have a function which should accept Interval. But you don't want to limit caller of the function to either half-open interval `core::ops::Range` or closed one `core::ops::RangeInclusive` you want allow to use anyone of iterable interval. To make that work smoothly use `IterableInterval`. Both `core::ops::Range` and `core::ops::RangeInclusive` implement the trait, also it's possible to work with non-iterable intervals, like ( -Infinity .. +Infinity ). diff --git a/module/core/interval_adapter/src/lib.rs b/module/core/interval_adapter/src/lib.rs index 4684d69850..6bf7fb7c55 100644 --- a/module/core/interval_adapter/src/lib.rs +++ b/module/core/interval_adapter/src/lib.rs @@ -4,16 +4,18 @@ #![ doc( html_root_url = "https://docs.rs/winterval/latest/winterval/" ) ] #![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] -/// Internal namespace. +/// Define a private namespace for all its items. #[ cfg( feature = "enabled" ) ] mod private { #[ doc( inline ) ] #[ allow( unused_imports ) ] + #[ allow( clippy::pub_use ) ] pub use core::ops::Bound; #[ doc( inline ) ] #[ allow( unused_imports ) ] + #[ allow( clippy::pub_use ) ] pub use core::ops::RangeBounds; use core::cmp::{ PartialEq, Eq }; @@ -21,6 +23,7 @@ mod private // xxx : seal it + #[ allow( clippy::wrong_self_convention ) ] /// Extend bound adding few methods. pub trait BoundExt< T > where @@ -39,23 +42,25 @@ mod private isize : Into< T >, { #[ inline( always ) ] + #[ allow( clippy::arithmetic_side_effects, clippy::implicit_return, clippy::pattern_type_mismatch ) ] fn into_left_closed( &self ) -> T { match self { - Bound::Included( v ) => *v, - Bound::Excluded( v ) => *v + 1.into(), + Bound::Included( value ) => *value, + Bound::Excluded( value ) => *value + 1.into(), Bound::Unbounded => 0.into(), // Bound::Unbounded => isize::MIN.into(), } } #[ inline( always ) ] + #[ allow( clippy::arithmetic_side_effects, clippy::implicit_return, clippy::pattern_type_mismatch ) ] fn into_right_closed( &self ) -> T { match self { - Bound::Included( v ) => *v, - Bound::Excluded( v ) => *v - 1.into(), + Bound::Included( value ) => *value, + Bound::Excluded( value ) => *value - 1.into(), Bound::Unbounded => isize::MAX.into(), } } @@ -97,6 +102,7 @@ mod private fn right( &self ) -> Bound< T >; /// Interval in closed format as pair of numbers. /// To convert open endpoint to closed add or subtract one. + #[ allow( clippy::implicit_return ) ] #[ inline( always ) ] fn bounds( &self ) -> ( Bound< T >, Bound< T > ) { @@ -104,18 +110,21 @@ mod private } /// The left endpoint of the interval, converting interval into closed one. + #[ allow( clippy::implicit_return ) ] #[ inline( always ) ] fn closed_left( &self ) -> T { self.left().into_left_closed() } /// The right endpoint of the interval, converting interval into closed one. + #[ allow( clippy::implicit_return ) ] #[ inline( always ) ] fn closed_right( &self ) -> T { self.right().into_right_closed() } /// Length of the interval, converting interval into closed one. + #[ allow( clippy::implicit_return, clippy::arithmetic_side_effects ) ] #[ inline( always ) ] fn closed_len( &self ) -> T { @@ -123,6 +132,7 @@ mod private self.closed_right() - self.closed_left() + one } /// Interval in closed format as pair of numbers, converting interval into closed one. + #[ allow( clippy::implicit_return ) ] #[ inline( always ) ] fn closed( &self ) -> ( T, T ) { @@ -130,6 +140,7 @@ mod private } /// Convert to interval in canonical format. + #[ allow( unknown_lints, clippy::implicit_return ) ] #[ inline( always ) ] fn canonical( &self ) -> Interval< T > { @@ -144,7 +155,6 @@ mod private /// `NonIterableInterval` it does not implement iterator unlike `IterableInterval`. /// `IterableInterval` inherits all methods of `NonIterableInterval`. /// - pub trait IterableInterval< T = isize > where Self : IntoIterator< Item = T > + NonIterableInterval< T >, @@ -166,16 +176,18 @@ mod private /// /// Canonical implementation of interval. Other implementations of interval is convertible to it. /// - /// Both [core::ops::Range], [core::ops::RangeInclusive] are convertable to [crate::Interval] + /// Both [`core::ops::Range`], [`core::ops::RangeInclusive`] are convertable to [`crate::Interval`] /// - + #[ allow( clippy::used_underscore_binding ) ] #[ derive( PartialEq, Eq, Debug, Clone, Copy ) ] pub struct Interval< T = isize > where T : EndPointTrait< T >, isize : Into< T >, { + /// Left _left : Bound< T >, + /// Right _right : Bound< T >, } @@ -185,15 +197,18 @@ mod private isize : Into< T >, { /// Constructor of an interval. Expects closed interval in arguments. + #[ allow( unknown_lints, clippy::implicit_return ) ] + #[ inline ] pub fn new( left : Bound< T >, right : Bound< T > ) -> Self { Self { _left : left, _right : right } } /// Convert to interval in canonical format. + #[ allow( clippy::implicit_return ) ] #[ inline( always ) ] pub fn iter< It >( &self ) -> impl Iterator< Item = T > { - ( &self ).into_iter() + self.into_iter() } } @@ -208,6 +223,7 @@ mod private { type Item = T; type IntoIter = IntervalIterator< T >; + #[ allow( clippy::implicit_return ) ] #[ inline( always ) ] fn into_iter( self ) -> Self::IntoIter { @@ -222,6 +238,7 @@ mod private { type Item = T; type IntoIter = IntervalIterator< T >; + #[ allow( unknown_lints, clippy::implicit_return ) ] #[ inline( always ) ] fn into_iter( self ) -> Self::IntoIter { @@ -229,13 +246,16 @@ mod private } } + /// qqq: Documentation #[ derive( Debug ) ] pub struct IntervalIterator< T > where T : EndPointTrait< T >, isize : Into< T >, { + /// current current : T, + /// right right : T, } @@ -245,6 +265,7 @@ mod private isize : Into< T >, { /// Constructor. + #[ allow( clippy::used_underscore_binding, clippy::implicit_return ) ] pub fn new( ins : Interval< T > ) -> Self { let current = ins._left.into_left_closed(); @@ -253,12 +274,14 @@ mod private } } + #[ allow( clippy::missing_trait_methods ) ] impl< T > Iterator for IntervalIterator< T > where T : EndPointTrait< T >, isize : Into< T >, { type Item = T; + #[ allow( clippy::implicit_return, clippy::arithmetic_side_effects ) ] #[ inline( always ) ] fn next( &mut self ) -> Option< Self::Item > { @@ -298,17 +321,20 @@ mod private // } // } + #[ allow( clippy::used_underscore_binding, clippy::missing_trait_methods ) ] impl< T > NonIterableInterval< T > for Interval< T > where T : EndPointTrait< T >, isize : Into< T >, { + #[ allow( clippy::implicit_return ) ] #[ inline( always ) ] fn left( &self ) -> Bound< T > { self._left } + #[ allow( clippy::implicit_return ) ] #[ inline( always ) ] fn right( &self ) -> Bound< T > { @@ -316,17 +342,20 @@ mod private } } + #[ allow( clippy::missing_trait_methods ) ] impl< T > NonIterableInterval< T > for core::ops::Range< T > where T : EndPointTrait< T >, isize : Into< T >, { + #[ allow( clippy::implicit_return ) ] #[ inline( always ) ] fn left( &self ) -> Bound< T > { Bound::Included( self.start ) } + #[ allow( clippy::implicit_return ) ] #[ inline( always ) ] fn right( &self ) -> Bound< T > { @@ -334,17 +363,20 @@ mod private } } + #[ allow( clippy::missing_trait_methods ) ] impl< T > NonIterableInterval< T > for core::ops::RangeInclusive< T > where T : EndPointTrait< T >, isize : Into< T >, { + #[ allow( clippy::implicit_return ) ] #[ inline( always ) ] fn left( &self ) -> Bound< T > { Bound::Included( *self.start() ) } + #[ allow( clippy::implicit_return ) ] #[ inline( always ) ] fn right( &self ) -> Bound< T > { @@ -352,17 +384,20 @@ mod private } } + #[ allow( clippy::missing_trait_methods ) ] impl< T > NonIterableInterval< T > for core::ops::RangeTo< T > where T : EndPointTrait< T >, isize : Into< T >, { + #[ allow( clippy::implicit_return ) ] #[ inline( always ) ] fn left( &self ) -> Bound< T > { Bound::Unbounded } + #[ allow( clippy::implicit_return ) ] #[ inline( always ) ] fn right( &self ) -> Bound< T > { @@ -370,17 +405,20 @@ mod private } } + #[ allow( clippy::missing_trait_methods ) ] impl< T > NonIterableInterval< T > for core::ops::RangeToInclusive< T > where T : EndPointTrait< T >, isize : Into< T >, { + #[ allow( clippy::implicit_return ) ] #[ inline( always ) ] fn left( &self ) -> Bound< T > { Bound::Unbounded } + #[ allow( clippy::implicit_return )] #[ inline( always ) ] fn right( &self ) -> Bound< T > { @@ -388,17 +426,20 @@ mod private } } + #[allow( clippy::missing_trait_methods ) ] impl< T > NonIterableInterval< T > for core::ops::RangeFrom< T > where T : EndPointTrait< T >, isize : Into< T >, { + #[ allow( clippy::implicit_return )] #[ inline( always ) ] fn left( &self ) -> Bound< T > { Bound::Included( self.start ) } + #[ allow( clippy::implicit_return )] #[ inline( always ) ] fn right( &self ) -> Bound< T > { @@ -406,17 +447,20 @@ mod private } } + #[ allow( clippy::missing_trait_methods ) ] impl< T > NonIterableInterval< T > for core::ops::RangeFull where T : EndPointTrait< T >, isize : Into< T >, { + #[ allow( clippy::implicit_return )] #[ inline( always ) ] fn left( &self ) -> Bound< T > { Bound::Unbounded } + #[ allow( clippy::implicit_return )] #[ inline( always ) ] fn right( &self ) -> Bound< T > { @@ -424,17 +468,20 @@ mod private } } + #[ allow( clippy::missing_trait_methods ) ] impl< T > NonIterableInterval< T > for ( T, T ) where T : EndPointTrait< T >, isize : Into< T >, { + #[ allow( clippy::implicit_return )] #[ inline( always ) ] fn left( &self ) -> Bound< T > { Bound::Included( self.0 ) } + #[ allow( clippy::implicit_return )] #[ inline( always ) ] fn right( &self ) -> Bound< T > { @@ -442,17 +489,22 @@ mod private } } + #[ allow( clippy::missing_trait_methods ) ] impl< T > NonIterableInterval< T > for ( Bound< T >, Bound< T > ) where T : EndPointTrait< T >, isize : Into< T >, { + #[ allow( unknown_lints )] + #[ allow( clippy::implicit_return )] #[ inline( always ) ] fn left( &self ) -> Bound< T > { self.0 } + #[ allow( unknown_lints )] + #[ allow( clippy::implicit_return )] #[ inline( always ) ] fn right( &self ) -> Bound< T > { @@ -460,17 +512,21 @@ mod private } } + #[ allow( clippy::missing_trait_methods ) ] impl< T > NonIterableInterval< T > for [ T ; 2 ] where T : EndPointTrait< T >, isize : Into< T >, { + #[ allow( clippy::implicit_return )] #[ inline( always ) ] fn left( &self ) -> Bound< T > { Bound::Included( self[ 0 ] ) } + #[ allow( unknown_lints )] + #[ allow( clippy::implicit_return )] #[ inline( always ) ] fn right( &self ) -> Bound< T > { @@ -478,17 +534,20 @@ mod private } } + #[ allow( clippy::missing_trait_methods ) ] impl< T > NonIterableInterval< T > for [ Bound< T > ; 2 ] where T : EndPointTrait< T >, isize : Into< T >, { + #[ allow( clippy::implicit_return )] #[ inline( always ) ] fn left( &self ) -> Bound< T > { self[ 0 ] } + #[ allow( clippy::implicit_return )] #[ inline( always ) ] fn right( &self ) -> Bound< T > { @@ -499,7 +558,7 @@ mod private // = // from for std // = - + /// qqq: documentation macro_rules! impl_interval_from { {} => {}; @@ -519,7 +578,7 @@ mod private { let _left = NonIterableInterval::left( &src ); let _right = NonIterableInterval::right( &src ); - Self { _left, _right } + return Self { _left, _right } } } }; @@ -564,6 +623,9 @@ mod private isize : Into< T >, Interval< T > : From< Self >, { + #[ allow( unknown_lints )] + #[ allow( clippy::implicit_return )] + #[ inline ] fn into_interval( self ) -> Interval< T > { From::from( self ) @@ -576,6 +638,7 @@ mod private #[ allow( unused_imports ) ] #[ cfg( feature = "enabled" ) ] // #[ allow( unused_imports ) ] +#[ allow( clippy::pub_use ) ] pub use own::*; /// Own namespace of the module. @@ -583,7 +646,8 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { - use super::*; + use super::orphan; + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] #[ doc( inline ) ] pub use orphan::*; } @@ -593,8 +657,9 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { - use super::*; + use super::exposed; #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] pub use exposed::*; } @@ -603,10 +668,12 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { - use super::*; + use super::{ prelude, private }; #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] pub use prelude::*; #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] pub use private:: { Bound, @@ -620,7 +687,7 @@ pub mod exposed } // #[ doc( inline ) ] -#[ allow( unused_imports ) ] +// #[ allow( unused_imports ) ] // #[ cfg( feature = "enabled" ) ] // #[ allow( unused_imports ) ] // pub use exposed::*; @@ -630,8 +697,9 @@ pub mod exposed #[ allow( unused_imports ) ] pub mod prelude { - use super::*; + use super::private; #[ doc( inline ) ] + #[ allow( clippy::useless_attribute, clippy::pub_use ) ] pub use private:: { IterableInterval, diff --git a/module/core/is_slice/Cargo.toml b/module/core/is_slice/Cargo.toml index b0945c3613..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.9.0" +version = "0.13.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -32,4 +32,5 @@ enabled = [] [dependencies] [dev-dependencies] -test_tools = { workspace = true } +# this crate should not rely on test_tools to exclude cyclic dependencies +# test_tools = { workspace = true } diff --git a/module/core/is_slice/License b/module/core/is_slice/License index e3e9e057cf..a23529f45b 100644 --- a/module/core/is_slice/License +++ b/module/core/is_slice/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/core/is_slice/Readme.md index cd6d9eadac..d4440be29b 100644 --- a/module/core/is_slice/Readme.md +++ b/module/core/is_slice/Readme.md @@ -2,7 +2,7 @@ # Module :: is_slice - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_is_slice_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_is_slice_push.yml) [![docs.rs](https://img.shields.io/docsrs/is_slice?color=e3e8f0&logo=docs.rs)](https://docs.rs/is_slice) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fis_slice%2Fexamples%2Fis_slice_trivial.rs,RUN_POSTFIX=--example%20is_slice_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_is_slice_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_is_slice_push.yml) [![docs.rs](https://img.shields.io/docsrs/is_slice?color=e3e8f0&logo=docs.rs)](https://docs.rs/is_slice) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fis_slice%2Fexamples%2Fis_slice_trivial.rs,RUN_POSTFIX=--example%20module%2Fcore%2Fis_slice%2Fexamples%2Fis_slice_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Macro to answer the question: is it a slice? diff --git a/module/core/is_slice/tests/inc/is_slice_test.rs b/module/core/is_slice/tests/inc/is_slice_test.rs index 19d026fde5..ae247a9cb3 100644 --- a/module/core/is_slice/tests/inc/is_slice_test.rs +++ b/module/core/is_slice/tests/inc/is_slice_test.rs @@ -2,33 +2,23 @@ use super::*; // -tests_impls! +#[ test ] +fn is_slice_basic() { - #[ test ] - fn is_slice_basic() - { - let src : &[ i32 ] = &[ 1, 2, 3 ]; - a_id!( the_module::is_slice!( src ), true ); - a_id!( the_module::is_slice!( &[ 1, 2, 3 ][ .. ] ), true ); - a_id!( the_module::is_slice!( &[ 1, 2, 3 ] ), false ); + let src : &[ i32 ] = &[ 1, 2, 3 ]; + assert_eq!( the_module::is_slice!( src ), true ); + assert_eq!( the_module::is_slice!( &[ 1, 2, 3 ][ .. ] ), true ); + assert_eq!( the_module::is_slice!( &[ 1, 2, 3 ] ), false ); - // the_module::inspect_type_of!( &[ 1, 2, 3 ][ .. ] ); - // the_module::inspect_type_of!( &[ 1, 2, 3 ] ); + // the_module::inspect_type_of!( &[ 1, 2, 3 ][ .. ] ); + // the_module::inspect_type_of!( &[ 1, 2, 3 ] ); - a_id!( the_module::is_slice!( vec!( 1, 2, 3 ) ), false ); - a_id!( the_module::is_slice!( 13_f32 ), false ); - a_id!( the_module::is_slice!( true ), false ); - let src = false; - a_id!( the_module::is_slice!( src ), false ); - a_id!( the_module::is_slice!( Box::new( true ) ), false ); - let src = Box::new( true ); - a_id!( the_module::is_slice!( src ), false ); - } -} - -// - -tests_index! -{ - is_slice_basic, + assert_eq!( the_module::is_slice!( vec!( 1, 2, 3 ) ), false ); + assert_eq!( the_module::is_slice!( 13_f32 ), false ); + assert_eq!( the_module::is_slice!( true ), false ); + let src = false; + assert_eq!( the_module::is_slice!( src ), false ); + assert_eq!( the_module::is_slice!( Box::new( true ) ), false ); + let src = Box::new( true ); + assert_eq!( the_module::is_slice!( src ), false ); } diff --git a/module/core/is_slice/tests/inc/mod.rs b/module/core/is_slice/tests/inc/mod.rs index d2e9305da9..3e91d401d9 100644 --- a/module/core/is_slice/tests/inc/mod.rs +++ b/module/core/is_slice/tests/inc/mod.rs @@ -1,6 +1,5 @@ -#![ no_std ] -#[ allow( unused_imports ) ] use super::*; +// use test_tools::exposed::*; mod is_slice_test; diff --git a/module/core/is_slice/tests/is_slice_tests.rs b/module/core/is_slice/tests/is_slice_tests.rs index 6aad89f853..b859cf6263 100644 --- a/module/core/is_slice/tests/is_slice_tests.rs +++ b/module/core/is_slice/tests/is_slice_tests.rs @@ -1,11 +1,11 @@ -// #![cfg_attr(docsrs, feature(doc_cfg))] +//! Smoke testing of the package. + +// #![ cfg_attr( docsrs, feature( doc_cfg ) ) ] // #![ cfg_attr( feature = "nightly", feature( type_name_of_val ) ) ] // #![ feature( type_name_of_val ) ] // #![ feature( trace_macros ) ] // #![ feature( meta_idents_concat ) ] +#![ allow( unused_imports ) ] -use test_tools::exposed::*; use is_slice as the_module; - -// #[ path = "./inc.rs" ] mod inc; diff --git a/module/core/is_slice/tests/smoke_test.rs b/module/core/is_slice/tests/smoke_test.rs index 828e9b016b..ee06731048 100644 --- a/module/core/is_slice/tests/smoke_test.rs +++ b/module/core/is_slice/tests/smoke_test.rs @@ -1,14 +1,13 @@ - - -#[ test ] -fn local_smoke_test() -{ - ::test_tools::smoke_test_for_local_run(); -} - - -#[ test ] -fn published_smoke_test() -{ - ::test_tools::smoke_test_for_published_run(); -} +//! Smoke testing of the package. + +// #[ 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/core/iter_tools/Cargo.toml b/module/core/iter_tools/Cargo.toml index ea0b221df3..442572a8a5 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.20.0" +version = "0.29.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/iter_tools/License b/module/core/iter_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/core/iter_tools/License +++ b/module/core/iter_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/core/iter_tools/Readme.md index 4aaebd7c0f..c4f8e91780 100644 --- a/module/core/iter_tools/Readme.md +++ b/module/core/iter_tools/Readme.md @@ -1,8 +1,8 @@ -# Module :: iter_tools +# Module :: `iter_tools` - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_iter_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_iter_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/iter_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/iter_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fiter_tools%2Fexamples%2Fiter_tools_trivial.rs,RUN_POSTFIX=--example%20iter_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_iter_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_iter_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/iter_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/iter_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fiter_tools%2Fexamples%2Fiter_tools_trivial.rs,RUN_POSTFIX=--example%20module%2Fcore%2Fiter_tools%2Fexamples%2Fiter_tools_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Collection of general purpose tools to iterate. Currently it simply reexports itertools. @@ -50,7 +50,7 @@ cd wTools cd examples/iter_tools_trivial cargo run ``` -` + ### Try out from the repository diff --git a/module/core/iter_tools/src/iter.rs b/module/core/iter_tools/src/iter.rs index 86fbda3f33..aa5d68128c 100644 --- a/module/core/iter_tools/src/iter.rs +++ b/module/core/iter_tools/src/iter.rs @@ -5,6 +5,8 @@ mod private #[ allow( unused_imports ) ] use crate::*; // use ::itertools::process_results; + + #[ cfg( feature = "iter_trait" ) ] use clone_dyn_types::CloneDyn; /// Trait that encapsulates an iterator with specific characteristics and implemetning `CloneDyn`. @@ -58,7 +60,6 @@ mod private /// } /// /// ``` - #[ cfg( feature = "iter_trait" ) ] pub trait _IterTrait< 'a, T > where @@ -162,7 +163,6 @@ mod private pub type BoxedIter< 'a, T > = Box< dyn _IterTrait< 'a, T > + 'a >; /// Extension of iterator. - // zzz : review #[ cfg( feature = "iter_ext" ) ] #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] @@ -171,6 +171,8 @@ mod private Self : core::iter::Iterator, { /// Iterate each element and return `core::Result::Err` if any element is error. + /// # Errors + /// qqq: errors fn map_result< F, RE, El >( self, f : F ) -> core::result::Result< Vec< El >, RE > where Self : Sized + Clone, @@ -207,6 +209,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use orphan::*; @@ -217,6 +220,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] @@ -267,7 +271,7 @@ pub mod orphan #[ cfg( not( feature = "no_std" ) ) ] #[ doc( inline ) ] - pub use std::iter::zip; + pub use core::iter::zip; } @@ -275,6 +279,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] @@ -291,10 +296,7 @@ pub mod exposed #[ doc( inline ) ] #[ cfg( feature = "iter_trait" ) ] #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] - pub use private:: - { - BoxedIter, - }; + pub use private::BoxedIter; @@ -304,6 +306,7 @@ pub mod exposed #[ allow( unused_imports ) ] pub mod prelude { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] diff --git a/module/core/iter_tools/src/lib.rs b/module/core/iter_tools/src/lib.rs index caa22f5593..e4b744172f 100644 --- a/module/core/iter_tools/src/lib.rs +++ b/module/core/iter_tools/src/lib.rs @@ -32,6 +32,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] @@ -48,6 +49,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; @@ -58,6 +60,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use prelude::*; diff --git a/module/core/iter_tools/tests/smoke_test.rs b/module/core/iter_tools/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/core/iter_tools/tests/smoke_test.rs +++ b/module/core/iter_tools/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/core/macro_tools/Cargo.toml b/module/core/macro_tools/Cargo.toml index cb1e7be33e..25f110709b 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.39.0" +version = "0.53.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 = [] } @@ -109,4 +117,4 @@ clone_dyn_types = { workspace = true, features = [] } former_types = { workspace = true, features = [ "types_component_assign" ] } [dev-dependencies] -test_tools = { workspace = true } +test_tools = { workspace = true } # Added test_tools dependency diff --git a/module/core/macro_tools/License b/module/core/macro_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/core/macro_tools/License +++ b/module/core/macro_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/core/macro_tools/Readme.md index 6d148200b3..37d574ccd1 100644 --- a/module/core/macro_tools/Readme.md +++ b/module/core/macro_tools/Readme.md @@ -1,8 +1,8 @@ -# Module :: proc_macro_tools +# Module :: `proc_macro_tools` - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_macro_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_macro_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/macro_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/macro_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fmacro_tools%2Fexamples%2Fmacro_tools_trivial.rs,RUN_POSTFIX=--example%20macro_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_macro_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_macro_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/macro_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/macro_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fmacro_tools%2Fexamples%2Fmacro_tools_trivial.rs,RUN_POSTFIX=--example%20module%2Fcore%2Fmacro_tools%2Fexamples%2Fmacro_tools_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Tools for writing procedural macros. @@ -67,10 +67,10 @@ using reusable components like `AttributePropertyBoolean`. - `AttributeComponent`: A trait that defines how an attribute should be parsed from a `syn::Attribute`. - `AttributePropertyComponent`: A trait that defines a marker for attribute properties. - `Assign`: A trait that simplifies the logic of assigning fields to a struct. Using a -component-based approach requires each field to have a unique type, which aligns with the -strengths of strongly-typed languages. This method ensures that the logic of -assigning values to fields is encapsulated within the fields themselves, promoting modularity -and reusability. + component-based approach requires each field to have a unique type, which aligns with the + strengths of strongly-typed languages. This method ensures that the logic of + assigning values to fields is encapsulated within the fields themselves, promoting modularity + and reusability. The reusable property components from the library come with parameters that distinguish different properties of the same type. This is useful when an attribute has multiple boolean diff --git a/module/core/macro_tools/src/attr.rs b/module/core/macro_tools/src/attr.rs index d87c7865b2..c600416f38 100644 --- a/module/core/macro_tools/src/attr.rs +++ b/module/core/macro_tools/src/attr.rs @@ -2,9 +2,10 @@ //! Attributes analyzys and manipulation. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; /// Checks if the given iterator of attributes contains an attribute named `debug`. @@ -48,15 +49,15 @@ mod private /// /// assert!( contains_debug, "Expected to find 'debug' attribute" ); /// ``` - /// - + /// # Errors + /// qqq: doc pub fn has_debug< 'a >( attrs : impl Iterator< Item = &'a syn::Attribute > ) -> syn::Result< bool > { for attr in attrs { if let Some( ident ) = attr.path().get_ident() { - let ident_string = format!( "{}", ident ); + let ident_string = format!( "{ident}" ); if ident_string == "debug" { return Ok( true ) @@ -67,7 +68,7 @@ mod private return_syn_err!( "Unknown structure attribute:\n{}", qt!{ attr } ); } } - return Ok( false ) + Ok( false ) } /// Checks if the given attribute name is a standard Rust attribute. @@ -110,8 +111,9 @@ mod private /// assert_eq!( macro_tools::attr::is_standard( "my_attribute" ), false ); /// ``` /// - - pub fn is_standard<'a>( attr_name : &'a str ) -> bool + #[ must_use ] + #[ allow( clippy::match_same_arms ) ] + pub fn is_standard( attr_name : &str ) -> bool { match attr_name { @@ -199,6 +201,7 @@ mod private } } + #[ allow( clippy::iter_without_into_iter ) ] impl AttributesInner { /// Iterator @@ -208,6 +211,7 @@ mod private } } + #[ allow( clippy::default_trait_access ) ] impl syn::parse::Parse for AttributesInner { @@ -274,6 +278,7 @@ mod private } } + #[ allow( clippy::iter_without_into_iter ) ] impl AttributesOuter { /// Iterator @@ -283,6 +288,7 @@ mod private } } + #[ allow( clippy::default_trait_access ) ] impl syn::parse::Parse for AttributesOuter { @@ -425,6 +431,9 @@ mod private /// # Returns /// /// A `syn::Result` containing the constructed component if successful, or an error if the parsing fails. + /// + /// # Errors + /// qqq: doc fn from_meta( attr : &syn::Attribute ) -> syn::Result< Self >; // zzz : redo maybe @@ -440,6 +449,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use orphan::*; @@ -456,6 +466,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; @@ -465,6 +476,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use super::super::attr; diff --git a/module/core/macro_tools/src/attr_prop.rs b/module/core/macro_tools/src/attr_prop.rs index a5e3aeaecd..e981e9803a 100644 --- a/module/core/macro_tools/src/attr_prop.rs +++ b/module/core/macro_tools/src/attr_prop.rs @@ -102,7 +102,7 @@ mod boolean_optional; mod syn; mod syn_optional; -/// Internal namespace. +/// Define a private namespace for all its items. mod private { // use crate::*; @@ -151,6 +151,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use orphan::*; @@ -164,6 +165,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; @@ -173,6 +175,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use super::super::attr_prop; diff --git a/module/core/macro_tools/src/attr_prop/boolean.rs b/module/core/macro_tools/src/attr_prop/boolean.rs index fa786b990d..d57917b576 100644 --- a/module/core/macro_tools/src/attr_prop/boolean.rs +++ b/module/core/macro_tools/src/attr_prop/boolean.rs @@ -3,6 +3,8 @@ //! Defaults to `false`. //! +use core::marker::PhantomData; +#[ allow( clippy::wildcard_imports ) ] use crate::*; // use former_types::Assign; @@ -114,6 +116,7 @@ pub struct AttributePropertyBoolean< Marker = AttributePropertyBooleanMarker >( impl< Marker > AttributePropertyBoolean< Marker > { /// Just unwraps and returns the internal data. + #[ must_use ] #[ inline( always ) ] pub fn internal( self ) -> bool { @@ -122,6 +125,7 @@ impl< Marker > AttributePropertyBoolean< Marker > /// Returns a reference to the internal boolean value. #[ inline( always ) ] + #[ must_use ] pub fn ref_internal( &self ) -> &bool { &self.0 @@ -160,9 +164,10 @@ impl< Marker > syn::parse::Parse for AttributePropertyBoolean< Marker > impl< Marker > From< bool > for AttributePropertyBoolean< Marker > { #[ inline( always ) ] + #[ allow( clippy::default_constructed_unit_structs ) ] fn from( src : bool ) -> Self { - Self( src, Default::default() ) + Self( src, PhantomData::default() ) } } diff --git a/module/core/macro_tools/src/attr_prop/boolean_optional.rs b/module/core/macro_tools/src/attr_prop/boolean_optional.rs index e695db40dd..bbc953c63a 100644 --- a/module/core/macro_tools/src/attr_prop/boolean_optional.rs +++ b/module/core/macro_tools/src/attr_prop/boolean_optional.rs @@ -2,7 +2,8 @@ //! A generic optional boolean attribute property: `Option< bool >`. //! Defaults to `false`. //! - +use core::marker::PhantomData; +#[ allow( clippy::wildcard_imports ) ] use crate::*; use components::Assign; @@ -19,6 +20,7 @@ pub struct AttributePropertyOptionalBoolean< Marker = AttributePropertyOptionalB impl< Marker > AttributePropertyOptionalBoolean< Marker > { /// Just unwraps and returns the internal data. + #[ must_use ] #[ inline( always ) ] pub fn internal( self ) -> Option< bool > { @@ -26,6 +28,7 @@ impl< Marker > AttributePropertyOptionalBoolean< Marker > } /// Returns a reference to the internal optional boolean value. + #[ must_use ] #[ inline( always ) ] pub fn ref_internal( &self ) -> Option< &bool > { @@ -42,6 +45,7 @@ where /// Inserts value of another instance into the option if it is None, then returns a mutable reference to the contained value. /// If another instance does is None then do nothing. #[ inline( always ) ] + #[ allow( clippy::single_match ) ] fn assign( &mut self, component : IntoT ) { let component = component.into(); @@ -73,18 +77,20 @@ impl< Marker > syn::parse::Parse for AttributePropertyOptionalBoolean< Marker > impl< Marker > From< bool > for AttributePropertyOptionalBoolean< Marker > { #[ inline( always ) ] + #[ allow( clippy::default_constructed_unit_structs ) ] fn from( src : bool ) -> Self { - Self( Some( src ), Default::default() ) + Self( Some( src ), PhantomData::default() ) } } impl< Marker > From< Option< bool > > for AttributePropertyOptionalBoolean< Marker > { #[ inline( always ) ] + #[ allow( clippy::default_constructed_unit_structs ) ] fn from( src : Option< bool > ) -> Self { - Self( src, Default::default() ) + Self( src, PhantomData::default() ) } } diff --git a/module/core/macro_tools/src/attr_prop/singletone.rs b/module/core/macro_tools/src/attr_prop/singletone.rs index 0e6970bdd0..1ee3d86266 100644 --- a/module/core/macro_tools/src/attr_prop/singletone.rs +++ b/module/core/macro_tools/src/attr_prop/singletone.rs @@ -11,6 +11,8 @@ //! //! This is useful for attributes that need to enable or disable features or flags. +use core::marker::PhantomData; +#[ allow( clippy::wildcard_imports ) ] use crate::*; // use former_types::Assign; @@ -35,6 +37,7 @@ impl< Marker > AttributePropertySingletone< Marker > { /// Unwraps and returns the internal optional boolean value. + #[ must_use ] #[ inline( always ) ] pub fn internal( self ) -> bool { @@ -42,6 +45,7 @@ impl< Marker > AttributePropertySingletone< Marker > } /// Returns a reference to the internal optional boolean value. + #[ must_use ] #[ inline( always ) ] pub fn ref_internal( &self ) -> &bool { @@ -72,9 +76,10 @@ where impl< Marker > From< bool > for AttributePropertySingletone< Marker > { #[ inline( always ) ] + #[ allow( clippy::default_constructed_unit_structs ) ] fn from( src : bool ) -> Self { - Self( src, Default::default() ) + Self( src, PhantomData::default() ) } } diff --git a/module/core/macro_tools/src/attr_prop/singletone_optional.rs b/module/core/macro_tools/src/attr_prop/singletone_optional.rs index 7d500cc94f..0761d65233 100644 --- a/module/core/macro_tools/src/attr_prop/singletone_optional.rs +++ b/module/core/macro_tools/src/attr_prop/singletone_optional.rs @@ -12,7 +12,8 @@ //! ``` //! //! This is useful for attributes that need to enable or disable features or flags. - +use core::marker::PhantomData; +#[ allow( clippy::wildcard_imports ) ] use crate::*; // use former_types::Assign; @@ -39,7 +40,10 @@ impl< Marker > AttributePropertyOptionalSingletone< Marker > { /// Return bool value: on/off, use argument as default if it's `None`. + /// # Panics + /// qqq: doc #[ inline ] + #[ must_use ] pub fn value( self, default : bool ) -> bool { if self.0.is_none() @@ -51,12 +55,14 @@ impl< Marker > AttributePropertyOptionalSingletone< Marker > /// Unwraps and returns the internal optional boolean value. #[ inline( always ) ] + #[ must_use ] pub fn internal( self ) -> Option< bool > { self.0 } /// Returns a reference to the internal optional boolean value. + #[ must_use ] #[ inline( always ) ] pub fn ref_internal( &self ) -> Option< &bool > { @@ -73,6 +79,7 @@ where /// Inserts value of another instance into the option if it is None, then returns a mutable reference to the contained value. /// If another instance does is None then do nothing. #[ inline( always ) ] + #[ allow( clippy::single_match ) ] fn assign( &mut self, component : IntoT ) { let component = component.into(); @@ -94,18 +101,20 @@ where impl< Marker > From< bool > for AttributePropertyOptionalSingletone< Marker > { #[ inline( always ) ] + #[ allow( clippy::default_constructed_unit_structs ) ] fn from( src : bool ) -> Self { - Self( Some( src ), Default::default() ) + Self( Some( src ), PhantomData::default() ) } } impl< Marker > From< Option< bool > > for AttributePropertyOptionalSingletone< Marker > { #[ inline( always ) ] + #[ allow( clippy::default_constructed_unit_structs ) ] fn from( src : Option< bool > ) -> Self { - Self( src, Default::default() ) + Self( src, PhantomData::default() ) } } diff --git a/module/core/macro_tools/src/attr_prop/syn.rs b/module/core/macro_tools/src/attr_prop/syn.rs index 183ead1a3a..4427a83f22 100644 --- a/module/core/macro_tools/src/attr_prop/syn.rs +++ b/module/core/macro_tools/src/attr_prop/syn.rs @@ -2,6 +2,8 @@ //! Property of an attribute which simply wraps one of the standard `syn` types. //! +use core::marker::PhantomData; +#[ allow( clippy::wildcard_imports ) ] use crate::*; // use former_types::Assign; @@ -108,8 +110,9 @@ impl< T, Marker > From< T > for AttributePropertySyn< T, Marker > where T : syn::parse::Parse + quote::ToTokens { #[ inline( always ) ] + #[ allow( clippy::default_constructed_unit_structs ) ] fn from( src : T ) -> Self { - Self( src, Default::default() ) + Self( src, PhantomData::default() ) } } diff --git a/module/core/macro_tools/src/attr_prop/syn_optional.rs b/module/core/macro_tools/src/attr_prop/syn_optional.rs index 4e5bba2783..e95e46b779 100644 --- a/module/core/macro_tools/src/attr_prop/syn_optional.rs +++ b/module/core/macro_tools/src/attr_prop/syn_optional.rs @@ -1,7 +1,8 @@ //! //! Property of an attribute which simply wraps one of the standard `syn` types and keeps it optional. //! - +use core::marker::PhantomData; +#[ allow( clippy::wildcard_imports ) ] use crate::*; // use former_types::Assign; @@ -46,6 +47,7 @@ where { /// Inserts value of another instance into the option if it is None, then returns a mutable reference to the contained value. /// If another instance does is None then do nothing. + #[ allow( clippy::single_match ) ] #[ inline( always ) ] fn assign( &mut self, component : IntoT ) { @@ -70,9 +72,10 @@ impl< T, Marker > Default for AttributePropertyOptionalSyn< T, Marker > where T : syn::parse::Parse + quote::ToTokens, { + #[ allow( clippy::default_constructed_unit_structs ) ] fn default() -> Self { - Self( None, Default::default() ) + Self( None, PhantomData::default() ) } } @@ -123,9 +126,10 @@ impl< T, Marker > From< T > for AttributePropertyOptionalSyn< T, Marker > where T : syn::parse::Parse + quote::ToTokens { #[ inline( always ) ] + #[ allow( clippy::default_constructed_unit_structs ) ] fn from( src : T ) -> Self { - Self( Some( src ), Default::default() ) + Self( Some( src ), PhantomData::default() ) } } @@ -133,9 +137,10 @@ impl< T, Marker > From< Option< T > > for AttributePropertyOptionalSyn< T, Marke where T : syn::parse::Parse + quote::ToTokens { #[ inline( always ) ] + #[ allow( clippy::default_constructed_unit_structs ) ] fn from( src : Option< T > ) -> Self { - Self( src, Default::default() ) + Self( src, PhantomData::default() ) } } diff --git a/module/core/macro_tools/src/components.rs b/module/core/macro_tools/src/components.rs index 5ff8c6fbe5..7d744f57c1 100644 --- a/module/core/macro_tools/src/components.rs +++ b/module/core/macro_tools/src/components.rs @@ -2,7 +2,7 @@ //! Type-based assigning. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { } @@ -15,6 +15,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use orphan::*; @@ -31,6 +32,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; @@ -40,6 +42,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use super::super::components; diff --git a/module/core/macro_tools/src/container_kind.rs b/module/core/macro_tools/src/container_kind.rs index b7cedc6149..32aae90f93 100644 --- a/module/core/macro_tools/src/container_kind.rs +++ b/module/core/macro_tools/src/container_kind.rs @@ -2,9 +2,10 @@ //! Determine kind of a container. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; // use crate::type_rightmost; @@ -39,7 +40,9 @@ mod private /// let kind = container_kind::of_type( &tree_type ); /// assert_eq!( kind, container_kind::ContainerKind::HashMap ); /// ``` - + /// # Panics + /// qqq: doc + #[ must_use ] pub fn of_type( ty : &syn::Type ) -> ContainerKind { @@ -61,7 +64,7 @@ mod private ContainerKind::No } - /// Return kind of container specified by type. Unlike [of_type] it also understand optional types. + /// Return kind of container specified by type. Unlike [`of_type`] it also understand optional types. /// /// Good to verify `Option< alloc::vec::Vec< i32 > >` is optional vector. /// @@ -75,7 +78,9 @@ mod private /// assert_eq!( kind, container_kind::ContainerKind::HashMap ); /// assert_eq!( optional, true ); /// ``` - + /// # Panics + /// qqq: doc + #[ must_use ] pub fn of_optional( ty : &syn::Type ) -> ( ContainerKind, bool ) { @@ -104,6 +109,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use orphan::*; @@ -122,6 +128,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; @@ -131,6 +138,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use super::super::container_kind; diff --git a/module/core/macro_tools/src/ct.rs b/module/core/macro_tools/src/ct.rs index dd3778e29b..4083f7321c 100644 --- a/module/core/macro_tools/src/ct.rs +++ b/module/core/macro_tools/src/ct.rs @@ -2,7 +2,7 @@ //! Compile-time tools. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { } @@ -19,6 +19,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] @@ -35,6 +36,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; @@ -44,6 +46,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use super::super::ct; diff --git a/module/core/macro_tools/src/derive.rs b/module/core/macro_tools/src/derive.rs index 84ab933fb4..7e754d34d9 100644 --- a/module/core/macro_tools/src/derive.rs +++ b/module/core/macro_tools/src/derive.rs @@ -2,9 +2,10 @@ //! Macro helpers around derive macro and structure [`syn::DeriveInput`]. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use syn::punctuated::Punctuated; @@ -24,8 +25,9 @@ mod private /// }; /// let fields = derive.named_fields( &ast ); /// ``` - - pub fn named_fields< 'a >( ast : &'a syn::DeriveInput ) -> crate::Result< &'a Punctuated< syn::Field, syn::token::Comma > > + /// # Errors + /// qqq: doc + pub fn named_fields( ast : &syn::DeriveInput ) -> crate::Result< &Punctuated< syn::Field, syn::token::Comma > > { let fields = match ast.data @@ -54,6 +56,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use orphan::*; @@ -70,6 +73,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; @@ -79,6 +83,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use super::super::derive; @@ -96,6 +101,7 @@ pub mod exposed #[ allow( unused_imports ) ] pub mod prelude { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] diff --git a/module/core/macro_tools/src/diag.rs b/module/core/macro_tools/src/diag.rs index 0f8fa6f6e8..0a9f0e8608 100644 --- a/module/core/macro_tools/src/diag.rs +++ b/module/core/macro_tools/src/diag.rs @@ -2,9 +2,11 @@ //! Macro helpers. //! -/// Internal namespace. + +/// Define a private namespace for all its items. mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; /// Adds indentation and optional prefix/postfix to each line of the given string. @@ -44,7 +46,6 @@ mod private /// and a semicolon at the end of each line. The function also demonstrates handling /// of input strings that end with a newline character by appending an additional line /// consisting only of the prefix and postfix. - pub fn indentation< Prefix, Src, Postfix >( prefix : Prefix, src : Src, postfix : Postfix ) -> String where Prefix : AsRef< str >, @@ -62,17 +63,17 @@ mod private { if b.0 > 0 { - a.push_str( "\n" ); + a.push( '\n' ); } a.push_str( prefix ); - a.push_str( &b.1 ); + a.push_str( b.1 ); a.push_str( postfix ); a }); - if src.ends_with( "\n" ) || src.ends_with( "\n\r" ) || src.ends_with( "\r\n" ) + if src.ends_with( '\n' ) || src.ends_with( "\n\r" ) || src.ends_with( "\r\n" ) { - result.push_str( "\n" ); + result.push( '\n' ); result.push_str( prefix ); result.push_str( postfix ); } @@ -128,11 +129,11 @@ mod private /// }; /// /// // Format the debug report for printing or logging - /// let formatted_report = report_format( "Code Transformation for MyStruct", original_input, generated_code ); + /// let formatted_report = report_format( &"Code Transformation for MyStruct", &original_input, generated_code ); /// println!( "{}", formatted_report ); /// ``` /// - + #[ allow( clippy::needless_pass_by_value ) ] pub fn report_format< IntoAbout, IntoInput, IntoOutput > ( about : IntoAbout, input : IntoInput, output : IntoOutput @@ -142,7 +143,7 @@ mod private IntoInput : ToString, IntoOutput : ToString, { - format!( "\n" ) + + "\n".to_string() + &format!( " = context\n\n{}\n\n", indentation( " ", about.to_string(), "" ) ) + &format!( " = original\n\n{}\n\n", indentation( " ", input.to_string(), "" ) ) + &format!( " = generated\n\n{}\n", indentation( " ", output.to_string(), "" ) ) @@ -194,7 +195,6 @@ mod private /// The above example demonstrates how the `report_print` function can be used to visualize the changes from original input code to the generated code, /// helping developers to verify and understand the modifications made during code generation processes. The output is formatted to show clear distinctions /// between the 'original' and 'generated' sections, providing an easy-to-follow comparison. - pub fn report_print< IntoAbout, IntoInput, IntoOutput > ( about : IntoAbout, input : IntoInput, output : IntoOutput @@ -219,7 +219,6 @@ mod private /// tree_print!( tree_type ); /// ``` /// - #[ macro_export ] macro_rules! tree_print { @@ -247,7 +246,6 @@ mod private /// tree_print!( tree_type ); /// ``` /// - #[ macro_export ] macro_rules! code_print { @@ -266,7 +264,6 @@ mod private /// /// Macro for diagnostics purpose to export both syntax tree and source code behind it into a string. /// - #[ macro_export ] macro_rules! tree_diagnostics_str { @@ -280,7 +277,6 @@ mod private /// /// Macro for diagnostics purpose to diagnose source code behind it and export it into a string. /// - #[ macro_export ] macro_rules! code_diagnostics_str { @@ -294,7 +290,6 @@ mod private /// /// Macro to export source code behind a syntax tree into a string. /// - #[ macro_export ] macro_rules! code_to_str { @@ -315,7 +310,6 @@ mod private /// # () /// ``` /// - #[ macro_export ] macro_rules! syn_err { @@ -353,7 +347,6 @@ mod private /// # () /// ``` /// - #[ macro_export ] macro_rules! return_syn_err { @@ -384,6 +377,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] @@ -395,6 +389,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; @@ -412,6 +407,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use super::super::diag; @@ -432,6 +428,7 @@ pub mod exposed #[ allow( unused_imports ) ] pub mod prelude { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] diff --git a/module/core/macro_tools/src/equation.rs b/module/core/macro_tools/src/equation.rs index 6a419dfe07..ae7080efdb 100644 --- a/module/core/macro_tools/src/equation.rs +++ b/module/core/macro_tools/src/equation.rs @@ -2,9 +2,10 @@ //! Attributes analyzys and manipulation. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; /// Represents an equation parsed from a procedural macro input. @@ -85,7 +86,7 @@ mod private /// /// For attribute like `#[former( default = 31 ) ]` return key `default` and value `31`, - /// as well as syn::Meta as the last element of result tuple. + /// as well as `syn::Meta` as the last element of result tuple. /// /// ### Basic use-case. /// @@ -96,19 +97,20 @@ mod private /// let got = equation::from_meta( &attr ).unwrap(); /// assert_eq!( macro_tools::code_to_str!( got ), "default = 31".to_string() ); /// ``` - + /// # Errors + /// qqq: doc pub fn from_meta( attr : &syn::Attribute ) -> Result< Equation > { let meta = &attr.meta; - return match meta + match meta { syn::Meta::List( ref meta_list ) => { let eq : Equation = syn::parse2( meta_list.tokens.clone() )?; Ok( eq ) } - _ => return Err( syn::Error::new( attr.span(), "Unknown format of attribute, expected syn::Meta::List( meta_list )" ) ), - }; + _ => Err( syn::Error::new( attr.span(), "Unknown format of attribute, expected syn::Meta::List( meta_list )" ) ), + } } } @@ -121,6 +123,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use orphan::*; @@ -135,6 +138,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; @@ -144,6 +148,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use super::super::equation; diff --git a/module/core/macro_tools/src/generic_args.rs b/module/core/macro_tools/src/generic_args.rs index 16636e8ac0..b07b22c5d3 100644 --- a/module/core/macro_tools/src/generic_args.rs +++ b/module/core/macro_tools/src/generic_args.rs @@ -2,7 +2,7 @@ //! This module provides utilities to handle and manipulate generic arguments using the `syn` crate. It includes traits and functions for transforming, merging, and managing generic parameters within procedural macros, enabling seamless syntactic analysis and code generation. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { @@ -24,6 +24,7 @@ mod private /// # Returns /// A new instance of `syn::AngleBracketedGenericArguments` representing the generic parameters /// of the original type. + #[ allow( clippy::wrong_self_convention ) ] fn into_generic_args( &self ) -> syn::AngleBracketedGenericArguments; } @@ -98,6 +99,7 @@ mod private /// /// This example demonstrates how lifetimes `'a` and `'b` are placed before other generic parameters /// like `T`, `U`, and `V` in the merged result, adhering to the expected syntax order in Rust generics. + #[ must_use ] pub fn merge ( a : &syn::AngleBracketedGenericArguments, @@ -110,7 +112,7 @@ mod private // Function to categorize and collect arguments into lifetimes and others let mut categorize_and_collect = |args : &syn::punctuated::Punctuated| { - for arg in args.iter() + for arg in args { match arg { @@ -148,6 +150,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] @@ -163,6 +166,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; diff --git a/module/core/macro_tools/src/generic_params.rs b/module/core/macro_tools/src/generic_params.rs index 599b5ca7cb..b54e5787b9 100644 --- a/module/core/macro_tools/src/generic_params.rs +++ b/module/core/macro_tools/src/generic_params.rs @@ -2,9 +2,10 @@ //! Functions and structures to handle and manipulate generic parameters using the `syn` crate. It's designed to support macro-driven code generation by simplifying, merging, extracting, and decomposing `syn::Generics`. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use crate::IterTrait; // use iter_tools::IterTrait; @@ -36,12 +37,15 @@ mod private impl GenericsWithWhere { /// Unwraps the `GenericsWithWhere` to retrieve the inner `syn::Generics`. + #[ must_use ] pub fn unwrap( self ) -> syn::Generics { self.generics } /// Parses a string to a `GenericsWithWhere`, specifically designed to handle generics syntax with where clauses effectively. + /// # Errors + /// qqq: doc pub fn parse_from_str( s : &str ) -> syn::Result< GenericsWithWhere > { syn::parse_str::< GenericsWithWhere >( s ) @@ -109,27 +113,28 @@ mod private /// # Examples /// /// - /// # use syn::{Generics, parse_quote}; + /// # use `syn::{Generics`, `parse_quote`}; /// - /// let mut generics_a : syn::Generics = parse_quote!{ < T : Clone, U : Default > }; - /// generics_a.where_clause = parse_quote!{ where T : Default }; - /// let mut generics_b : syn::Generics = parse_quote!{ < V : core::fmt::Debug > }; - /// generics_b.where_clause = parse_quote!{ where V : Sized }; - /// let got = generic_params::merge( &generics_a, &generics_b ); + /// let mut `generics_a` : `syn::Generics` = `parse_quote`!{ < T : Clone, U : Default > }; + /// `generics_a.where_clause` = `parse_quote`!{ where T : Default }; + /// let mut `generics_b` : `syn::Generics` = `parse_quote`!{ < V : `core::fmt::Debug` > }; + /// `generics_b.where_clause` = `parse_quote`!{ where V : Sized }; + /// let got = `generic_params::merge`( &`generics_a`, &`generics_b` ); /// - /// let mut exp : syn::Generics = parse_quote! + /// let mut exp : `syn::Generics` = `parse_quote`! /// { - /// < T : Clone, U : Default, V : core::fmt::Debug > + /// < T : Clone, U : Default, V : `core::fmt::Debug` > /// }; - /// exp.where_clause = parse_quote! + /// `exp.where_clause` = `parse_quote`! /// { /// where /// T : Default, /// V : Sized /// }; /// - /// assert_eq!( got, exp ); - + /// `assert_eq`!( got, exp ); + #[ must_use ] + #[ allow( clippy::default_trait_access ) ] pub fn merge( a : &syn::Generics, b : &syn::Generics ) -> syn::Generics { @@ -204,7 +209,8 @@ mod private /// assert_eq!( simplified_generics.params.len(), 4 ); // Contains T, U, 'a, and N /// assert!( simplified_generics.where_clause.is_none() ); // Where clause is removed /// ``` - + #[ allow( clippy::default_trait_access ) ] + #[ must_use ] pub fn only_names( generics : &syn::Generics ) -> syn::Generics { // use syn::{ Generics, GenericParam, LifetimeDef, TypeParam, ConstParam }; @@ -282,9 +288,9 @@ mod private /// &syn::Ident::new( "N", proc_macro2::Span::call_site() ) /// ]); /// ``` - - pub fn names< 'a >( generics : &'a syn::Generics ) - -> impl IterTrait< 'a, &'a syn::Ident > + #[ must_use ] + pub fn names( generics : &syn::Generics ) + -> impl IterTrait< '_, &syn::Ident > // -> std::iter::Map // < // syn::punctuated::Iter< 'a, syn::GenericParam >, @@ -387,7 +393,8 @@ mod private /// } /// ``` /// - + #[ allow( clippy::type_complexity ) ] + #[ must_use ] pub fn decompose ( generics : &syn::Generics, @@ -512,6 +519,7 @@ pub use own::*; /// Own namespace of the module. pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] @@ -530,6 +538,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; diff --git a/module/core/macro_tools/src/ident.rs b/module/core/macro_tools/src/ident.rs new file mode 100644 index 0000000000..371e2fae8f --- /dev/null +++ b/module/core/macro_tools/src/ident.rs @@ -0,0 +1,93 @@ +//! +//! 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" ); + /// ``` + #[must_use] + 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/item.rs b/module/core/macro_tools/src/item.rs index 605124e8a5..1c6c3e5b49 100644 --- a/module/core/macro_tools/src/item.rs +++ b/module/core/macro_tools/src/item.rs @@ -3,9 +3,10 @@ //! to manipulate the structure of items, handle different kinds of fields, and provide a structured approach to //! organizing the codebase into different access levels. -/// Internal namespace. +/// Define a private namespace for all its items. mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; /// Ensures the last field in a struct has a trailing comma. @@ -56,7 +57,7 @@ mod private /// } /// }.to_string() ); /// ``` - + #[ must_use ] pub fn ensure_comma( input : &syn::ItemStruct ) -> syn::ItemStruct { let mut new_input = input.clone(); // Clone the input to modify it @@ -66,12 +67,12 @@ mod private // Handle named fields syn::Fields::Named( syn::FieldsNamed { named, .. } ) => { - punctuated::ensure_trailing_comma( named ) + punctuated::ensure_trailing_comma( named ); }, // Handle unnamed fields (tuples) syn::Fields::Unnamed( syn::FieldsUnnamed { unnamed, .. } ) => { - punctuated::ensure_trailing_comma( unnamed ) + punctuated::ensure_trailing_comma( unnamed ); }, // Do nothing for unit structs syn::Fields::Unit => {} @@ -90,6 +91,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use orphan::*; @@ -104,6 +106,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; diff --git a/module/core/macro_tools/src/item_struct.rs b/module/core/macro_tools/src/item_struct.rs index 1855fa67b3..09f8f2c7a5 100644 --- a/module/core/macro_tools/src/item_struct.rs +++ b/module/core/macro_tools/src/item_struct.rs @@ -2,16 +2,18 @@ //! Parse structures, like `struct { a : i32 }`. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; // use iter_tools::{ IterTrait, BoxedIter }; /// Extracts the types of each field into a vector. - pub fn field_types< 'a >( t : &'a syn::ItemStruct ) + #[ must_use ] + pub fn field_types( t : &syn::ItemStruct ) -> - impl IterTrait< 'a, &'a syn::Type > + impl IterTrait< '_, &syn::Type > // -> std::iter::Map // < // syn::punctuated::Iter< 'a, syn::Field >, @@ -22,7 +24,13 @@ mod private } /// Retrieves the names of each field, if they exist. - pub fn field_names< 'a >( t : &'a syn::ItemStruct ) -> Option< BoxedIter< 'a, &'a syn::Ident > > + /// # Errors + /// qqq: doc + /// # Panics + /// qqq: error + #[ allow( clippy::match_wildcard_for_single_variants ) ] + #[ must_use ] + pub fn field_names( t : &syn::ItemStruct ) -> Option< BoxedIter< '_, &syn::Ident > > { match &t.fields { @@ -35,6 +43,9 @@ mod private /// Retrieves the type of the first field of the struct. /// /// Returns the type if the struct has at least one field, otherwise returns an error. + /// # Errors + /// qqq + #[ allow( clippy::match_wildcard_for_single_variants ) ] pub fn first_field_type( t : &syn::ItemStruct ) -> Result< syn::Type > { let maybe_field = match t.fields @@ -49,13 +60,16 @@ mod private return Ok( field.ty.clone() ) } - return Err( syn_err!( t.span(), "Expects at least one field" ) ); + Err( syn_err!( t.span(), "Expects at least one field" ) ) } /// Retrieves the name of the first field of the struct, if available. /// /// Returns `Some` with the field identifier for named fields, or `None` for unnamed fields. /// Returns an error if the struct has no fields + /// # Errors + /// qqq: doc + #[ allow( clippy::match_wildcard_for_single_variants ) ] pub fn first_field_name( t : &syn::ItemStruct ) -> Result< Option< syn::Ident > > { let maybe_field = match t.fields @@ -70,7 +84,7 @@ mod private return Ok( field.ident.clone() ) } - return Err( syn_err!( t.span(), "Expects type for fields" ) ); + Err( syn_err!( t.span(), "Expects type for fields" ) ) } @@ -84,6 +98,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use orphan::*; @@ -101,6 +116,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; @@ -110,6 +126,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use super::super::item_struct; diff --git a/module/core/macro_tools/src/iter.rs b/module/core/macro_tools/src/iter.rs index 6ad9773801..587750de8a 100644 --- a/module/core/macro_tools/src/iter.rs +++ b/module/core/macro_tools/src/iter.rs @@ -2,7 +2,7 @@ //! Tailored iterator. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { } @@ -15,6 +15,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use orphan::*; @@ -27,6 +28,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; @@ -36,6 +38,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; // pub use super::super::iter; diff --git a/module/core/macro_tools/src/kw.rs b/module/core/macro_tools/src/kw.rs index c7910e7571..9ae3e2abf2 100644 --- a/module/core/macro_tools/src/kw.rs +++ b/module/core/macro_tools/src/kw.rs @@ -2,7 +2,7 @@ //! Keywords //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { // use crate::*; @@ -17,6 +17,7 @@ mod private // qqq : cover by test /// Check is string a keyword. + #[ must_use ] pub fn is( src : &str ) -> bool { KEYWORDS.contains( &src ) @@ -32,6 +33,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use orphan::*; @@ -41,6 +43,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; @@ -50,6 +53,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use super::super::kw; diff --git a/module/core/macro_tools/src/lib.rs b/module/core/macro_tools/src/lib.rs index a11fdf7f69..1176755c8c 100644 --- a/module/core/macro_tools/src/lib.rs +++ b/module/core/macro_tools/src/lib.rs @@ -4,17 +4,17 @@ #![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] -/// Internal namespace. +/// Define a private namespace for all its items. #[ cfg( feature = "enabled" ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; /// - /// Result with syn::Error. + /// Result with `syn::Error`. /// - - pub type Result< T > = std::result::Result< T, syn::Error >; + pub type Result< T > = core::result::Result< T, syn::Error >; } @@ -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" ) ) ] @@ -63,13 +65,12 @@ pub mod typ; #[ cfg( all( feature = "enabled", feature = "typed" ) ) ] pub mod typed; -#[ cfg( all( feature = "enabled" ) ) ] +#[ cfg( feature = "enabled" ) ] pub mod iter; /// /// Dependencies of the module. /// - #[ cfg( feature = "enabled" ) ] #[ allow( unused_imports ) ] pub mod dependency @@ -98,6 +99,7 @@ pub mod own mod _all { + #[ allow( clippy::wildcard_imports ) ] use super::super::*; pub use orphan::*; @@ -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,13 +164,14 @@ pub mod own /// Parented namespace of the module. #[ cfg( feature = "enabled" ) ] - #[ allow( unused_imports ) ] +#[ allow( unused_imports ) ] pub mod orphan { use super::*; mod _all { + #[ allow( clippy::wildcard_imports ) ] use super::super::*; pub use exposed::*; } @@ -185,6 +190,7 @@ pub mod exposed mod _all { + #[ allow( clippy::wildcard_imports ) ] use super::super::*; pub use prelude::*; @@ -208,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" ) ] @@ -249,6 +257,7 @@ pub mod prelude mod _all { + #[ allow( clippy::wildcard_imports ) ] use super::super::*; // pub use prelude::*; @@ -272,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" ) ] @@ -349,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/name.rs b/module/core/macro_tools/src/name.rs index a9f53887b0..c6899b308a 100644 --- a/module/core/macro_tools/src/name.rs +++ b/module/core/macro_tools/src/name.rs @@ -2,14 +2,13 @@ //! Tait to getn name of an Item. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { /// /// Trait to get name of an syntax element. /// - pub trait Name { /// Get name. @@ -39,7 +38,7 @@ mod private syn::Item::Union( item ) => item.name(), // syn::Item::Use( item ) => item.name(), // syn::Item::Verbatim( item ) => item.name(), - _ => "".into(), + _ => String::new(), } } } @@ -51,7 +50,7 @@ mod private let first = self.segments.first(); if first.is_none() { - return "".into() + return String::new() } let first = first.unwrap(); first.ident.to_string() @@ -104,7 +103,7 @@ mod private { if self.trait_.is_none() { - return "".into() + return String::new() } let t = self.trait_.as_ref().unwrap(); t.1.name() @@ -117,7 +116,7 @@ mod private { if self.ident.is_none() { - return "".to_string() + return String::new() } let ident = self.ident.as_ref().unwrap(); ident.to_string() @@ -232,6 +231,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use orphan::*; @@ -241,6 +241,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; @@ -250,6 +251,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use super::super::name; @@ -263,6 +265,7 @@ pub mod exposed #[ allow( unused_imports ) ] pub mod prelude { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] #[ allow( unused_imports ) ] diff --git a/module/core/macro_tools/src/phantom.rs b/module/core/macro_tools/src/phantom.rs index f4bc1ec350..3b26da705b 100644 --- a/module/core/macro_tools/src/phantom.rs +++ b/module/core/macro_tools/src/phantom.rs @@ -4,9 +4,10 @@ //! Functions and structures to handle and manipulate `PhantomData` fields in structs using the `syn` crate. These utilities ensure that generic parameters are correctly accounted for in type checking, even if they are not directly used in the struct's fields. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; /// Adds a `PhantomData` field to a struct to manage generic parameter usage. @@ -42,7 +43,8 @@ mod private /// // Output will include a _phantom field of type `PhantomData< ( T, U ) >` /// ``` /// - + #[ allow( clippy::default_trait_access, clippy::semicolon_if_nothing_returned ) ] + #[ must_use ] pub fn add_to_item( input : &syn::ItemStruct ) -> syn::ItemStruct { @@ -108,7 +110,7 @@ mod private } ) } - }; + } input } @@ -136,6 +138,8 @@ mod private /// // Output : ::core::marker::PhantomData< ( &'a (), *const T, N ) > /// ``` /// + #[ must_use ] + #[ allow( clippy::default_trait_access ) ] pub fn tuple( input : &syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma > ) -> syn::Type { use proc_macro2::Span; @@ -198,6 +202,7 @@ pub use own::*; /// Own namespace of the module. pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] @@ -214,6 +219,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; diff --git a/module/core/macro_tools/src/punctuated.rs b/module/core/macro_tools/src/punctuated.rs index 2257904a81..a2c3fa0c8a 100644 --- a/module/core/macro_tools/src/punctuated.rs +++ b/module/core/macro_tools/src/punctuated.rs @@ -4,7 +4,7 @@ //! This module provides functionality to manipulate and ensure correct punctuation in `syn::punctuated::Punctuated` collections, commonly used in procedural macros to represent sequences of elements separated by punctuation marks, such as commas. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { @@ -28,6 +28,7 @@ pub use own::*; /// Own namespace of the module. pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] @@ -43,6 +44,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; diff --git a/module/core/macro_tools/src/quantifier.rs b/module/core/macro_tools/src/quantifier.rs index 379c38e9a4..c3c1e2607b 100644 --- a/module/core/macro_tools/src/quantifier.rs +++ b/module/core/macro_tools/src/quantifier.rs @@ -2,15 +2,15 @@ //! Quantifiers like Pair and Many. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; /// /// Marker saying how to parse several elements of such type in a row. /// - pub trait AsMuchAsPossibleNoDelimiter {} /// Element of parsing. @@ -105,11 +105,13 @@ mod private T : Element, { /// Constructor. + #[ must_use ] pub fn new() -> Self { Self( Vec::new() ) } /// Constructor. + #[ must_use ] pub fn new_with( src : Vec< T > ) -> Self { Self( src ) @@ -148,6 +150,7 @@ mod private T : quote::ToTokens, { type Item = T; + #[ allow( clippy::std_instead_of_alloc ) ] type IntoIter = std::vec::IntoIter< Self::Item >; fn into_iter( self ) -> Self::IntoIter { @@ -253,6 +256,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use orphan::*; @@ -262,6 +266,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; @@ -271,6 +276,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use super::super::quantifier; @@ -291,6 +297,7 @@ pub mod exposed #[ allow( unused_imports ) ] pub mod prelude { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use private:: diff --git a/module/core/macro_tools/src/struct_like.rs b/module/core/macro_tools/src/struct_like.rs index 1ec494be89..abed28a510 100644 --- a/module/core/macro_tools/src/struct_like.rs +++ b/module/core/macro_tools/src/struct_like.rs @@ -2,9 +2,10 @@ //! Parse structures, like `struct { a : i32 }`. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; /// Enum to encapsulate either a field from a struct or a variant from an enum. @@ -17,7 +18,7 @@ mod private Variant( &'a syn::Variant ), } - impl< 'a > Copy for FieldOrVariant< 'a > + impl Copy for FieldOrVariant< '_ > { } @@ -55,10 +56,11 @@ mod private } } - impl< 'a > FieldOrVariant< 'a > + impl FieldOrVariant< '_ > { /// Returns a reference to the attributes of the item. + #[ must_use ] pub fn attrs( &self ) -> &Vec< syn::Attribute > { match self @@ -69,6 +71,7 @@ mod private } /// Returns a reference to the visibility of the item. + #[ must_use ] pub fn vis( &self ) -> Option< &syn::Visibility > { match self @@ -79,6 +82,7 @@ mod private } /// Returns a reference to the mutability of the item. + #[ must_use ] pub fn mutability( &self ) -> Option< &syn::FieldMutability > { match self @@ -89,6 +93,7 @@ mod private } /// Returns a reference to the identifier of the item. + #[ must_use] pub fn ident( &self ) -> Option< &syn::Ident > { match self @@ -99,6 +104,7 @@ mod private } /// Returns an iterator over elements of the item. + #[ must_use ] pub fn typ( &self ) -> Option< &syn::Type > { match self @@ -115,6 +121,7 @@ mod private } /// Returns a reference to the fields of the item. + #[ must_use ] pub fn fields( &self ) -> Option< &syn::Fields > { match self @@ -125,6 +132,7 @@ mod private } /// Returns a reference to the discriminant of the item. + #[ must_use ] pub fn discriminant( &self ) -> Option< &( syn::token::Eq, syn::Expr ) > { match self @@ -202,7 +210,7 @@ mod private // Parse ItemStruct let mut item_struct : ItemStruct = input.parse()?; item_struct.vis = visibility; - item_struct.attrs = attributes.into(); + item_struct.attrs = attributes; if item_struct.fields.is_empty() { Ok( StructLike::Unit( item_struct ) ) @@ -217,7 +225,7 @@ mod private // Parse ItemEnum let mut item_enum : ItemEnum = input.parse()?; item_enum.vis = visibility; - item_enum.attrs = attributes.into(); + item_enum.attrs = attributes; Ok( StructLike::Enum( item_enum ) ) } else @@ -274,14 +282,12 @@ mod private } /// Returns an iterator over elements of the item. + #[ must_use ] pub fn attrs( &self ) -> &Vec< syn::Attribute > { match self { - StructLike::Unit( item ) => - { - &item.attrs - }, + StructLike::Unit( item ) | StructLike::Struct( item ) => { &item.attrs @@ -294,14 +300,12 @@ mod private } /// Returns an iterator over elements of the item. + #[ must_use ] pub fn vis( &self ) -> &syn::Visibility { match self { - StructLike::Unit( item ) => - { - &item.vis - }, + StructLike::Unit( item ) | StructLike::Struct( item ) => { &item.vis @@ -314,14 +318,12 @@ mod private } /// Returns an iterator over elements of the item. + #[ must_use ] pub fn ident( &self ) -> &syn::Ident { match self { - StructLike::Unit( item ) => - { - &item.ident - }, + StructLike::Unit( item ) | StructLike::Struct( item ) => { &item.ident @@ -334,14 +336,12 @@ mod private } /// Returns an iterator over elements of the item. + #[ must_use ] pub fn generics( &self ) -> &syn::Generics { match self { - StructLike::Unit( item ) => - { - &item.generics - }, + StructLike::Unit( item ) | StructLike::Struct( item ) => { &item.generics @@ -355,13 +355,14 @@ mod private /// Returns an iterator over fields of the item. // pub fn fields< 'a >( &'a self ) -> impl IterTrait< 'a, &'a syn::Field > + #[ must_use ] pub fn fields< 'a >( &'a self ) -> BoxedIter< 'a, &'a syn::Field > { let result : BoxedIter< 'a, &'a syn::Field > = match self { StructLike::Unit( _item ) => { - Box::new( std::iter::empty() ) + Box::new( core::iter::empty() ) }, StructLike::Struct( item ) => { @@ -369,22 +370,22 @@ mod private }, StructLike::Enum( _item ) => { - Box::new( std::iter::empty() ) + Box::new( core::iter::empty() ) }, }; result } /// Extracts the name of each field. + /// # Panics + /// qqq: docs // pub fn field_names< 'a >( &'a self ) -> Option< impl IterTrait< 'a, &'a syn::Ident > + '_ > - pub fn field_names< 'a >( &'a self ) -> Option< BoxedIter< 'a, &'a syn::Ident >> + #[ must_use ] + pub fn field_names( &self ) -> Option< BoxedIter< '_, &syn::Ident >> { match self { - StructLike::Unit( item ) => - { - item_struct::field_names( item ) - }, + StructLike::Unit( item ) | StructLike::Struct( item ) => { item_struct::field_names( item ) @@ -398,8 +399,9 @@ mod private } /// Extracts the type of each field. - pub fn field_types<'a>( &'a self ) - -> BoxedIter< 'a, &'a syn::Type > + #[ must_use ] + pub fn field_types( & self ) + -> BoxedIter< '_, & syn::Type > // -> std::iter::Map // < // std::boxed::Box< dyn _IterTrait< '_, &syn::Field > + 'a >, @@ -411,8 +413,9 @@ mod private /// Extracts the name of each field. // pub fn field_attrs< 'a >( &'a self ) -> impl IterTrait< 'a, &'a Vec< syn::Attribute > > - pub fn field_attrs<'a>( &'a self ) - -> BoxedIter< 'a, &'a Vec< syn::Attribute > > + #[ must_use ] + pub fn field_attrs( & self ) + -> BoxedIter< '_, &Vec< syn::Attribute > > // -> std::iter::Map // < // std::boxed::Box< dyn _IterTrait< '_, &syn::Field > + 'a >, @@ -423,6 +426,7 @@ mod private } /// Extract the first field. + #[ must_use ] pub fn first_field( &self ) -> Option< &syn::Field > { self.fields().next() @@ -443,6 +447,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use orphan::*; @@ -458,6 +463,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; @@ -467,6 +473,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use super::super::struct_like; diff --git a/module/core/macro_tools/src/tokens.rs b/module/core/macro_tools/src/tokens.rs index 0d7fd568e8..cfb52da63f 100644 --- a/module/core/macro_tools/src/tokens.rs +++ b/module/core/macro_tools/src/tokens.rs @@ -2,9 +2,10 @@ //! Attributes analyzys and manipulation. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use core::fmt; @@ -32,6 +33,7 @@ mod private impl Tokens { /// Constructor from `proc_macro2::TokenStream`. + #[ must_use ] pub fn new( inner : proc_macro2::TokenStream ) -> Self { Tokens { inner } @@ -59,7 +61,7 @@ mod private { fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result { - write!( f, "{}", self.inner.to_string() ) + write!( f, "{}", self.inner ) } } @@ -67,7 +69,7 @@ mod private { fn fmt( &self, f : &mut core::fmt::Formatter< '_ > ) -> core::fmt::Result { - write!( f, "{}", self.inner.to_string() ) + write!( f, "{}", self.inner ) } } @@ -81,6 +83,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use orphan::*; @@ -90,6 +93,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; @@ -99,6 +103,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use super::super::tokens; diff --git a/module/core/macro_tools/src/typ.rs b/module/core/macro_tools/src/typ.rs index 03b535081e..a6f3eef52c 100644 --- a/module/core/macro_tools/src/typ.rs +++ b/module/core/macro_tools/src/typ.rs @@ -2,9 +2,10 @@ //! Advanced syntax elements. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use interval_adapter::BoundExt; @@ -22,7 +23,9 @@ mod private /// let got = typ::type_rightmost( &tree_type ); /// assert_eq!( got, Some( "Option".to_string() ) ); /// ``` - + /// # Panics + /// qqq: doc + #[ must_use ] pub fn type_rightmost( ty : &syn::Type ) -> Option< String > { if let syn::Type::Path( path ) = ty @@ -53,7 +56,9 @@ mod private /// // < i16 /// // < i32 /// ``` - + /// # Panics + /// qqq: doc + #[ allow( clippy::cast_possible_wrap, clippy::needless_pass_by_value ) ] pub fn type_parameters( ty : &syn::Type, range : impl NonIterableInterval ) -> Vec< &syn::Type > { if let syn::Type::Path( syn::TypePath{ path : syn::Path { ref segments, .. }, .. } ) = ty @@ -104,7 +109,7 @@ mod private /// assert!( macro_tools::typ::is_optional( &parsed_type ) ); /// ``` /// - + #[ must_use ] pub fn is_optional( ty : &syn::Type ) -> bool { typ::type_rightmost( ty ) == Some( "Option".to_string() ) @@ -124,7 +129,8 @@ mod private /// let first_param = macro_tools::typ::parameter_first( &parsed_type ).expect( "Should have at least one parameter" ); /// // Option< i32 > /// ``` - + /// # Errors + /// qqq: docs pub fn parameter_first( ty : &syn::Type ) -> Result< &syn::Type > { typ::type_parameters( ty, 0 ..= 0 ) @@ -143,6 +149,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use orphan::*; @@ -160,6 +167,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; @@ -169,6 +177,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use super::super::typ; diff --git a/module/core/macro_tools/src/typed.rs b/module/core/macro_tools/src/typed.rs index 3eeeba271f..c5d2d05c3c 100644 --- a/module/core/macro_tools/src/typed.rs +++ b/module/core/macro_tools/src/typed.rs @@ -2,7 +2,7 @@ //! Typed parsing. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { // use crate::*; @@ -17,6 +17,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] @@ -35,6 +36,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; @@ -44,6 +46,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use super::super::typed; 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/smoke_test.rs b/module/core/macro_tools/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/core/macro_tools/tests/smoke_test.rs +++ b/module/core/macro_tools/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { 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/Cargo.toml b/module/core/mem_tools/Cargo.toml index fd90f9d727..ba71b5759a 100644 --- a/module/core/mem_tools/Cargo.toml +++ b/module/core/mem_tools/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mem_tools" -version = "0.6.0" +version = "0.8.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -24,7 +24,6 @@ workspace = true features = [ "full" ] all-features = false - include = [ "/rust/impl/mem", "/Cargo.toml", diff --git a/module/core/mem_tools/License b/module/core/mem_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/core/mem_tools/License +++ b/module/core/mem_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/mem_tools/Readme.md b/module/core/mem_tools/Readme.md index 96f5ae9605..6d35a9253a 100644 --- a/module/core/mem_tools/Readme.md +++ b/module/core/mem_tools/Readme.md @@ -2,7 +2,7 @@ # Module :: mem_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_mem_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_mem_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/mem_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/mem_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fmem_tools%2Fexamples%2Fmem_tools_trivial.rs,RUN_POSTFIX=--example%20mem_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_mem_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_mem_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/mem_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/mem_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fmem_tools%2Fexamples%2Fmem_tools_trivial.rs,RUN_POSTFIX=--example%20module%2Fcore%2Fmem_tools%2Fexamples%2Fmem_tools_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Collection of tools to manipulate memory. diff --git a/module/core/mem_tools/src/lib.rs b/module/core/mem_tools/src/lib.rs index 03270e0a05..141da61a9d 100644 --- a/module/core/mem_tools/src/lib.rs +++ b/module/core/mem_tools/src/lib.rs @@ -37,7 +37,6 @@ pub mod own #[ doc( inline ) ] pub use orphan::*; #[ doc( inline ) ] - #[ allow( unused_imports ) ] pub use super::mem::orphan::*; } @@ -60,7 +59,6 @@ pub mod exposed #[ doc( inline ) ] pub use prelude::*; #[ doc( inline ) ] - #[ allow( unused_imports ) ] pub use super::mem::exposed::*; } @@ -71,6 +69,5 @@ pub mod prelude { use super::*; #[ doc( inline ) ] - #[ allow( unused_imports ) ] pub use super::mem::prelude::*; } diff --git a/module/core/mem_tools/src/mem.rs b/module/core/mem_tools/src/mem.rs index e35cb611cd..3a48ddad8b 100644 --- a/module/core/mem_tools/src/mem.rs +++ b/module/core/mem_tools/src/mem.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { // use crate::own::*; @@ -64,30 +64,28 @@ mod private } +#[ doc( inline ) ] +#[ allow( unused_imports ) ] +pub use own::*; + /// Own namespace of the module. #[ allow( unused_imports ) ] pub mod own { use super::*; #[ doc( inline ) ] - #[ allow( unused_imports ) ] pub use super:: { orphan::*, }; } -#[ doc( inline ) ] -#[ allow( unused_imports ) ] -pub use own::*; - /// Orphan namespace of the module. #[ allow( unused_imports ) ] pub mod orphan { use super::*; #[ doc( inline ) ] - #[ allow( unused_imports ) ] pub use super:: { exposed::*, @@ -103,6 +101,9 @@ pub mod orphan pub mod exposed { use super::*; + // Expose itself. + pub use super::super::mem; + #[ doc( inline ) ] pub use prelude::*; } diff --git a/module/core/mem_tools/tests/inc/mem_test.rs b/module/core/mem_tools/tests/inc/mem_test.rs index 1b2fa2954e..bc9a9ce519 100644 --- a/module/core/mem_tools/tests/inc/mem_test.rs +++ b/module/core/mem_tools/tests/inc/mem_test.rs @@ -8,21 +8,21 @@ tests_impls! fn same_data() { let buf = [ 0u8; 128 ]; - a_true!( the_module::same_data( &buf, &buf ) ); + a_true!( the_module::mem::same_data( &buf, &buf ) ); let x = [ 0u8; 1 ]; let y = 0u8; - a_true!( the_module::same_data( &x, &y ) ); + a_true!( the_module::mem::same_data( &x, &y ) ); - a_false!( the_module::same_data( &buf, &x ) ); - a_false!( the_module::same_data( &buf, &y ) ); + a_false!( the_module::mem::same_data( &buf, &x ) ); + a_false!( the_module::mem::same_data( &buf, &y ) ); struct H1( &'static str ); struct H2( &'static str ); - a_true!( the_module::same_data( &H1( "hello" ), &H2( "hello" ) ) ); - a_false!( the_module::same_data( &H1( "qwerty" ), &H2( "hello" ) ) ); + a_true!( the_module::mem::same_data( &H1( "hello" ), &H2( "hello" ) ) ); + a_false!( the_module::mem::same_data( &H1( "qwerty" ), &H2( "hello" ) ) ); } @@ -31,15 +31,15 @@ tests_impls! let src1 = "abc"; let src2 = "abc"; - a_true!( the_module::same_ptr( src1, src2 ) ); + a_true!( the_module::mem::same_ptr( src1, src2 ) ); let src1 = ( 1, ); let src2 = ( 1, ); - a_false!( the_module::same_ptr( &src1, &src2 ) ); + a_false!( the_module::mem::same_ptr( &src1, &src2 ) ); let src1 = ( 1 ); let src2 = "abcde"; - a_false!( the_module::same_ptr( &src1, src2 ) ); + a_false!( the_module::mem::same_ptr( &src1, src2 ) ); } @@ -50,15 +50,15 @@ tests_impls! let src1 = "abc"; let src2 = "cba"; - a_true!( the_module::same_size( src1, src2 ) ); + a_true!( the_module::mem::same_size( src1, src2 ) ); let src1 = ( 1, ); let src2 = ( 3, ); - a_true!( the_module::same_size( &src1, &src2 ) ); + a_true!( the_module::mem::same_size( &src1, &src2 ) ); let src1 = ( 1 ); let src2 = "abcde"; - a_false!( the_module::same_size( &src1, src2 ) ); + a_false!( the_module::mem::same_size( &src1, src2 ) ); } @@ -69,15 +69,15 @@ tests_impls! let src1 = "abc"; let src2 = "abc"; - a_true!( the_module::same_region( src1, src2 ) ); + a_true!( the_module::mem::same_region( src1, src2 ) ); let src1 = ( 1, ); let src2 = ( 1, ); - a_false!( the_module::same_region( &src1, &src2 ) ); + a_false!( the_module::mem::same_region( &src1, &src2 ) ); let src1 = ( 1 ); let src2 = "abcde"; - a_false!( the_module::same_region( &src1, src2 ) ); + a_false!( the_module::mem::same_region( &src1, src2 ) ); } @@ -85,24 +85,23 @@ tests_impls! fn samples() { - use the_module as mem; // Are two pointers are the same, not taking into accoint type. // Unlike `std::ptr::eq()` does not require arguments to have the same type. let src1 = ( 1, ); let src2 = ( 1, ); - assert!( !mem::same_ptr( &src1, &src2 ) ); + assert!( !the_module::mem::same_ptr( &src1, &src2 ) ); // Are two pointers points on data of the same size. let src1 = "abc"; let src2 = "cba"; - assert!( mem::same_size( src1, src2 ) ); + assert!( the_module::mem::same_size( src1, src2 ) ); // Are two pointers points on the same region, ie same size and same pointer. // Does not require arguments to have the same type. let src1 = "abc"; let src2 = "abc"; - assert!( mem::same_region( src1, src2 ) ); + assert!( the_module::mem::same_region( src1, src2 ) ); } diff --git a/module/core/mem_tools/tests/inc/mod.rs b/module/core/mem_tools/tests/inc/mod.rs index 9147b3ddcc..cc1110aad5 100644 --- a/module/core/mem_tools/tests/inc/mod.rs +++ b/module/core/mem_tools/tests/inc/mod.rs @@ -1,4 +1,7 @@ #[ allow( unused_imports ) ] use super::*; +#[ allow( unused_imports ) ] +use test_tools::exposed::*; + mod mem_test; diff --git a/module/core/mem_tools/tests/mem_tools_tests.rs b/module/core/mem_tools/tests/mem_tools_tests.rs index 5f9856b952..51260d5101 100644 --- a/module/core/mem_tools/tests/mem_tools_tests.rs +++ b/module/core/mem_tools/tests/mem_tools_tests.rs @@ -1,3 +1,5 @@ +//! All tests. + // #![ deny( rust_2018_idioms ) ] // #![ deny( missing_debug_implementations ) ] // #![ deny( missing_docs ) ] @@ -5,8 +7,5 @@ // #![ feature( trace_macros ) ] // #![ feature( type_name_of_val ) ] -#[ allow( unused_imports ) ] -use test_tools::exposed::*; use mem_tools as the_module; - mod inc; diff --git a/module/core/mem_tools/tests/smoke_test.rs b/module/core/mem_tools/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/core/mem_tools/tests/smoke_test.rs +++ b/module/core/mem_tools/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/core/meta_tools/Cargo.toml b/module/core/meta_tools/Cargo.toml index 282db2f43f..ec2054076a 100644 --- a/module/core/meta_tools/Cargo.toml +++ b/module/core/meta_tools/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "meta_tools" -version = "0.10.0" +version = "0.12.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -24,47 +24,45 @@ workspace = true features = [ "full" ] all-features = false - - [features] default = [ "enabled", "meta_for_each", "meta_impls_index", - # "meta_mod_interface", - "meta_constructors", + "mod_interface", "meta_idents_concat", ] full = [ "enabled", "meta_for_each", "meta_impls_index", - # "meta_mod_interface", - "meta_constructors", + "mod_interface", "meta_idents_concat", ] no_std = [] use_alloc = [ "no_std" ] -enabled = [] +enabled = [ + "for_each/enabled", + "impls_index/enabled", + "mod_interface/enabled", +] -meta_for_each = [ "for_each/enabled" ] -meta_impls_index = [ "impls_index/enabled" ] -meta_mod_interface = [ "mod_interface/enabled" ] -# xxx : qqq : make mod_interface optional maybe +meta_for_each = [ "for_each/enabled", "dep:for_each" ] +meta_impls_index = [ "impls_index/enabled", "dep:impls_index" ] +mod_interface = [ "mod_interface/enabled", "dep:mod_interface" ] -meta_constructors = [ "literally" ] -meta_idents_concat = [ "paste" ] +# meta_constructors = [ "literally" ] +meta_idents_concat = [ "dep:paste" ] [dependencies] -## external -literally = { version = "~0.1.3", optional = true, default-features = false } -paste = { version = "~1.0.14", optional = true, default-features = false } +# ## external +paste = { workspace = true, optional = true, default-features = false } ## internal -impls_index = { workspace = true } -for_each = { workspace = true } -mod_interface = { workspace = true, features = [ "default" ] } +for_each = { workspace = true, optional = true } +impls_index = { workspace = true, optional = true } +mod_interface = { workspace = true, optional = true } [dev-dependencies] test_tools = { workspace = true } diff --git a/module/core/meta_tools/License b/module/core/meta_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/core/meta_tools/License +++ b/module/core/meta_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/core/meta_tools/Readme.md index 0d472b069f..76c8cfcf19 100644 --- a/module/core/meta_tools/Readme.md +++ b/module/core/meta_tools/Readme.md @@ -2,26 +2,11 @@ # Module :: meta_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_meta_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_meta_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/meta_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/meta_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fmeta_tools%2Fexamples%2Fmeta_tools_trivial.rs,RUN_POSTFIX=--example%20meta_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_meta_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_meta_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/meta_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/meta_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fmeta_tools%2Fexamples%2Fmeta_tools_trivial.rs,RUN_POSTFIX=--example%20module%2Fcore%2Fmeta_tools%2Fexamples%2Fmeta_tools_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Collection of general purpose meta tools. -### Basic use-case :: variadic constructor of collections - -Among other useful meta tools the module aggregates variadic constructors of collections. For example macro `hmap!` for constructing a hash map. - - - -```rust -use meta_tools::*; - -let meta_map = hmap! { 3 => 13 }; -let mut std_map = std::collections::HashMap::new(); -std_map.insert( 3, 13 ); -assert_eq!( meta_map, std_map ); -``` - ### Basic Use Case :: function-style call Apply a macro for each element of a list. diff --git a/module/core/meta_tools/examples/meta_tools_trivial.rs b/module/core/meta_tools/examples/meta_tools_trivial.rs index 75d17ddace..983e55c9d6 100644 --- a/module/core/meta_tools/examples/meta_tools_trivial.rs +++ b/module/core/meta_tools/examples/meta_tools_trivial.rs @@ -3,8 +3,10 @@ use meta_tools::*; fn main() { - let meta_map = hmap! { 3 => 13 }; - let mut std_map = std::collections::HashMap::new(); - std_map.insert( 3, 13 ); - assert_eq!( meta_map, std_map ); + for_each!( dbg, "a", "b", "c" ); + + // generates + dbg!( "a" ); + dbg!( "b" ); + dbg!( "c" ); } diff --git a/module/core/meta_tools/src/lib.rs b/module/core/meta_tools/src/lib.rs index cd49c9841e..e31d911ffe 100644 --- a/module/core/meta_tools/src/lib.rs +++ b/module/core/meta_tools/src/lib.rs @@ -10,32 +10,69 @@ pub mod dependency { - // #[ cfg( feature = "meta_mod_interface" ) ] pub use ::mod_interface; #[ cfg( feature = "meta_for_each" ) ] pub use ::for_each; #[ cfg( feature = "meta_impls_index" ) ] pub use ::impls_index; - - #[ cfg( feature = "meta_constructors" ) ] - pub use ::literally; #[ cfg( feature = "meta_idents_concat" ) ] pub use ::paste; - // #[ cfg( feature = "former" ) ] - // pub use ::former; - // #[ cfg( feature = "options" ) ] - // pub use ::woptions; - } +mod private {} + +// + +// // qqq : meta interface should be optional dependancy. please fix writing equivalent code manually +// #[ cfg( feature = "enabled" ) ] +// mod_interface::mod_interface! +// { +// // #![ debug ] // +// layer meta; +// +// } + +pub mod meta; -// qqq : meta interface should be optional dependancy. please fix writing equivalent code manually #[ cfg( feature = "enabled" ) ] -mod_interface::mod_interface! +#[ allow( unused_imports ) ] +pub use own::*; + +/// Own namespace of the module. +#[ cfg( feature = "enabled" ) ] +#[ allow( unused_imports ) ] +pub mod own { + use super::*; + pub use meta::orphan::*; +} - layer meta; +/// Orphan namespace of the module. +#[ cfg( feature = "enabled" ) ] +#[ allow( unused_imports ) ] +pub mod orphan +{ + use super::*; + pub use exposed::*; +} +/// Exposed namespace of the module. +#[ cfg( feature = "enabled" ) ] +#[ allow( unused_imports ) ] +pub mod exposed +{ + use super::*; + pub use prelude::*; + pub use meta::exposed::*; +} + +/// Prelude to use essentials: `use my_module::prelude::*`. +#[ cfg( feature = "enabled" ) ] +#[ allow( unused_imports ) ] +pub mod prelude +{ + use super::*; + pub use meta::prelude::*; } diff --git a/module/core/meta_tools/src/meta.rs b/module/core/meta_tools/src/meta.rs index e05ad7deec..1047857beb 100644 --- a/module/core/meta_tools/src/meta.rs +++ b/module/core/meta_tools/src/meta.rs @@ -2,39 +2,82 @@ //! Collection of general purpose meta tools. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { } // +// #[ cfg( feature = "enabled" ) ] +// mod_interface::mod_interface! +// { +// #![ debug ] +// +// #[ cfg( feature = "meta_impls_index" ) ] +// use ::impls_index; +// #[ cfg( feature = "meta_for_each" ) ] +// use ::for_each; +// // #[ cfg( feature = "meta_mod_interface" ) ] +// use ::mod_interface; +// // #[ cfg( feature = "meta_mod_interface" ) ] +// prelude use ::mod_interface::mod_interface; +// +// #[ cfg( feature = "meta_idents_concat" ) ] +// prelude use ::paste::paste as meta_idents_concat; +// +// } + +#[ cfg( feature = "enabled" ) ] +#[ allow( unused_imports ) ] +pub use own::*; + +/// Own namespace of the module. +#[ cfg( feature = "enabled" ) ] +pub mod own +{ + use super::*; + pub use ::impls_index::orphan::*; + pub use ::for_each::orphan::*; + pub use ::mod_interface::orphan::*; + pub use orphan::*; +} + +/// Orphan namespace of the module. #[ cfg( feature = "enabled" ) ] -mod_interface::mod_interface! +#[ allow( unused_imports ) ] +pub mod orphan { + use super::*; + + // pub use ::impls_index; + // pub use ::for_each; + // pub use ::mod_interface; - #[ cfg( feature = "meta_impls_index" ) ] - use ::impls_index; - #[ cfg( feature = "meta_for_each" ) ] - use ::for_each; - // #[ cfg( feature = "meta_mod_interface" ) ] - use ::mod_interface; - // #[ cfg( feature = "meta_mod_interface" ) ] - prelude use ::mod_interface::mod_interface; - - #[ cfg( feature = "meta_constructors" ) ] - prelude use ::literally::*; - #[ cfg( feature = "meta_idents_concat" ) ] - prelude use ::paste::paste as meta_idents_concat; - - // #[ cfg( feature = "options" ) ] - // use ::woptions; - // #[ cfg( feature = "options" ) ] - // prelude use ::woptions as options; - - // #[ cfg( feature = "former" ) ] - // use ::former; - // #[ cfg( feature = "former" ) ] - // prelude use ::former as former; + pub use exposed::*; +} +/// Exposed namespace of the module. +#[ cfg( feature = "enabled" ) ] +#[ allow( unused_imports ) ] +pub mod exposed +{ + use super::*; + pub use prelude::*; + pub use super::super::meta; + pub use ::impls_index::exposed::*; + pub use ::for_each::exposed::*; + pub use ::mod_interface::exposed::*; + pub use ::paste::paste as meta_idents_concat; +} + +/// Prelude to use essentials: `use my_module::prelude::*`. +#[ cfg( feature = "enabled" ) ] +#[ allow( unused_imports ) ] +pub mod prelude +{ + use super::*; + pub use ::impls_index::prelude::*; + pub use ::for_each::prelude::*; + pub use ::mod_interface::prelude::*; } diff --git a/module/core/meta_tools/tests/inc/meta_constructor_test.rs b/module/core/meta_tools/tests/inc/meta_constructor_test.rs index acee680259..d4cffdf307 100644 --- a/module/core/meta_tools/tests/inc/meta_constructor_test.rs +++ b/module/core/meta_tools/tests/inc/meta_constructor_test.rs @@ -1,50 +1,50 @@ -use super::*; - -// - -tests_impls! -{ - - fn hash_map() - { - - // test.case( "empty" ); - let got : std::collections::HashMap< i32, i32 > = the_module::hmap!{}; - let exp = std::collections::HashMap::new(); - a_id!( got, exp ); - - // test.case( "single entry" ); - let got = the_module::hmap!{ 3 => 13 }; - let mut exp = std::collections::HashMap::new(); - exp.insert( 3, 13 ); - a_id!( got, exp ); - - } - - // - - - fn hash_set() - { - - // test.case( "empty" ); - let got : std::collections::HashSet< i32 > = the_module::hset!{}; - let exp = std::collections::HashSet::new(); - a_id!( got, exp ); - - // test.case( "single entry" ); - let got = the_module::hset!{ 13 }; - let mut exp = std::collections::HashSet::new(); - exp.insert( 13 ); - a_id!( got, exp ); - - } -} - -// - -tests_index! -{ - hash_map, - hash_set, -} +// use super::*; +// +// // +// +// tests_impls! +// { +// +// fn hash_map() +// { +// +// // test.case( "empty" ); +// let got : std::collections::HashMap< i32, i32 > = the_module::hmap!{}; +// let exp = std::collections::HashMap::new(); +// a_id!( got, exp ); +// +// // test.case( "single entry" ); +// let got = the_module::hmap!{ 3 => 13 }; +// let mut exp = std::collections::HashMap::new(); +// exp.insert( 3, 13 ); +// a_id!( got, exp ); +// +// } +// +// // +// +// +// fn hash_set() +// { +// +// // test.case( "empty" ); +// let got : std::collections::HashSet< i32 > = the_module::hset!{}; +// let exp = std::collections::HashSet::new(); +// a_id!( got, exp ); +// +// // test.case( "single entry" ); +// let got = the_module::hset!{ 13 }; +// let mut exp = std::collections::HashSet::new(); +// exp.insert( 13 ); +// a_id!( got, exp ); +// +// } +// } +// +// // +// +// tests_index! +// { +// hash_map, +// hash_set, +// } diff --git a/module/core/meta_tools/tests/inc/mod.rs b/module/core/meta_tools/tests/inc/mod.rs index 9fc942d2c2..98e402d4c3 100644 --- a/module/core/meta_tools/tests/inc/mod.rs +++ b/module/core/meta_tools/tests/inc/mod.rs @@ -1,17 +1,17 @@ #[ allow( unused_imports ) ] use super::*; -#[ cfg( any( feature = "meta_constructors", feature = "meta_constructors" ) ) ] -mod meta_constructor_test; +// #[ cfg( any( feature = "meta_constructors", feature = "meta_constructors" ) ) ] +// mod meta_constructor_test; #[ cfg( any( feature = "meta_idents_concat", feature = "meta_idents_concat" ) ) ] mod indents_concat_test; -#[ cfg( any( feature = "for_each", feature = "meta_for_each" ) ) ] +#[ cfg( any( feature = "meta_for_each" ) ) ] #[ path = "../../../for_each/tests/inc/mod.rs" ] mod for_each_test; -#[ cfg( any( feature = "impls_index", feature = "meta_impls_index" ) ) ] +#[ cfg( any( feature = "meta_impls_index" ) ) ] #[ path = "../../../impls_index/tests/inc/mod.rs" ] mod impls_index; diff --git a/module/core/meta_tools/tests/smoke_test.rs b/module/core/meta_tools/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/core/meta_tools/tests/smoke_test.rs +++ b/module/core/meta_tools/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/core/mod_interface/Cargo.toml b/module/core/mod_interface/Cargo.toml index 38f56efef2..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.23.0" +version = "0.33.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -28,8 +28,6 @@ all-features = false [features] default = [ "enabled" ] full = [ "enabled" ] -no_std = [] -use_alloc = [ "no_std" ] enabled = [ "mod_interface_meta/enabled" ] # keep these examples in directories diff --git a/module/core/mod_interface/License b/module/core/mod_interface/License index e3e9e057cf..a23529f45b 100644 --- a/module/core/mod_interface/License +++ b/module/core/mod_interface/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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 72573cc881..32f5dc7612 100644 --- a/module/core/mod_interface/Readme.md +++ b/module/core/mod_interface/Readme.md @@ -1,140 +1,314 @@ -# Module :: mod_interface +# Module :: `mod_interface` [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_mod_interface_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_mod_interface_push.yml) [![docs.rs](https://img.shields.io/docsrs/mod_interface?color=e3e8f0&logo=docs.rs)](https://docs.rs/mod_interface) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) -Protocol of modularity unifying interface of a module and introducing layers. +Provides the `mod_interface!` macro to define structured module interfaces with controlled visibility and propagation, simplifying the creation of layered architectures in Rust. -### Basic use-case +### Overview -Library file with code `inner.rs`: +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: -```rust ignore -mod private +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. + +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 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 Exposure Levels within a layer determine the visibility and propagation scope: + +| 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` 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 `**: 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 + +This example shows a parent module using a `child` layer, demonstrating how items propagate based on their assigned exposure level. + +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; + +// Define a module named `child`. +pub mod child { - /// Routine of inner module. - pub fn inner_is() -> bool + + // Define a private namespace for all its items. + mod private { - 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; + } -mod_interface::mod_interface! -{ - prelude use inner_is; } -``` - -Main file that generates modules and namespaces `main.rs` : - -```rust ignore -use mod_interface::mod_interface; -// +// Parent module also needs a private namespace. +mod private {} -fn main() +// Parent module uses the `child` layer. +crate::mod_interface! { - assert_eq!( prelude::inner_is(), inner::prelude::inner_is() ); + /// Use the child layer. + use super::child; } -// -mod_interface::mod_interface! +// fn main() // Example usage demonstrating visibility: { - /// Inner. - layer inner; + + // `prelude_thing` is in `prelude`, so it propagates everywhere. + assert!( child::prelude_thing(), "prelude thing of child is there" ); + 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(), "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(), "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" ); // 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 + } + ``` -It generates code : +
+Click to see the code expanded by the macro ```rust use mod_interface::mod_interface; -// - -fn main() -{ - assert_eq!( prelude::inner_is(), inner::prelude::inner_is() ); -} - -// - -/// Inner. -pub mod inner +// Define a module named `child` +pub mod child { + // Define a private namespace for all its items. mod private { - /// Routine of inner module. - pub fn inner_is() -> 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 { - pub use orphan::*; + use super::*; + pub use orphan::*; + pub use private::my_thing; } - pub use own::*; - /// Orphan namespace of the module. pub mod orphan { - pub use exposed::*; + use super::*; + pub use exposed::*; + pub use private::orphan_thing; } - /// Exposed namespace of the module. pub mod exposed { - pub use prelude::*; + use super::*; + pub use prelude::*; + pub use private::exposed_thing; } - /// Prelude to use essentials: `use my_module::prelude::*`. pub mod prelude { - pub use private::inner_is; + use super::*; + pub use private::prelude_thing; } + } +// 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::inner::orphan::*; + 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 } -pub use 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 super::inner::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 super::inner::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() // 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(), "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(), "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(), "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" ); // 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 + +} + ``` +
+ ### Debugging To debug module interface use directive `#![ debug ]` in macro `mod_interface`. Let's update the main file of the example : @@ -144,7 +318,7 @@ mod_interface::mod_interface! { #![ debug ] /// Inner. - layer inner; + layer child; // Or `use super::child;` if defined separately } ``` @@ -171,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 6cc31966fb..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,13 +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 `inner.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. +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/inner.rs b/module/core/mod_interface/examples/mod_interface_debug/src/child.rs similarity index 80% rename from module/core/mod_interface/examples/mod_interface_debug/src/inner.rs rename to module/core/mod_interface/examples/mod_interface_debug/src/child.rs index cc62b2c56b..dd734212d9 100644 --- a/module/core/mod_interface/examples/mod_interface_debug/src/inner.rs +++ b/module/core/mod_interface/examples/mod_interface_debug/src/child.rs @@ -1,6 +1,6 @@ mod private { - /// Routine of inner module. + /// Routine of child module. pub fn inner_is() -> bool { true 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 e316b7acd6..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,18 +1,39 @@ -//! 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; // -fn main() +// 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! { - assert_eq!( prelude::inner_is(), inner::prelude::inner_is() ); + // 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 layer integration. + /// Defines the `child` module in this file and integrates its interface. + layer child; } // -mod_interface! +fn main() { - #![ debug ] - /// Inner. - layer inner; -} + // 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 343322a31c..b9335ebb8d 100644 --- a/module/core/mod_interface/examples/mod_interface_trivial/Readme.md +++ b/module/core/mod_interface/examples/mod_interface_trivial/Readme.md @@ -1,11 +1,9 @@ -# Sample +### Example: Using Layers and Entities [![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_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) -A sample demonstrates basic usage of macro `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 **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. -In file `inner.rs` demonstrated how to generate module interface from namespace `private` and its public routine. - -In file `main.rs` demonstrated how to generate module interface from layer ( file with full module interface ). +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 new file mode 100644 index 0000000000..1be662fbdc --- /dev/null +++ b/module/core/mod_interface/examples/mod_interface_trivial/src/child.rs @@ -0,0 +1,43 @@ + +// Define a private namespace where all items are initially defined. +mod private +{ + /// 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/inner.rs b/module/core/mod_interface/examples/mod_interface_trivial/src/inner.rs deleted file mode 100644 index cc62b2c56b..0000000000 --- a/module/core/mod_interface/examples/mod_interface_trivial/src/inner.rs +++ /dev/null @@ -1,15 +0,0 @@ -mod private -{ - /// Routine of inner module. - pub fn inner_is() -> bool - { - true - } -} - -// - -mod_interface::mod_interface! -{ - prelude use inner_is; -} 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 03b8905594..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,21 +1,70 @@ -//! qqq : write proper descriptionuse mod_interface::mod_interface; - -// +//! 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; -fn main() +/// Child module defined in `child.rs`. +pub mod child; + +// 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! { - assert_eq!( prelude::inner_is(), prelude::inner_is() ); + /// Use the child layer. + use super::child; } -// -mod_interface! +fn main() { - /// Inner. - layer inner; -} -// qqq : rewrite sample -/* aaa : Dmytro : sample with layer */ + // `prelude_thing` is in `child::prelude`, propagates everywhere. + assert!( child::prelude_thing(), "prelude thing of child is there" ); + 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(), "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(), "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" ); // 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/src/lib.rs b/module/core/mod_interface/src/lib.rs index 30c7731f89..193b9197c1 100644 --- a/module/core/mod_interface/src/lib.rs +++ b/module/core/mod_interface/src/lib.rs @@ -1,4 +1,4 @@ -#![ cfg_attr( feature = "no_std", no_std ) ] +#![ no_std ] #![ doc( html_logo_url = "https://raw.githubusercontent.com/Wandalen/wTools/master/asset/img/logo_v3_trans_square.png" ) ] #![ doc( html_favicon_url = "https://raw.githubusercontent.com/Wandalen/wTools/alpha/asset/img/logo_v3_trans_square_icon_small_v2.ico" ) ] #![ doc( html_root_url = "https://docs.rs/mod_interface/latest/mod_interface/" ) ] @@ -22,6 +22,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use orphan::*; @@ -37,6 +38,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use exposed::*; @@ -47,13 +49,13 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; #[ doc( inline ) ] pub use prelude::*; } /// Prelude to use essentials: `use my_module::prelude::*`. - #[ cfg( feature = "enabled" ) ] #[ allow( unused_imports ) ] pub mod prelude diff --git a/module/core/mod_interface/tests/inc/derive/attr_debug/mod.rs b/module/core/mod_interface/tests/inc/derive/attr_debug/mod.rs index 939ebcdfb3..7b425682cc 100644 --- a/module/core/mod_interface/tests/inc/derive/attr_debug/mod.rs +++ b/module/core/mod_interface/tests/inc/derive/attr_debug/mod.rs @@ -1,9 +1,11 @@ use super::*; -mod_interface! +mod private {} + +the_module::mod_interface! { - #![ debug ] + // #![ debug ] /// layer_a layer layer_a; diff --git a/module/core/mod_interface/tests/inc/derive/layer/mod.rs b/module/core/mod_interface/tests/inc/derive/layer/mod.rs index d8e79fb20b..8a567560f7 100644 --- a/module/core/mod_interface/tests/inc/derive/layer/mod.rs +++ b/module/core/mod_interface/tests/inc/derive/layer/mod.rs @@ -6,7 +6,7 @@ mod private { } -mod_interface! +the_module::mod_interface! { /// layer_a diff --git a/module/core/mod_interface/tests/inc/derive/layer_have_layer/layer_a.rs b/module/core/mod_interface/tests/inc/derive/layer_have_layer/layer_a.rs index dfffa8d8a8..082005e6be 100644 --- a/module/core/mod_interface/tests/inc/derive/layer_have_layer/layer_a.rs +++ b/module/core/mod_interface/tests/inc/derive/layer_have_layer/layer_a.rs @@ -33,7 +33,7 @@ mod private // -mod_interface! +the_module::mod_interface! { // orphan use super::private:: diff --git a/module/core/mod_interface/tests/inc/derive/layer_have_layer/layer_b.rs b/module/core/mod_interface/tests/inc/derive/layer_have_layer/layer_b.rs index 9f17f61637..1d265d3c4f 100644 --- a/module/core/mod_interface/tests/inc/derive/layer_have_layer/layer_b.rs +++ b/module/core/mod_interface/tests/inc/derive/layer_have_layer/layer_b.rs @@ -39,7 +39,7 @@ pub struct SubStruct2 // -mod_interface! +the_module::mod_interface! { own use layer_b_own; diff --git a/module/core/mod_interface/tests/inc/derive/layer_have_layer/mod.rs b/module/core/mod_interface/tests/inc/derive/layer_have_layer/mod.rs index 6cf3f6db29..219360f435 100644 --- a/module/core/mod_interface/tests/inc/derive/layer_have_layer/mod.rs +++ b/module/core/mod_interface/tests/inc/derive/layer_have_layer/mod.rs @@ -11,7 +11,7 @@ mod private { } -mod_interface! +the_module::mod_interface! { /// layer_a diff --git a/module/core/mod_interface/tests/inc/derive/layer_have_layer_cfg/layer_a.rs b/module/core/mod_interface/tests/inc/derive/layer_have_layer_cfg/layer_a.rs index dfffa8d8a8..082005e6be 100644 --- a/module/core/mod_interface/tests/inc/derive/layer_have_layer_cfg/layer_a.rs +++ b/module/core/mod_interface/tests/inc/derive/layer_have_layer_cfg/layer_a.rs @@ -33,7 +33,7 @@ mod private // -mod_interface! +the_module::mod_interface! { // orphan use super::private:: diff --git a/module/core/mod_interface/tests/inc/derive/layer_have_layer_cfg/layer_b.rs b/module/core/mod_interface/tests/inc/derive/layer_have_layer_cfg/layer_b.rs index 9f17f61637..1d265d3c4f 100644 --- a/module/core/mod_interface/tests/inc/derive/layer_have_layer_cfg/layer_b.rs +++ b/module/core/mod_interface/tests/inc/derive/layer_have_layer_cfg/layer_b.rs @@ -39,7 +39,7 @@ pub struct SubStruct2 // -mod_interface! +the_module::mod_interface! { own use layer_b_own; diff --git a/module/core/mod_interface/tests/inc/derive/layer_have_layer_cfg/mod.rs b/module/core/mod_interface/tests/inc/derive/layer_have_layer_cfg/mod.rs index 09c564f64b..b0fc4d5d70 100644 --- a/module/core/mod_interface/tests/inc/derive/layer_have_layer_cfg/mod.rs +++ b/module/core/mod_interface/tests/inc/derive/layer_have_layer_cfg/mod.rs @@ -11,7 +11,7 @@ mod private { } -mod_interface! +the_module::mod_interface! { /// layer_a diff --git a/module/core/mod_interface/tests/inc/derive/layer_have_layer_separate_use/layer_a.rs b/module/core/mod_interface/tests/inc/derive/layer_have_layer_separate_use/layer_a.rs index 8d8d6b1faf..c71e0af7d2 100644 --- a/module/core/mod_interface/tests/inc/derive/layer_have_layer_separate_use/layer_a.rs +++ b/module/core/mod_interface/tests/inc/derive/layer_have_layer_separate_use/layer_a.rs @@ -33,7 +33,7 @@ mod private // -mod_interface! +the_module::mod_interface! { own use { layer_a_own }; diff --git a/module/core/mod_interface/tests/inc/derive/layer_have_layer_separate_use/layer_b.rs b/module/core/mod_interface/tests/inc/derive/layer_have_layer_separate_use/layer_b.rs index 9f17f61637..1d265d3c4f 100644 --- a/module/core/mod_interface/tests/inc/derive/layer_have_layer_separate_use/layer_b.rs +++ b/module/core/mod_interface/tests/inc/derive/layer_have_layer_separate_use/layer_b.rs @@ -39,7 +39,7 @@ pub struct SubStruct2 // -mod_interface! +the_module::mod_interface! { own use layer_b_own; diff --git a/module/core/mod_interface/tests/inc/derive/layer_have_layer_separate_use/mod.rs b/module/core/mod_interface/tests/inc/derive/layer_have_layer_separate_use/mod.rs index c34df1c831..0d2ec22d26 100644 --- a/module/core/mod_interface/tests/inc/derive/layer_have_layer_separate_use/mod.rs +++ b/module/core/mod_interface/tests/inc/derive/layer_have_layer_separate_use/mod.rs @@ -12,11 +12,11 @@ mod private } /// layer_a -mod layer_a; +pub mod layer_a; /// layer_b -mod layer_b; +pub mod layer_b; -mod_interface! +the_module::mod_interface! { /// layer_a diff --git a/module/core/mod_interface/tests/inc/derive/layer_have_layer_separate_use_two/layer_a.rs b/module/core/mod_interface/tests/inc/derive/layer_have_layer_separate_use_two/layer_a.rs index 8d8d6b1faf..c71e0af7d2 100644 --- a/module/core/mod_interface/tests/inc/derive/layer_have_layer_separate_use_two/layer_a.rs +++ b/module/core/mod_interface/tests/inc/derive/layer_have_layer_separate_use_two/layer_a.rs @@ -33,7 +33,7 @@ mod private // -mod_interface! +the_module::mod_interface! { own use { layer_a_own }; diff --git a/module/core/mod_interface/tests/inc/derive/layer_have_layer_separate_use_two/layer_b.rs b/module/core/mod_interface/tests/inc/derive/layer_have_layer_separate_use_two/layer_b.rs index 9f17f61637..1d265d3c4f 100644 --- a/module/core/mod_interface/tests/inc/derive/layer_have_layer_separate_use_two/layer_b.rs +++ b/module/core/mod_interface/tests/inc/derive/layer_have_layer_separate_use_two/layer_b.rs @@ -39,7 +39,7 @@ pub struct SubStruct2 // -mod_interface! +the_module::mod_interface! { own use layer_b_own; diff --git a/module/core/mod_interface/tests/inc/derive/layer_have_layer_separate_use_two/mod.rs b/module/core/mod_interface/tests/inc/derive/layer_have_layer_separate_use_two/mod.rs index 4774b23347..c20f8d770a 100644 --- a/module/core/mod_interface/tests/inc/derive/layer_have_layer_separate_use_two/mod.rs +++ b/module/core/mod_interface/tests/inc/derive/layer_have_layer_separate_use_two/mod.rs @@ -12,11 +12,11 @@ mod private } /// layer_a -mod layer_a; +pub mod layer_a; /// layer_b -mod layer_b; +pub mod layer_b; -mod_interface! +the_module::mod_interface! { // zzz : test with `layer { layer_a, layer_a };` diff --git a/module/core/mod_interface/tests/inc/derive/layer_have_mod_cfg/mod.rs b/module/core/mod_interface/tests/inc/derive/layer_have_mod_cfg/mod.rs index 2188f4a6b3..38ff58d0eb 100644 --- a/module/core/mod_interface/tests/inc/derive/layer_have_mod_cfg/mod.rs +++ b/module/core/mod_interface/tests/inc/derive/layer_have_mod_cfg/mod.rs @@ -11,7 +11,7 @@ mod private { } -mod_interface! +the_module::mod_interface! { /// mod_a diff --git a/module/core/mod_interface/tests/inc/derive/layer_unknown_vis/trybuild.stderr b/module/core/mod_interface/tests/inc/derive/layer_unknown_vis/trybuild.stderr index 2d2aa78ea8..125e29bbdb 100644 --- a/module/core/mod_interface/tests/inc/derive/layer_unknown_vis/trybuild.stderr +++ b/module/core/mod_interface/tests/inc/derive/layer_unknown_vis/trybuild.stderr @@ -1,4 +1,4 @@ -error: expected one of: `mod`, `use`, `layer` +error: expected one of: `mod`, `use`, `layer`, `reuse` --> tests/inc/derive/layer_unknown_vis/mod.rs | | xyz layer layer_a; diff --git a/module/core/mod_interface/tests/inc/derive/layer_use_cfg/layer_a.rs b/module/core/mod_interface/tests/inc/derive/layer_use_cfg/layer_a.rs index dfffa8d8a8..082005e6be 100644 --- a/module/core/mod_interface/tests/inc/derive/layer_use_cfg/layer_a.rs +++ b/module/core/mod_interface/tests/inc/derive/layer_use_cfg/layer_a.rs @@ -33,7 +33,7 @@ mod private // -mod_interface! +the_module::mod_interface! { // orphan use super::private:: diff --git a/module/core/mod_interface/tests/inc/derive/layer_use_cfg/layer_b.rs b/module/core/mod_interface/tests/inc/derive/layer_use_cfg/layer_b.rs index 9f17f61637..1d265d3c4f 100644 --- a/module/core/mod_interface/tests/inc/derive/layer_use_cfg/layer_b.rs +++ b/module/core/mod_interface/tests/inc/derive/layer_use_cfg/layer_b.rs @@ -39,7 +39,7 @@ pub struct SubStruct2 // -mod_interface! +the_module::mod_interface! { own use layer_b_own; diff --git a/module/core/mod_interface/tests/inc/derive/layer_use_cfg/mod.rs b/module/core/mod_interface/tests/inc/derive/layer_use_cfg/mod.rs index 0c730db3a7..d9eedf0a3e 100644 --- a/module/core/mod_interface/tests/inc/derive/layer_use_cfg/mod.rs +++ b/module/core/mod_interface/tests/inc/derive/layer_use_cfg/mod.rs @@ -12,12 +12,13 @@ mod private } /// layer_a -mod layer_a; +pub mod layer_a; /// layer_b -mod layer_b; +pub mod layer_b; -mod_interface! +the_module::mod_interface! { + // #![ debug ] /// layer_a use super::layer_a; @@ -33,4 +34,3 @@ mod_interface! // include!( "../../only_test/layer_simple_only_test.rs" ); - diff --git a/module/core/mod_interface/tests/inc/derive/layer_use_macro/layer_a.rs b/module/core/mod_interface/tests/inc/derive/layer_use_macro/layer_a.rs index 12db22ecfc..b37c839cd0 100644 --- a/module/core/mod_interface/tests/inc/derive/layer_use_macro/layer_a.rs +++ b/module/core/mod_interface/tests/inc/derive/layer_use_macro/layer_a.rs @@ -35,7 +35,7 @@ mod private // -mod_interface! +the_module::mod_interface! { // exposed( crate ) use macro1; diff --git a/module/core/mod_interface/tests/inc/derive/layer_use_macro/mod.rs b/module/core/mod_interface/tests/inc/derive/layer_use_macro/mod.rs index 1bb9569c90..67a972b145 100644 --- a/module/core/mod_interface/tests/inc/derive/layer_use_macro/mod.rs +++ b/module/core/mod_interface/tests/inc/derive/layer_use_macro/mod.rs @@ -11,7 +11,7 @@ mod private { } -mod_interface! +the_module::mod_interface! { /// layer_a diff --git a/module/core/mod_interface/tests/inc/derive/micro_modules/mod.rs b/module/core/mod_interface/tests/inc/derive/micro_modules/mod.rs index 09c94a139e..57adff6ff2 100644 --- a/module/core/mod_interface/tests/inc/derive/micro_modules/mod.rs +++ b/module/core/mod_interface/tests/inc/derive/micro_modules/mod.rs @@ -6,8 +6,9 @@ mod private { } -mod_interface! +the_module::mod_interface! { + // #![ debug ] /// mod_own own mod mod_own; diff --git a/module/core/mod_interface/tests/inc/derive/micro_modules/mod_protected.rs b/module/core/mod_interface/tests/inc/derive/micro_modules/mod_own.rs similarity index 100% rename from module/core/mod_interface/tests/inc/derive/micro_modules/mod_protected.rs rename to module/core/mod_interface/tests/inc/derive/micro_modules/mod_own.rs diff --git a/module/core/mod_interface/tests/inc/derive/micro_modules_bad_vis/trybuild.stderr b/module/core/mod_interface/tests/inc/derive/micro_modules_bad_vis/trybuild.stderr index b84160eec0..d73c3c374c 100644 --- a/module/core/mod_interface/tests/inc/derive/micro_modules_bad_vis/trybuild.stderr +++ b/module/core/mod_interface/tests/inc/derive/micro_modules_bad_vis/trybuild.stderr @@ -1,4 +1,4 @@ -error: To include a non-standard module use either [ private, protected, orphan, exposed, prelude ] visibility: +error: To include a non-standard module use either [ private, own, orphan, exposed, prelude ] visibility: #[doc = " mod_exposed"] pub mod mod_exposed; --> tests/inc/derive/micro_modules_bad_vis/mod.rs | diff --git a/module/core/mod_interface/tests/inc/manual/micro_modules/mod_protected.rs b/module/core/mod_interface/tests/inc/derive/micro_modules_glob/child.rs similarity index 100% rename from module/core/mod_interface/tests/inc/manual/micro_modules/mod_protected.rs rename to module/core/mod_interface/tests/inc/derive/micro_modules_glob/child.rs diff --git a/module/core/mod_interface/tests/inc/derive/micro_modules_glob/mod.rs b/module/core/mod_interface/tests/inc/derive/micro_modules_glob/mod.rs new file mode 100644 index 0000000000..f567778739 --- /dev/null +++ b/module/core/mod_interface/tests/inc/derive/micro_modules_glob/mod.rs @@ -0,0 +1,28 @@ + +// use super::*; + +/// Define a private namespace for all its items. +mod private +{ + pub struct Struct1; + pub struct Struct2; +} + +// + +crate::the_module::mod_interface! +{ + own use + { + * + }; +} + +// + +#[ test ] +fn basic() +{ + let _s1 = Struct1; + let _s2 = Struct2; +} diff --git a/module/core/mod_interface/tests/inc/derive/micro_modules_two/mod.rs b/module/core/mod_interface/tests/inc/derive/micro_modules_two/mod.rs index 9071caf2d1..c62e6f7e18 100644 --- a/module/core/mod_interface/tests/inc/derive/micro_modules_two/mod.rs +++ b/module/core/mod_interface/tests/inc/derive/micro_modules_two/mod.rs @@ -6,7 +6,7 @@ mod private { } -mod_interface! +the_module::mod_interface! { /// mod_own1 diff --git a/module/core/mod_interface/tests/inc/derive/micro_modules_two/mod_protected1.rs b/module/core/mod_interface/tests/inc/derive/micro_modules_two/mod_own1.rs similarity index 100% rename from module/core/mod_interface/tests/inc/derive/micro_modules_two/mod_protected1.rs rename to module/core/mod_interface/tests/inc/derive/micro_modules_two/mod_own1.rs diff --git a/module/core/mod_interface/tests/inc/derive/micro_modules_two/mod_protected2.rs b/module/core/mod_interface/tests/inc/derive/micro_modules_two/mod_own2.rs similarity index 100% rename from module/core/mod_interface/tests/inc/derive/micro_modules_two/mod_protected2.rs rename to module/core/mod_interface/tests/inc/derive/micro_modules_two/mod_own2.rs diff --git a/module/core/mod_interface/tests/inc/derive/micro_modules_two_joined/mod.rs b/module/core/mod_interface/tests/inc/derive/micro_modules_two_joined/mod.rs index ced5712479..de2d1c2e88 100644 --- a/module/core/mod_interface/tests/inc/derive/micro_modules_two_joined/mod.rs +++ b/module/core/mod_interface/tests/inc/derive/micro_modules_two_joined/mod.rs @@ -6,7 +6,7 @@ mod private { } -mod_interface! +the_module::mod_interface! { own mod diff --git a/module/core/mod_interface/tests/inc/derive/micro_modules_two_joined/mod_protected1.rs b/module/core/mod_interface/tests/inc/derive/micro_modules_two_joined/mod_own1.rs similarity index 100% rename from module/core/mod_interface/tests/inc/derive/micro_modules_two_joined/mod_protected1.rs rename to module/core/mod_interface/tests/inc/derive/micro_modules_two_joined/mod_own1.rs diff --git a/module/core/mod_interface/tests/inc/derive/micro_modules_two_joined/mod_protected2.rs b/module/core/mod_interface/tests/inc/derive/micro_modules_two_joined/mod_own2.rs similarity index 100% rename from module/core/mod_interface/tests/inc/derive/micro_modules_two_joined/mod_protected2.rs rename to module/core/mod_interface/tests/inc/derive/micro_modules_two_joined/mod_own2.rs diff --git a/module/core/mod_interface/tests/inc/derive/micro_modules_unknown_vis/trybuild.stderr b/module/core/mod_interface/tests/inc/derive/micro_modules_unknown_vis/trybuild.stderr index 8df4ef8899..0fd927c7e0 100644 --- a/module/core/mod_interface/tests/inc/derive/micro_modules_unknown_vis/trybuild.stderr +++ b/module/core/mod_interface/tests/inc/derive/micro_modules_unknown_vis/trybuild.stderr @@ -1,4 +1,4 @@ -error: expected one of: `mod`, `use`, `layer` +error: expected one of: `mod`, `use`, `layer`, `reuse` --> tests/inc/derive/micro_modules_unknown_vis/mod.rs | | not_vis mod mod_exposed; diff --git a/module/core/mod_interface/tests/inc/derive/reuse_basic/child.rs b/module/core/mod_interface/tests/inc/derive/reuse_basic/child.rs new file mode 100644 index 0000000000..19fcd7abde --- /dev/null +++ b/module/core/mod_interface/tests/inc/derive/reuse_basic/child.rs @@ -0,0 +1,15 @@ +mod private +{ + pub struct Own; + pub struct Orphan; + pub struct Exposed; + pub struct Prelude; +} + +crate::the_module::mod_interface! +{ + own use Own; + orphan use Orphan; + exposed use Exposed; + prelude use Prelude; +} diff --git a/module/core/mod_interface/tests/inc/derive/reuse_basic/mod.rs b/module/core/mod_interface/tests/inc/derive/reuse_basic/mod.rs new file mode 100644 index 0000000000..8ee5259142 --- /dev/null +++ b/module/core/mod_interface/tests/inc/derive/reuse_basic/mod.rs @@ -0,0 +1,34 @@ + +// use super::*; + +/// Define a private namespace for all its items. +mod private +{ +} + +mod child; + +// + +crate::the_module::mod_interface! +{ + reuse child; +} + +// + +#[ test ] +fn basic() +{ + + let _ = child::Own; + let _ = child::Orphan; + let _ = child::Exposed; + let _ = child::Prelude; + + let _ = Own; + let _ = Orphan; + let _ = Exposed; + let _ = Prelude; + +} diff --git a/module/core/mod_interface/tests/inc/derive/use_as/derive.rs b/module/core/mod_interface/tests/inc/derive/use_as/derive.rs index 4c452702b0..1d0b464591 100644 --- a/module/core/mod_interface/tests/inc/derive/use_as/derive.rs +++ b/module/core/mod_interface/tests/inc/derive/use_as/derive.rs @@ -4,7 +4,9 @@ use super::*; /// Layer X pub mod layer_x; -mod_interface! +mod private {} + +the_module::mod_interface! { // #![ debug ] @@ -13,7 +15,7 @@ mod_interface! // /// layer_a // pub use super::layer_x as layer_a; - // xxx : make that working + // zzz : make that working } diff --git a/module/core/mod_interface/tests/inc/derive/use_as/manual_only.rs b/module/core/mod_interface/tests/inc/derive/use_as/manual_only.rs index f6fcb2f162..9ce347e8fb 100644 --- a/module/core/mod_interface/tests/inc/derive/use_as/manual_only.rs +++ b/module/core/mod_interface/tests/inc/derive/use_as/manual_only.rs @@ -3,7 +3,7 @@ use layer_x as layer_a; #[doc(inline)] #[allow(unused_imports)] -pub use protected :: * ; +pub use own :: * ; #[doc = r" Own namespace of the module."] #[ allow( unused_imports ) ] diff --git a/module/core/mod_interface/tests/inc/derive/use_bad_vis/trybuild.stderr b/module/core/mod_interface/tests/inc/derive/use_bad_vis/trybuild.stderr index b63d146f04..cb5d08876c 100644 --- a/module/core/mod_interface/tests/inc/derive/use_bad_vis/trybuild.stderr +++ b/module/core/mod_interface/tests/inc/derive/use_bad_vis/trybuild.stderr @@ -1,4 +1,4 @@ -error: Use either [ private, protected, orphan, exposed, prelude ] visibility: +error: Use either [ private, own, orphan, exposed, prelude ] visibility: #[doc = " layer_a"] pub use; --> tests/inc/derive/use_bad_vis/mod.rs | diff --git a/module/core/mod_interface/tests/inc/derive/use_basic/mod.rs b/module/core/mod_interface/tests/inc/derive/use_basic/mod.rs index 3cfbb3ad53..4afc8262c6 100644 --- a/module/core/mod_interface/tests/inc/derive/use_basic/mod.rs +++ b/module/core/mod_interface/tests/inc/derive/use_basic/mod.rs @@ -1,10 +1,14 @@ use super::*; -mod layer_a; -mod layer_b; +// private layer +pub mod layer_a; +// private layer +pub mod layer_b; -mod_interface! +mod private {} + +the_module::mod_interface! { /// layer_a diff --git a/module/core/mod_interface/tests/inc/derive/use_layer/layer_a.rs b/module/core/mod_interface/tests/inc/derive/use_layer/layer_a.rs index 14ecb25b3e..1b892a03b1 100644 --- a/module/core/mod_interface/tests/inc/derive/use_layer/layer_a.rs +++ b/module/core/mod_interface/tests/inc/derive/use_layer/layer_a.rs @@ -34,7 +34,7 @@ pub struct SubStruct4 // -mod_interface! +the_module::mod_interface! { orphan use ::std::vec::Vec; diff --git a/module/core/mod_interface/tests/inc/derive/use_layer/mod.rs b/module/core/mod_interface/tests/inc/derive/use_layer/mod.rs index 17247d2d07..4b4bfaa581 100644 --- a/module/core/mod_interface/tests/inc/derive/use_layer/mod.rs +++ b/module/core/mod_interface/tests/inc/derive/use_layer/mod.rs @@ -6,12 +6,7 @@ mod tools pub use super::super::*; } -// /// Private namespace of the module. -// mod private -// { -// } - -mod layer_a; +pub mod layer_a; /// SuperStruct1. #[ derive( Debug, PartialEq ) ] @@ -19,7 +14,9 @@ pub struct SuperStruct1 { } -mod_interface! +mod private {} + +the_module::mod_interface! { /// layer_a diff --git a/module/core/error_tools/src/untyped.rs b/module/core/mod_interface/tests/inc/derive/use_private_layers/layer_a.rs similarity index 63% rename from module/core/error_tools/src/untyped.rs rename to module/core/mod_interface/tests/inc/derive/use_private_layers/layer_a.rs index df16162bab..8c49982711 100644 --- a/module/core/error_tools/src/untyped.rs +++ b/module/core/mod_interface/tests/inc/derive/use_private_layers/layer_a.rs @@ -1,13 +1,9 @@ -/// Internal namespace. + +/// Private namespace of the module. mod private { - } -#[ doc( inline ) ] -#[ allow( unused_imports ) ] -pub use own::*; - /// Own namespace of the module. #[ allow( unused_imports ) ] pub mod own @@ -15,38 +11,29 @@ pub mod own use super::*; #[ doc( inline ) ] pub use orphan::*; - - #[ doc( inline ) ] - pub use ::anyhow:: + /// layer_a_own + pub fn layer_a_own() -> bool { - Chain, - Context, - Error, - Ok, - Result, - }; - + true + } } -/// Shared with parent namespace of the module +#[ doc( inline ) ] +#[ allow( unused_imports ) ] +pub use own::*; + +/// Orphan namespace of the module. #[ allow( unused_imports ) ] pub mod orphan { use super::*; - pub use super::super::untyped; - pub use super::super::untyped as for_app; - #[ doc( inline ) ] pub use exposed::*; - - #[ doc( inline ) ] - pub use ::anyhow:: + /// layer_a_orphan + pub fn layer_a_orphan() -> bool { - format_err, - ensure, - bail, - }; - + true + } } /// Exposed namespace of the module. @@ -54,10 +41,13 @@ pub mod orphan pub mod exposed { use super::*; - #[ doc( inline ) ] pub use prelude::*; - + /// layer_a_exposed + pub fn layer_a_exposed() -> bool + { + true + } } /// Prelude to use essentials: `use my_module::prelude::*`. @@ -65,4 +55,9 @@ pub mod exposed pub mod prelude { use super::*; -} \ No newline at end of file + /// layer_a_prelude + pub fn layer_a_prelude() -> bool + { + true + } +} diff --git a/module/core/mod_interface/tests/inc/derive/use_private_layers/layer_b.rs b/module/core/mod_interface/tests/inc/derive/use_private_layers/layer_b.rs new file mode 100644 index 0000000000..1e15689f05 --- /dev/null +++ b/module/core/mod_interface/tests/inc/derive/use_private_layers/layer_b.rs @@ -0,0 +1,63 @@ + +/// Private namespace of the module. +mod private +{ +} + +/// Own namespace of the module. +#[ allow( unused_imports ) ] +pub mod own +{ + use super::*; + #[ doc( inline ) ] + pub use orphan::*; + /// layer_b_own + pub fn layer_b_own() -> bool + { + true + } +} + +#[ 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::*; + /// layer_b_orphan + pub fn layer_b_orphan() -> bool + { + true + } +} + +/// Exposed namespace of the module. +#[ allow( unused_imports ) ] +pub mod exposed +{ + use super::*; + #[ doc( inline ) ] + pub use prelude::*; + /// layer_b_exposed + pub fn layer_b_exposed() -> bool + { + true + } +} + +/// Prelude to use essentials: `use my_module::prelude::*`. +#[ allow( unused_imports ) ] +pub mod prelude +{ + use super::*; + /// layer_b_prelude + pub fn layer_b_prelude() -> bool + { + true + } +} diff --git a/module/core/mod_interface/tests/inc/derive/use_private_layers/mod.rs b/module/core/mod_interface/tests/inc/derive/use_private_layers/mod.rs new file mode 100644 index 0000000000..531513253f --- /dev/null +++ b/module/core/mod_interface/tests/inc/derive/use_private_layers/mod.rs @@ -0,0 +1,28 @@ +#![ allow( dead_code ) ] +#![ allow( unused_imports ) ] + +use super::*; + +// private layer +mod layer_a; +// private layer +mod layer_b; + +mod private {} + +// xxx : qqq : make it working + +// the_module::mod_interface! +// { +// +// /// layer_a +// priv use super::layer_a; +// +// /// layer_b +// priv use super::layer_b; +// +// } +// +// // +// +// include!( "../../only_test/layer_simple_only_test.rs" ); diff --git a/module/core/mod_interface/tests/inc/derive/use_unknown_vis/trybuild.stderr b/module/core/mod_interface/tests/inc/derive/use_unknown_vis/trybuild.stderr index 530570d39a..0dc9fb08bc 100644 --- a/module/core/mod_interface/tests/inc/derive/use_unknown_vis/trybuild.stderr +++ b/module/core/mod_interface/tests/inc/derive/use_unknown_vis/trybuild.stderr @@ -1,4 +1,4 @@ -error: expected one of: `mod`, `use`, `layer` +error: expected one of: `mod`, `use`, `layer`, `reuse` --> tests/inc/derive/use_unknown_vis/mod.rs | | xyz use f1; diff --git a/module/core/mod_interface/tests/inc/manual/layer/mod.rs b/module/core/mod_interface/tests/inc/manual/layer/mod.rs index 044ff08dbf..adb8be65df 100644 --- a/module/core/mod_interface/tests/inc/manual/layer/mod.rs +++ b/module/core/mod_interface/tests/inc/manual/layer/mod.rs @@ -19,11 +19,13 @@ pub mod own #[ doc( inline ) ] pub use orphan::*; #[ doc( inline ) ] - #[ allow( unused_imports ) ] pub use super::layer_a::orphan::*; #[ doc( inline ) ] - #[ allow( unused_imports ) ] pub use super::layer_b::orphan::*; + #[ doc( inline ) ] + pub use super::layer_a; + #[ doc( inline ) ] + pub use super::layer_b; } #[ doc( inline ) ] diff --git a/module/core/mod_interface/tests/inc/manual/micro_modules/mod.rs b/module/core/mod_interface/tests/inc/manual/micro_modules/mod.rs index 65ba341ba1..5aa53251a1 100644 --- a/module/core/mod_interface/tests/inc/manual/micro_modules/mod.rs +++ b/module/core/mod_interface/tests/inc/manual/micro_modules/mod.rs @@ -1,3 +1,4 @@ +#![ allow( dead_code ) ] use super::*; diff --git a/module/core/mod_interface/tests/inc/manual/micro_modules/mod_own.rs b/module/core/mod_interface/tests/inc/manual/micro_modules/mod_own.rs new file mode 100644 index 0000000000..a6619cc0c4 --- /dev/null +++ b/module/core/mod_interface/tests/inc/manual/micro_modules/mod_own.rs @@ -0,0 +1,5 @@ +/// has_own +pub fn has_own() -> bool +{ + true +} \ No newline at end of file diff --git a/module/core/mod_interface/tests/inc/manual/micro_modules_two/mod_protected1.rs b/module/core/mod_interface/tests/inc/manual/micro_modules_two/mod_own1.rs similarity index 100% rename from module/core/mod_interface/tests/inc/manual/micro_modules_two/mod_protected1.rs rename to module/core/mod_interface/tests/inc/manual/micro_modules_two/mod_own1.rs diff --git a/module/core/mod_interface/tests/inc/manual/micro_modules_two/mod_protected2.rs b/module/core/mod_interface/tests/inc/manual/micro_modules_two/mod_own2.rs similarity index 100% rename from module/core/mod_interface/tests/inc/manual/micro_modules_two/mod_protected2.rs rename to module/core/mod_interface/tests/inc/manual/micro_modules_two/mod_own2.rs diff --git a/module/core/mod_interface/tests/inc/manual/layer_use/layer_a.rs b/module/core/mod_interface/tests/inc/manual/use_layer/layer_a.rs similarity index 100% rename from module/core/mod_interface/tests/inc/manual/layer_use/layer_a.rs rename to module/core/mod_interface/tests/inc/manual/use_layer/layer_a.rs diff --git a/module/core/mod_interface/tests/inc/manual/layer_use/layer_b.rs b/module/core/mod_interface/tests/inc/manual/use_layer/layer_b.rs similarity index 100% rename from module/core/mod_interface/tests/inc/manual/layer_use/layer_b.rs rename to module/core/mod_interface/tests/inc/manual/use_layer/layer_b.rs diff --git a/module/core/mod_interface/tests/inc/manual/layer_use/mod.rs b/module/core/mod_interface/tests/inc/manual/use_layer/mod.rs similarity index 93% rename from module/core/mod_interface/tests/inc/manual/layer_use/mod.rs rename to module/core/mod_interface/tests/inc/manual/use_layer/mod.rs index 044ff08dbf..43622260c8 100644 --- a/module/core/mod_interface/tests/inc/manual/layer_use/mod.rs +++ b/module/core/mod_interface/tests/inc/manual/use_layer/mod.rs @@ -24,6 +24,10 @@ pub mod own #[ doc( inline ) ] #[ allow( unused_imports ) ] pub use super::layer_b::orphan::*; + #[ doc( inline ) ] + pub use super::layer_a; + #[ doc( inline ) ] + pub use super::layer_b; } #[ doc( inline ) ] diff --git a/module/core/mod_interface/tests/inc/mod.rs b/module/core/mod_interface/tests/inc/mod.rs index 5d8aaa7045..1809e2f2e8 100644 --- a/module/core/mod_interface/tests/inc/mod.rs +++ b/module/core/mod_interface/tests/inc/mod.rs @@ -9,7 +9,7 @@ mod manual mod micro_modules; mod micro_modules_two; mod layer; - mod layer_use; + mod use_layer; } @@ -22,6 +22,7 @@ mod derive mod micro_modules; mod micro_modules_two; mod micro_modules_two_joined; + mod micro_modules_glob; // layer mod layer; @@ -33,16 +34,23 @@ mod derive mod layer_use_cfg; mod layer_use_macro; + // use mod use_layer; mod use_basic; + mod use_private_layers; #[ path = "./use_as/derive.rs" ] mod use_as_derive; #[ path = "./use_as/manual.rs" ] mod use_as_manual; + // reuse + mod reuse_basic; + // attr mod attr_debug; } mod trybuild_test; + +// xxx : enable \ No newline at end of file diff --git a/module/core/mod_interface/tests/inc/only_test/layer_simple_only_test.rs b/module/core/mod_interface/tests/inc/only_test/layer_simple_only_test.rs index 93b1190705..f62756f61a 100644 --- a/module/core/mod_interface/tests/inc/only_test/layer_simple_only_test.rs +++ b/module/core/mod_interface/tests/inc/only_test/layer_simple_only_test.rs @@ -7,6 +7,12 @@ tests_impls! fn basic() { + /* test.case( "layers themself" ); */ + { + a_id!( own::layer_a::layer_a_own(), true ); + a_id!( own::layer_b::layer_b_own(), true ); + } + /* test.case( "root" ); */ { a_id!( layer_a::layer_a_own(), true ); diff --git a/module/core/mod_interface/tests/inc/trybuild_test.rs b/module/core/mod_interface/tests/inc/trybuild_test.rs index 5acc2a4f29..f5dbbbaece 100644 --- a/module/core/mod_interface/tests/inc/trybuild_test.rs +++ b/module/core/mod_interface/tests/inc/trybuild_test.rs @@ -5,6 +5,8 @@ use super::*; // #[ cfg_attr( feature = "enabled", module_mod_interface ) ] +// xxx : qqq : enable it + // #[ cfg( module_mod_interface ) ] // #[ cfg( module_is_terminal ) ] #[ test_tools::nightly ] @@ -12,50 +14,49 @@ use super::*; fn trybuild_tests() { // qqq : fix test : if run its test with --target-dir flag it's fall (for example : cargo test --target-dir C:\foo\bar ) - // // use test_tools::dependency::trybuild; - // println!( "current_dir : {:?}", std::env::current_dir().unwrap() ); - // // let t = trybuild::TestCases::new(); - // let t = test_tools::compiletime::TestCases::new(); - // - // let current_exe_path = std::env::current_exe().expect( "No such file or directory" ); - // - // let exe_directory = dbg!(current_exe_path.parent().expect("No such file or directory")); - // fn find_workspace_root( start_path : &std::path::Path ) -> Option< &std::path::Path > - // { - // start_path - // .ancestors() - // .find( |path| path.join( "Cargo.toml" ).exists() ) - // } - // - // let workspace_root = find_workspace_root( exe_directory ).expect( "No such file or directory" ); - // let current_dir = workspace_root.join( "module/core/mod_interface" ); - // - // // micro module - // - // t.pass( current_dir.join( "tests/inc/derive/micro_modules/trybuild.rs" ) ); - // t.pass( current_dir.join( "tests/inc/derive/micro_modules_two/trybuild.rs" ) ); - // t.pass( current_dir.join( "tests/inc/derive/micro_modules_two_joined/trybuild.rs" ) ); - // - // // layer - // - // t.pass( current_dir.join( "tests/inc/derive/layer/trybuild.rs" ) ); - // t.pass( current_dir.join( "tests/inc/derive/layer_have_layer/trybuild.rs" ) ); - // t.pass( current_dir.join( "tests/inc/derive/layer_have_layer_separate_use/trybuild.rs" ) ); - // t.pass( current_dir.join( "tests/inc/derive/layer_have_layer_separate_use_two/trybuild.rs" ) ); - // t.pass( current_dir.join( "tests/inc/derive/layer_have_layer_cfg/trybuild.rs" ) ); - // t.pass( current_dir.join( "tests/inc/derive/layer_use_cfg/trybuild.rs" ) ); - // t.pass( current_dir.join( "tests/inc/derive/layer_have_mod_cfg/trybuild.rs" ) ); - // t.pass( current_dir.join( "tests/inc/derive/layer_use_macro/trybuild.rs" ) ); - // - // // use - // - // t.pass( current_dir.join( "tests/inc/derive/use_basic/trybuild.rs" ) ); - // t.pass( current_dir.join( "tests/inc/derive/use_layer/trybuild.rs" ) ); - // t.pass( current_dir.join( "tests/inc/derive/use_as/trybuild.rs" ) ); - // - // // attr - // - // t.pass( current_dir.join( "tests/inc/derive/attr_debug/trybuild.rs" ) ); + // use test_tools::dependency::trybuild; + println!( "current_dir : {:?}", std::env::current_dir().unwrap() ); + let t = test_tools::compiletime::TestCases::new(); + + let current_exe_path = std::env::current_exe().expect( "No such file or directory" ); + + let exe_directory = dbg!(current_exe_path.parent().expect("No such file or directory")); + fn find_workspace_root( start_path : &std::path::Path ) -> Option< &std::path::Path > + { + start_path + .ancestors() + .find( |path| path.join( "Cargo.toml" ).exists() ) + } + + let workspace_root = find_workspace_root( exe_directory ).expect( "No such file or directory" ); + let current_dir = workspace_root.join( "module/core/mod_interface" ); + + // micro module + + t.pass( current_dir.join( "tests/inc/derive/micro_modules/trybuild.rs" ) ); + t.pass( current_dir.join( "tests/inc/derive/micro_modules_two/trybuild.rs" ) ); + t.pass( current_dir.join( "tests/inc/derive/micro_modules_two_joined/trybuild.rs" ) ); + + // layer + + t.pass( current_dir.join( "tests/inc/derive/layer/trybuild.rs" ) ); + t.pass( current_dir.join( "tests/inc/derive/layer_have_layer/trybuild.rs" ) ); + t.pass( current_dir.join( "tests/inc/derive/layer_have_layer_separate_use/trybuild.rs" ) ); + t.pass( current_dir.join( "tests/inc/derive/layer_have_layer_separate_use_two/trybuild.rs" ) ); + t.pass( current_dir.join( "tests/inc/derive/layer_have_layer_cfg/trybuild.rs" ) ); + t.pass( current_dir.join( "tests/inc/derive/layer_use_cfg/trybuild.rs" ) ); + t.pass( current_dir.join( "tests/inc/derive/layer_have_mod_cfg/trybuild.rs" ) ); + t.pass( current_dir.join( "tests/inc/derive/layer_use_macro/trybuild.rs" ) ); + + // use + + t.pass( current_dir.join( "tests/inc/derive/use_basic/trybuild.rs" ) ); + t.pass( current_dir.join( "tests/inc/derive/use_layer/trybuild.rs" ) ); + t.pass( current_dir.join( "tests/inc/derive/use_as/trybuild.rs" ) ); + + // attr + + t.pass( current_dir.join( "tests/inc/derive/attr_debug/trybuild.rs" ) ); // } @@ -69,29 +70,28 @@ only_for_terminal_module! fn cta_trybuild_tests() { // qqq : fix test : if run its test with --target-dir flag it's fall (for example : cargo test --target-dir C:\foo\bar ) - // use test_tools::dependency::trybuild; - // println!( "current_dir : {:?}", std::env::current_dir().unwrap() ); - // // let t = trybuild::TestCases::new(); - // let t = test_tools::compiletime::TestCases::new(); - // - // let current_exe_path = std::env::current_exe().expect( "No such file or directory" ); - // - // let exe_directory = current_exe_path.parent().expect( "No such file or directory" ); - // fn find_workspace_root( start_path : &std::path::Path ) -> Option< &std::path::Path > - // { - // start_path - // .ancestors() - // .find( |path| path.join( "Cargo.toml" ).exists() ) - // } - // - // let workspace_root = find_workspace_root( exe_directory ).expect( "No such file or directory" ); - // let current_dir = workspace_root.join( "module/core/mod_interface" ); - // - // t.compile_fail( current_dir.join( "tests/inc/derive/micro_modules_bad_vis/trybuild.rs" ) ); - // t.compile_fail( current_dir.join( "tests/inc/derive/micro_modules_unknown_vis/trybuild.rs" ) ); - // t.compile_fail( current_dir.join( "tests/inc/derive/layer_bad_vis/trybuild.rs" ) ); - // t.compile_fail( current_dir.join( "tests/inc/derive/layer_unknown_vis/trybuild.rs" ) ); - // t.compile_fail( current_dir.join( "tests/inc/derive/use_bad_vis/trybuild.rs" ) ); - // t.compile_fail( current_dir.join( "tests/inc/derive/use_unknown_vis/trybuild.rs" ) ); + use test_tools::dependency::trybuild; + println!( "current_dir : {:?}", std::env::current_dir().unwrap() ); + let t = test_tools::compiletime::TestCases::new(); + + let current_exe_path = std::env::current_exe().expect( "No such file or directory" ); + + let exe_directory = current_exe_path.parent().expect( "No such file or directory" ); + fn find_workspace_root( start_path : &std::path::Path ) -> Option< &std::path::Path > + { + start_path + .ancestors() + .find( |path| path.join( "Cargo.toml" ).exists() ) + } + + let workspace_root = find_workspace_root( exe_directory ).expect( "No such file or directory" ); + let current_dir = workspace_root.join( "module/core/mod_interface" ); + + t.compile_fail( current_dir.join( "tests/inc/derive/micro_modules_bad_vis/trybuild.rs" ) ); + t.compile_fail( current_dir.join( "tests/inc/derive/micro_modules_unknown_vis/trybuild.rs" ) ); + t.compile_fail( current_dir.join( "tests/inc/derive/layer_bad_vis/trybuild.rs" ) ); + t.compile_fail( current_dir.join( "tests/inc/derive/layer_unknown_vis/trybuild.rs" ) ); + t.compile_fail( current_dir.join( "tests/inc/derive/use_bad_vis/trybuild.rs" ) ); + t.compile_fail( current_dir.join( "tests/inc/derive/use_unknown_vis/trybuild.rs" ) ); } } diff --git a/module/core/mod_interface/tests/smoke_test.rs b/module/core/mod_interface/tests/smoke_test.rs index 828e9b016b..d826b0e72a 100644 --- a/module/core/mod_interface/tests/smoke_test.rs +++ b/module/core/mod_interface/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke tests #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/core/mod_interface/tests/tests.rs b/module/core/mod_interface/tests/tests.rs index 33120affda..7736531699 100644 --- a/module/core/mod_interface/tests/tests.rs +++ b/module/core/mod_interface/tests/tests.rs @@ -1,3 +1,5 @@ +//! Main tests +#![ allow( unused_imports ) ] /// A struct for testing purpose. #[ derive( Debug, PartialEq ) ] @@ -5,9 +7,7 @@ pub struct CrateStructForTesting1 { } -#[ allow( unused_imports ) ] use ::mod_interface as the_module; -#[ allow( unused_imports ) ] use test_tools::exposed::*; #[ path="../../../../module/step/meta/src/module/terminal.rs" ] mod terminal; diff --git a/module/core/mod_interface_meta/Cargo.toml b/module/core/mod_interface_meta/Cargo.toml index 05f0bbf285..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.23.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 e3e9e057cf..a23529f45b 100644 --- a/module/core/mod_interface_meta/License +++ b/module/core/mod_interface_meta/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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_meta/Readme.md b/module/core/mod_interface_meta/Readme.md index d9b2a9bd8b..d953c21eca 100644 --- a/module/core/mod_interface_meta/Readme.md +++ b/module/core/mod_interface_meta/Readme.md @@ -1,11 +1,11 @@ -# Module :: mod_interface_meta +# Module :: `mod_interface_meta` [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_mod_interface_meta_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_mod_interface_meta_push.yml) [![docs.rs](https://img.shields.io/docsrs/mod_interface_meta?color=e3e8f0&logo=docs.rs)](https://docs.rs/mod_interface_meta) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Protocol of modularity unifying interface of a module and introducing layers. -Not intended to be used without runtime. This module and runtime is aggregate in module::mod_interface is [here](https://github.com/Wandalen/wTools/tree/master/module/core/mod_interface). -module and runtime is aggregate in module::mod_interface is [here](https://github.com/Wandalen/wTools/tree/master/module/core/mod_interface). +Not intended to be used without runtime. This module and runtime is aggregate in `module::mod_interface` is [here](https://github.com/Wandalen/wTools/tree/master/module/core/mod_interface). +module and runtime is aggregate in `module::mod_interface` is [here](https://github.com/Wandalen/wTools/tree/master/module/core/mod_interface). diff --git a/module/core/mod_interface_meta/src/impls.rs b/module/core/mod_interface_meta/src/impls.rs index 737dab4d78..6cfd989ffe 100644 --- a/module/core/mod_interface_meta/src/impls.rs +++ b/module/core/mod_interface_meta/src/impls.rs @@ -1,7 +1,9 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; + #[ allow( clippy::wildcard_imports ) ] use macro_tools::exposed::*; use std::collections::HashMap; @@ -16,7 +18,7 @@ mod private // = ? // x - // protected protected1; + // own own1; // orphan orphan1; // exposed exposed1; // prelude prelude1; @@ -33,7 +35,7 @@ mod private // x // orphan macromod mod_orphan1; - // : protected -> protected + // : own -> own // : orphan -> orphan // : exposed -> orphan // : prelude -> orphan @@ -42,14 +44,14 @@ mod private // x // prelude exposed macromod mod_own1; - // : protected -> exposed + // : own -> exposed // : orphan -> exposed // : exposed -> exposed // : prelude -> prelude // x - // prelude protected macromod mod_exposed1; - // : protected -> protected + // prelude own macromod mod_exposed1; + // : own -> own // : orphan -> orphan // : exposed -> exposed // : prelude -> prelude @@ -58,14 +60,14 @@ mod private // x // exposed exposed macromod mod_exposed1; - // : protected -> exposed + // : own -> exposed // : orphan -> exposed // : exposed -> exposed // : prelude -> exposed // x // exposed orphan macromod mod_exposed1; - // : protected -> orphan + // : own -> orphan // : orphan -> orphan // : exposed -> exposed // : prelude -> exposed @@ -102,12 +104,10 @@ mod private /// /// Handle record "use" with implicit visibility. /// - #[ allow ( dead_code ) ] - fn record_use_implicit + fn record_reuse_implicit ( record : &Record, c : &'_ mut RecordContext< '_ >, - // clauses_map : &mut HashMap< u32, Vec< proc_macro2::TokenStream > >, ) -> syn::Result< () > @@ -115,35 +115,88 @@ mod private let attrs1 = &record.attrs; let path = record.use_elements.as_ref().unwrap(); - // let vis = record.vis.clone(); - // if vis == Visibility::Inherited + let path = if let Some( rename ) = &path.rename + { + let pure_path = path.pure_without_super_path()?; + c.clauses_map.get_mut( &ClauseImmediates::Kind() ).unwrap().push( qt! + { + pub use #pure_path as #rename; + }); + parse_qt!{ #rename } + } + else + { + path.clone() + }; - // xxx + let adjsuted_path = path.prefixed_with_all(); - // let _path; - // let path2 = if path.prefix_is_needed() - // { - // _path = parse_qt!{ super::private::#path }; - // &_path - // } - // else - // { - // path - // }; + c.clauses_map.get_mut( &VisOwn::Kind() ).unwrap().push( qt! + { + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + #attrs1 + pub use #adjsuted_path::own::*; + }); - let adjsuted_path = path.adjsuted_implicit_path()?; + c.clauses_map.get_mut( &VisOrphan::Kind() ).unwrap().push( qt! + { + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + #attrs1 + pub use #adjsuted_path::orphan::*; + }); + + c.clauses_map.get_mut( &VisExposed::Kind() ).unwrap().push( qt! + { + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + #attrs1 + pub use #adjsuted_path::exposed::*; + }); + + c.clauses_map.get_mut( &VisPrelude::Kind() ).unwrap().push( qt! + { + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + #attrs1 + pub use #adjsuted_path::prelude::*; + }); + + Ok( () ) + } + + /// + /// Handle record "use" with implicit visibility. + /// + fn record_use_implicit + ( + record : &Record, + c : &'_ mut RecordContext< '_ >, + ) + -> + syn::Result< () > + { - // println!( "adjsuted_path : {}", qt!{ #adjsuted_path } ); + let attrs1 = &record.attrs; + let path = record.use_elements.as_ref().unwrap(); - if let Some( rename ) = &path.rename + let path = if let Some( rename ) = &path.rename { let pure_path = path.pure_without_super_path()?; c.clauses_map.get_mut( &ClauseImmediates::Kind() ).unwrap().push( qt! { pub use #pure_path as #rename; }); + parse_qt!{ #rename } } + else + { + path.clone() + }; + + let adjsuted_path = path.prefixed_with_all(); c.clauses_map.get_mut( &VisOwn::Kind() ).unwrap().push( qt! { @@ -153,6 +206,16 @@ mod private pub use #adjsuted_path::orphan::*; }); + // export layer as own field of current layer + let prefixed_with_super_maybe = path.prefixed_with_super_maybe(); + c.clauses_map.get_mut( &VisOwn::Kind() ).unwrap().push( qt! + { + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + #attrs1 + pub use #prefixed_with_super_maybe; + }); + c.clauses_map.get_mut( &VisExposed::Kind() ).unwrap().push( qt! { #[ doc( inline ) ] @@ -175,12 +238,10 @@ mod private /// /// Handle record "use" with explicit visibility. /// - #[ allow ( dead_code ) ] fn record_use_explicit ( record : &Record, c : &'_ mut RecordContext< '_ >, - // clauses_map : &mut HashMap< u32, Vec< proc_macro2::TokenStream > >, ) -> syn::Result< () > @@ -200,8 +261,7 @@ mod private )); } - let adjsuted_path = path.adjsuted_explicit_path(); - + let adjsuted_path = path.prefixed_with_all(); let vis2 = if vis.restriction().is_some() { qt!{ pub( crate ) } @@ -225,7 +285,6 @@ mod private /// /// Handle record micro module. /// - fn record_micro_module ( record : &Record, @@ -248,13 +307,16 @@ mod private if !record.vis.valid_sub_namespace() { - return Err( syn_err! + return Err ( - record, - "To include a non-standard module use either {} visibility:\n {}", - VALID_VISIBILITY_LIST_STR, - qt!{ #record }, - )); + syn_err! + ( + record, + "To include a non-standard module use either {} visibility:\n {}", + VALID_VISIBILITY_LIST_STR, + qt!{ #record }, + ) + ); } c.clauses_map.get_mut( &record.vis.kind() ).unwrap().push( qt! @@ -263,7 +325,8 @@ mod private #[ allow( unused_imports ) ] #attrs1 #attrs2 - pub use #path; + pub use __all__::#path; + // pub use super::#path; // xxx : remove super? }); @@ -310,7 +373,17 @@ mod private #[ allow( unused_imports ) ] #attrs1 #attrs2 - pub use #path::orphan::*; + pub use __all__::#path::orphan::*; + }); + + // export layer as own field of current layer + // let prefixed_with_super_maybe = path.prefixed_with_super_maybe(); + c.clauses_map.get_mut( &VisOwn::Kind() ).unwrap().push( qt! + { + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + #attrs1 + pub use super::#path; }); c.clauses_map.get_mut( &VisExposed::Kind() ).unwrap().push( qt! @@ -319,7 +392,7 @@ mod private #[ allow( unused_imports ) ] #attrs1 #attrs2 - pub use #path::exposed::*; + pub use __all__::#path::exposed::*; }); c.clauses_map.get_mut( &VisPrelude::Kind() ).unwrap().push( qt! @@ -328,7 +401,7 @@ mod private #[ allow( unused_imports ) ] #attrs1 #attrs2 - pub use #path::prelude::*; + pub use __all__::#path::prelude::*; }); Ok( () ) @@ -337,9 +410,10 @@ mod private /// /// Protocol of modularity unifying interface of a module and introducing layers. /// - #[ allow ( dead_code ) ] + #[ allow ( dead_code, clippy::too_many_lines ) ] pub fn mod_interface( input : proc_macro::TokenStream ) -> syn::Result< proc_macro2::TokenStream > { + #[ allow( clippy::enum_glob_use ) ] use ElementType::*; let original_input = input.clone(); @@ -383,6 +457,23 @@ mod private record_use_explicit( record, &mut record_context )?; } }, + Reuse( _ ) => + { + let vis = &record.vis; + if vis == &Visibility::Inherited + { + record_reuse_implicit( record, &mut record_context )?; + } + else + { + return Err( syn_err! + ( + record, + "Using visibility usesd before `reuse` is illegal\n{}", + qt!{ #record }, + )); + } + }, _ => { record.elements.iter().try_for_each( | element | -> syn::Result::< () > @@ -397,20 +488,21 @@ mod private { record_layer( record, element, &mut record_context )?; }, - Use( _ ) => + _ => { + panic!( "Unexpected" ) }, } syn::Result::Ok( () ) })?; } - }; + } syn::Result::Ok( () ) })?; let immediates_clause = clauses_map.get( &ClauseImmediates::Kind() ).unwrap(); - let protected_clause = clauses_map.get( &VisOwn::Kind() ).unwrap(); + let own_clause = clauses_map.get( &VisOwn::Kind() ).unwrap(); let orphan_clause = clauses_map.get( &VisOrphan::Kind() ).unwrap(); let exposed_clause = clauses_map.get( &VisExposed::Kind() ).unwrap(); let prelude_clause = clauses_map.get( &VisPrelude::Kind() ).unwrap(); @@ -420,6 +512,8 @@ mod private #( #immediates_clause )* + // use private as __private__; // this line is necessary for readable error in case private namespace is not present + #[ doc( inline ) ] #[ allow( unused_imports ) ] pub use own::*; @@ -428,19 +522,34 @@ mod private #[ allow( unused_imports ) ] pub mod own { - use super::*; + // There must be internal private namespace + // Because it's not possible to direcly make `use super::*;` + // Because then items from super can't be exposed publicly complaining: + // `error[E0428]: the name `mod1` is defined multiple times` + // use super::*; + use super::private; // this line is necessary for readable error in case private namespace is not present + mod __all__ + { + pub use super::super::*; + pub use super::super::private::*; + } #[ doc( inline ) ] - pub use orphan::*; - #( #protected_clause )* + pub use super::orphan::*; + #( #own_clause )* } /// Orphan namespace of the module. #[ allow( unused_imports ) ] pub mod orphan { - use super::*; + // use super::*; + mod __all__ + { + pub use super::super::*; + pub use super::super::private::*; + } #[ doc( inline ) ] - pub use exposed::*; + pub use super::exposed::*; #( #orphan_clause )* } @@ -448,9 +557,14 @@ mod private #[ allow( unused_imports ) ] pub mod exposed { - use super::*; + // use super::*; + mod __all__ + { + pub use super::super::*; + pub use super::super::private::*; + } #[ doc( inline ) ] - pub use prelude::*; + pub use super::prelude::*; #( #exposed_clause )* } @@ -458,7 +572,12 @@ mod private #[ allow( unused_imports ) ] pub mod prelude { - use super::*; + // use super::*; + mod __all__ + { + pub use super::super::*; + pub use super::super::private::*; + } #( #prelude_clause )* } @@ -466,7 +585,7 @@ mod private if has_debug { - let about = format!( "derive : mod_interface" ); + let about = "derive : mod_interface"; diag::report_print( about, &original_input, &result ); } @@ -484,6 +603,7 @@ mod private #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use orphan::*; } @@ -494,6 +614,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use exposed::*; } @@ -502,6 +623,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use prelude::*; pub use private:: @@ -513,6 +635,7 @@ pub mod exposed #[ allow( unused_imports ) ] pub mod prelude { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use private:: { diff --git a/module/core/mod_interface_meta/src/lib.rs b/module/core/mod_interface_meta/src/lib.rs index bb595ba9a2..fe22c8b29c 100644 --- a/module/core/mod_interface_meta/src/lib.rs +++ b/module/core/mod_interface_meta/src/lib.rs @@ -1,9 +1,23 @@ #![ 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/mod_interface_meta/latest/mod_interface_meta/" ) ] -#![ deny( dead_code ) ] #![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] +#![ warn( dead_code ) ] + +// /// Derives. +// layer derive; +// own use super::derive; +// // xxx : change to remove need to write explicitly that + +// xxx : change to remove need to write explicitly that +// crate::mod_interface! +// { +// /// Derives. +// layer derive; +// own use super::derive; // xxx : change to remove need to write explicitly that +// } + // xxx : clean up, ad solve problems // - example based on simpified version of test::layer_have_layer with single sublayer // - example with attribute `#![ debug ]` @@ -29,7 +43,7 @@ // }; // } -// xxx : make use proper_path_tools::own::path working +// xxx : make use pth::own::path working // xxx : put modular files into a namespace `file` maybe // #[ cfg( feature = "enabled" ) ] @@ -79,16 +93,18 @@ mod impls; #[ allow( unused_imports ) ] use impls::exposed::*; mod record; +#[ allow( clippy::wildcard_imports ) ] use record::exposed::*; mod visibility; +#[ allow( clippy::wildcard_imports ) ] use visibility::exposed::*; mod use_tree; +#[ allow( clippy::wildcard_imports ) ] use use_tree::exposed::*; /// /// Protocol of modularity unifying interface of a module and introducing layers. /// - #[ cfg( feature = "enabled" ) ] #[ proc_macro ] pub fn mod_interface( input : proc_macro::TokenStream ) -> proc_macro::TokenStream diff --git a/module/core/mod_interface_meta/src/record.rs b/module/core/mod_interface_meta/src/record.rs index c60b0bb55c..8aa78aeb17 100644 --- a/module/core/mod_interface_meta/src/record.rs +++ b/module/core/mod_interface_meta/src/record.rs @@ -1,16 +1,18 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; + #[ allow( clippy::wildcard_imports ) ] use macro_tools::exposed::*; /// /// Custom keywords. /// - pub mod kw { super::syn::custom_keyword!( layer ); + super::syn::custom_keyword!( reuse ); } /// @@ -23,6 +25,7 @@ mod private MicroModule( syn::token::Mod ), Layer( kw::layer ), Use( syn::token::Use ), + Reuse( kw::reuse ), } // @@ -47,6 +50,10 @@ mod private { ElementType::Layer( input.parse()? ) }, + _case if lookahead.peek( kw::reuse ) => + { + ElementType::Reuse( input.parse()? ) + }, _default => { return Err( lookahead.error() ) @@ -63,12 +70,14 @@ mod private { fn to_tokens( &self, tokens : &mut proc_macro2::TokenStream ) { + #[ allow( clippy::enum_glob_use ) ] use ElementType::*; match self { MicroModule( e ) => e.to_tokens( tokens ), Use( e ) => e.to_tokens( tokens ), Layer( e ) => e.to_tokens( tokens ), + Reuse( e ) => e.to_tokens( tokens ), } } } @@ -104,7 +113,7 @@ mod private match element_type { - ElementType::Use( _ ) => + ElementType::Use( _ ) | ElementType::Reuse( _ ) => { use_elements = Some( input.parse()? ); elements = syn::punctuated::Punctuated::new(); @@ -121,7 +130,7 @@ mod private { let ident = input.parse()?; elements = syn::punctuated::Punctuated::new(); - elements.push( Pair::new( Default::default(), ident ) ); + elements.push( Pair::new( AttributesOuter::default(), ident ) ); } }, } @@ -164,7 +173,6 @@ mod private /// /// Many records. /// - pub type Records = Many< Record >; impl AsMuchAsPossibleNoDelimiter for Record {} @@ -194,8 +202,7 @@ mod private // code_print!( attr.path() ); // code_print!( attr.meta ); - let good = true - && code_to_str!( attr.path() ) == "debug" + let good = code_to_str!( attr.path() ) == "debug" // && code_to_str!( attr.meta ).is_empty() ; @@ -261,6 +268,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use orphan::*; } @@ -269,6 +277,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use exposed::*; } @@ -277,6 +286,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use prelude::*; pub use private:: @@ -292,6 +302,7 @@ pub mod exposed #[ allow( unused_imports ) ] pub mod prelude { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use private:: { diff --git a/module/core/mod_interface_meta/src/use_tree.rs b/module/core/mod_interface_meta/src/use_tree.rs index f87ee133ad..513782408e 100644 --- a/module/core/mod_interface_meta/src/use_tree.rs +++ b/module/core/mod_interface_meta/src/use_tree.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use macro_tools::prelude::*; @@ -24,12 +24,13 @@ mod private /// Is adding prefix to the tree path required? /// Add `super::private::` to path unless it starts from `::` or `super` or `crate`. - pub fn prefix_is_needed( &self ) -> bool + pub fn private_prefix_is_needed( &self ) -> bool { + #[ allow( clippy::wildcard_imports, clippy::enum_glob_use ) ] use syn::UseTree::*; - // println!( "prefix_is_needed : {:?}", self ); - // println!( "prefix_is_needed : self.leading_colon : {:?}", self.leading_colon ); + // println!( "private_prefix_is_needed : {:?}", self ); + // println!( "private_prefix_is_needed : self.leading_colon : {:?}", self.leading_colon ); if self.leading_colon.is_some() { @@ -46,6 +47,7 @@ mod private /// Get pure path, cutting off `as module2` from `use module1 as module2`. pub fn pure_path( &self ) -> syn::Result< syn::punctuated::Punctuated< syn::Ident, Token![::] > > { + #[ allow( clippy::wildcard_imports, clippy::enum_glob_use ) ] use syn::UseTree::*; // let leading_colon = None; @@ -80,7 +82,7 @@ mod private { return Err( syn_err!( "Complex group uses like `use module1::{ module2, module3 }` are not supported." ) ); }, - }; + } } Ok( path ) @@ -92,11 +94,11 @@ mod private pub fn pure_without_super_path( &self ) -> syn::Result< syn::punctuated::Punctuated< syn::Ident, Token![::] > > { let path = self.pure_path()?; - if path.len() < 1 + if path.is_empty() { return Ok( path ); } - if path[ 0 ].to_string() == "super" + if path[ 0 ] == "super" { // let mut path2 = syn::punctuated::Punctuated::< syn::Ident, Token![::] >::new(); let path2 : syn::punctuated::Punctuated< syn::Ident, Token![::] > = path.into_iter().skip(1).collect(); @@ -105,32 +107,34 @@ mod private Ok( path ) } - /// Adjusted path. - /// Add `super::private::` to path unless it starts from `::` or `super` or `crate`. - pub fn adjsuted_implicit_path( &self ) -> syn::Result< syn::punctuated::Punctuated< syn::Ident, Token![::] > > + /// Prefix path with __all__ if it's appropriate. + pub fn prefixed_with_all( &self ) -> Self { + // use syn::UseTree::*; - let pure_path = self.pure_path()?; - if self.prefix_is_needed() + if self.private_prefix_is_needed() { - Ok( parse_qt!{ super::private::#pure_path } ) + let mut clone = self.clone(); + let tree = parse_qt!{ __all__::#self }; + clone.tree = tree; + clone } else { - Ok( pure_path ) + self.clone() } + } - /// Adjusted path. - /// Add `super::private::` to path unless it starts from `::` or `super` or `crate`. - // pub fn adjsuted_explicit_path( &self ) -> syn::UseTree - pub fn adjsuted_explicit_path( &self ) -> Self + /// Prefix path with `super::` if it's appropriate to avoid "re-export of crate public `child`" problem. + pub fn prefixed_with_super_maybe( &self ) -> Self { + // use syn::UseTree::*; - if self.prefix_is_needed() + if self.private_prefix_is_needed() { let mut clone = self.clone(); - let tree = parse_qt!{ super::private::#self }; + let tree = parse_qt!{ super::#self }; clone.tree = tree; clone } @@ -138,6 +142,7 @@ mod private { self.clone() } + } } @@ -146,6 +151,7 @@ mod private { fn parse( input : ParseStream< '_ > ) -> syn::Result< Self > { + #[ allow( clippy::wildcard_imports, clippy::enum_glob_use ) ] use syn::UseTree::*; let leading_colon = input.parse()?; let tree = input.parse()?; @@ -181,7 +187,7 @@ mod private group = true; break; }, - }; + } } Ok( Self @@ -213,6 +219,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use orphan::*; } @@ -221,6 +228,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use exposed::*; } @@ -229,6 +237,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use prelude::*; diff --git a/module/core/mod_interface_meta/src/visibility.rs b/module/core/mod_interface_meta/src/visibility.rs index acece0cb4f..770c0f5e04 100644 --- a/module/core/mod_interface_meta/src/visibility.rs +++ b/module/core/mod_interface_meta/src/visibility.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use macro_tools::prelude::*; @@ -10,9 +10,9 @@ mod private /// /// Custom keywords /// - pub mod kw { + #[ allow( clippy::wildcard_imports ) ] use super::*; // syn::custom_keyword!( private ); syn::custom_keyword!( own ); @@ -27,7 +27,6 @@ mod private /// /// Visibility constructor. /// - pub trait VisibilityInterface { type Token : syn::token::Token + syn::parse::Parse; @@ -40,7 +39,6 @@ mod private /// /// Trait answering question can the visibility be used for non-standard module. /// - pub trait ValidSubNamespace { fn valid_sub_namespace( &self ) -> bool { false } @@ -425,7 +423,7 @@ mod private Visibility::Orphan( e ) => e.restriction(), Visibility::Exposed( e ) => e.restriction(), Visibility::Prelude( e ) => e.restriction(), - Visibility::Public( _ ) => None, + Visibility::Public( _ ) | // Visibility::Restricted( e ) => e.restriction(), Visibility::Inherited => None, } @@ -485,12 +483,12 @@ mod private } } - #[ allow( clippy::derive_hash_xor_eq ) ] + #[ allow( clippy::derived_hash_with_manual_eq ) ] impl Hash for Visibility { fn hash< H : Hasher >( &self, state : &mut H ) { - self.kind().hash( state ) + self.kind().hash( state ); } } @@ -520,6 +518,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use orphan::*; } @@ -528,6 +527,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use exposed::*; } @@ -536,6 +536,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use prelude::*; diff --git a/module/core/mod_interface_meta/tests/smoke_test.rs b/module/core/mod_interface_meta/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/core/mod_interface_meta/tests/smoke_test.rs +++ b/module/core/mod_interface_meta/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/core/process_tools/Cargo.toml b/module/core/process_tools/Cargo.toml index 8b43896373..ffd2498fea 100644 --- a/module/core/process_tools/Cargo.toml +++ b/module/core/process_tools/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "process_tools" -version = "0.8.0" +version = "0.13.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -19,19 +19,17 @@ keywords = [ "fundamental", "general-purpose" ] [lints] workspace = true - [package.metadata.docs.rs] features = [ "full" ] all-features = false - [features] default = [ "enabled", "process_environment_is_cicd" ] full = [ "default" ] enabled = [ "mod_interface/enabled", "former/enabled", - "proper_path_tools/enabled", + "pth/enabled", "error_tools/enabled", "iter_tools/enabled", ] @@ -42,7 +40,7 @@ process_environment_is_cicd = [] [dependencies] mod_interface = { workspace = true } former = { workspace = true, features = [ "derive_former" ] } -proper_path_tools = { workspace = true } +pth = { workspace = true } error_tools = { workspace = true, features = [ "error_untyped" ] } iter_tools = { workspace = true } diff --git a/module/core/process_tools/License b/module/core/process_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/core/process_tools/License +++ b/module/core/process_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/core/process_tools/Readme.md index 97f7c673ea..86fb5c3d6d 100644 --- a/module/core/process_tools/Readme.md +++ b/module/core/process_tools/Readme.md @@ -1,6 +1,6 @@ -# Module :: process_tools +# Module :: `process_tools` [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_process_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_process_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/process_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/process_tools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) diff --git a/module/core/process_tools/src/environment.rs b/module/core/process_tools/src/environment.rs index 6ba4ba20fd..77d7ad10ad 100644 --- a/module/core/process_tools/src/environment.rs +++ b/module/core/process_tools/src/environment.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { @@ -6,7 +6,7 @@ mod private /// /// This function looks for environment variables that are commonly set by CI/CD systems to determine if it's running /// within such an environment. It supports detection for a variety of popular CI/CD platforms including GitHub Actions, - /// GitLab CI, Travis CI, CircleCI, and Jenkins. + /// GitLab CI, Travis CI, `CircleCI`, and Jenkins. /// /// # Returns /// - `true` if an environment variable indicating a CI/CD environment is found. @@ -27,12 +27,12 @@ mod private /// use process_tools::environment; /// assert_eq!( environment::is_cicd(), true ); /// ``` - #[ cfg( feature = "process_environment_is_cicd" ) ] + #[ must_use ] pub fn is_cicd() -> bool { use std::env; - let ci_vars = vec! + let ci_vars = [ "CI", // Common in many CI systems "GITHUB_ACTIONS", // GitHub Actions diff --git a/module/core/process_tools/src/lib.rs b/module/core/process_tools/src/lib.rs index ceb35389ea..2f91e2f714 100644 --- a/module/core/process_tools/src/lib.rs +++ b/module/core/process_tools/src/lib.rs @@ -7,6 +7,8 @@ #[ cfg( feature = "enabled" ) ] use mod_interface::mod_interface; +mod private {} + #[ cfg( feature = "enabled" ) ] mod_interface! { diff --git a/module/core/process_tools/src/process.rs b/module/core/process_tools/src/process.rs index 8636c628b5..d0637d805a 100644 --- a/module/core/process_tools/src/process.rs +++ b/module/core/process_tools/src/process.rs @@ -1,4 +1,5 @@ -/// Internal namespace. +/// Define a private namespace for all its items. +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { // use crate::*; @@ -73,17 +74,20 @@ mod private /// Executes an external process in a specified directory without using a shell. /// /// # Arguments: - /// - `bin_path`: Path to the executable bin_path. - /// - `args`: Command-line arguments for the bin_path. - /// - `current_path`: Directory current_path to run the bin_path in. + /// - `bin_path`: Path to the executable `bin_path`. + /// - `args`: Command-line arguments for the `bin_path`. + /// - `current_path`: Directory `current_path` to run the `bin_path` in. /// /// # Returns: /// A `Result` containing `Report` on success, detailing execution output, /// or an error message on failure. /// - /// # Errors: + /// # Errors /// Returns an error if the process fails to spawn, complete, or if output /// cannot be decoded as UTF-8. + /// + /// # Panics + /// qqq: doc // // qqq : for Petro : use typed error // qqq : for Petro : write example @@ -131,7 +135,7 @@ mod private .context( "failed to spawn process" ) .map_err( | e | { - report.error = Err( e.into() ); + report.error = Err( e ); Err::< (), () >( () ) }); @@ -141,16 +145,14 @@ mod private } let child = child.unwrap(); - let output = child + child .wait_with_output() .context( "failed to wait on child" ) .map_err( | e | { - report.error = Err( e.into() ); + report.error = Err( e ); Err::< (), () >( () ) - }); - - output + }) }; if report.error.is_err() @@ -163,7 +165,7 @@ mod private .context( "Found invalid UTF-8" ) .map_err( | e | { - report.error = Err( e.into() ); + report.error = Err( e ); Err::< (), () >( () ) }); @@ -179,7 +181,7 @@ mod private .context( "Found invalid UTF-8" ) .map_err( | e | { - report.error = Err( e.into() ); + report.error = Err( e ); Err::< (), () >( () ) }); @@ -290,10 +292,10 @@ mod private { Report { - command : Default::default(), + command : String::default(), current_path : PathBuf::new(), - out : Default::default(), - err : Default::default(), + out : String::default(), + err : String::default(), error : Ok( () ), } } diff --git a/module/core/process_tools/tests/smoke_test.rs b/module/core/process_tools/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/core/process_tools/tests/smoke_test.rs +++ b/module/core/process_tools/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/core/program_tools/Cargo.toml b/module/core/program_tools/Cargo.toml index 1ba2675334..a5e28c9202 100644 --- a/module/core/program_tools/Cargo.toml +++ b/module/core/program_tools/Cargo.toml @@ -36,7 +36,7 @@ full = [ enabled = [ "mod_interface/enabled", "former/enabled", - "proper_path_tools/enabled", + "pth/enabled", "error_tools/enabled", "iter_tools/enabled", ] @@ -44,7 +44,7 @@ enabled = [ [dependencies] mod_interface = { workspace = true } former = { workspace = true, features = [ "derive_former" ] } -proper_path_tools = { workspace = true } +pth = { workspace = true } error_tools = { workspace = true, features = [ "error_untyped" ] } # qqq : xxx : rid of error_untyped iter_tools = { workspace = true } diff --git a/module/core/program_tools/License b/module/core/program_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/core/program_tools/License +++ b/module/core/program_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/src/program.rs b/module/core/program_tools/src/program.rs index 70d66a7ead..90737df823 100644 --- a/module/core/program_tools/src/program.rs +++ b/module/core/program_tools/src/program.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { diff --git a/module/core/program_tools/tests/smoke_test.rs b/module/core/program_tools/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/core/program_tools/tests/smoke_test.rs +++ b/module/core/program_tools/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/core/proper_path_tools/Readme.md b/module/core/proper_path_tools/Readme.md deleted file mode 100644 index d142018019..0000000000 --- a/module/core/proper_path_tools/Readme.md +++ /dev/null @@ -1,35 +0,0 @@ - - -# Module :: proper_path_tools - - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_proper_path_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_proper_path_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/proper_path_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/proper_path_tools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) - - -Collection of algorithms and structures to handle paths properly. - -All functions in the crate don't touch file system, but only process paths. - - diff --git a/module/core/proper_path_tools/src/path/current_path.rs b/module/core/proper_path_tools/src/path/current_path.rs deleted file mode 100644 index f9e5a83293..0000000000 --- a/module/core/proper_path_tools/src/path/current_path.rs +++ /dev/null @@ -1,62 +0,0 @@ -/// Internal namespace. -mod private -{ - - use crate::*; - use std::env; - - /// Symbolize current path. - #[ derive( Clone, Copy, Debug, Default, PartialEq, Eq ) ] - pub struct CurrentPath; - - #[ cfg( feature = "path_utf8" ) ] - impl TryFrom< CurrentPath > for Utf8PathBuf - { - type Error = std::io::Error; - - #[ inline ] - fn try_from( src : CurrentPath ) -> Result< Self, Self::Error > - { - Utf8PathBuf::try_from( PathBuf::try_from( src )? ) - .map_err - ( - | err | - { - std::io::Error::new - ( - std::io::ErrorKind::NotFound, - format!( "Cant convert to utf8 {}", err ), - ) - } - ) - } - } - - impl TryFrom< CurrentPath > for PathBuf - { - type Error = std::io::Error; - - #[ inline ] - fn try_from( _ : CurrentPath ) -> Result< Self, Self::Error > - { - env::current_dir() - } - } - - impl TryFrom< CurrentPath > for AbsolutePath - { - type Error = std::io::Error; - - #[ inline ] - fn try_from( src : CurrentPath ) -> Result< Self, Self::Error > - { - AbsolutePath::try_from( PathBuf::try_from( src )? ) - } - } - -} - -crate::mod_interface! -{ - exposed use CurrentPath; -} diff --git a/module/core/proper_path_tools/Cargo.toml b/module/core/pth/Cargo.toml similarity index 84% rename from module/core/proper_path_tools/Cargo.toml rename to module/core/pth/Cargo.toml index a9de7e5f46..fce38c2429 100644 --- a/module/core/proper_path_tools/Cargo.toml +++ b/module/core/pth/Cargo.toml @@ -1,15 +1,15 @@ [package] -name = "proper_path_tools" -version = "0.9.0" +name = "pth" +version = "0.23.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", ] license = "MIT" readme = "Readme.md" -documentation = "https://docs.rs/proper_path_tools" -repository = "https://github.com/Wandalen/wTools/tree/master/module/core/proper_path_tools" -homepage = "https://github.com/Wandalen/wTools/tree/master/module/core/proper_path_tools" +documentation = "https://docs.rs/pth" +repository = "https://github.com/Wandalen/wTools/tree/master/module/core/pth" +homepage = "https://github.com/Wandalen/wTools/tree/master/module/core/pth" description = """ Collection of algorithms and structures to handle paths properly. """ @@ -35,6 +35,8 @@ full = [ "path_utf8", ] no_std = [] +# qqq : xxx : negate no_std +# use_std = [] use_alloc = [ "no_std" ] enabled = [ "mod_interface/enabled" ] @@ -43,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 new file mode 100644 index 0000000000..72c80c1308 --- /dev/null +++ b/module/core/pth/License @@ -0,0 +1,22 @@ +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 +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/core/pth/Readme.md b/module/core/pth/Readme.md new file mode 100644 index 0000000000..9479a502d6 --- /dev/null +++ b/module/core/pth/Readme.md @@ -0,0 +1,51 @@ + + +# Module :: pth + + [![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_pth_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_pth_push.yml) [![docs.rs](https://img.shields.io/docsrs/pth?color=e3e8f0&logo=docs.rs)](https://docs.rs/pth) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + + +Collection of algorithms and structures to handle paths properly. + +All functions in the crate don't touch file system, but only process paths. + +### Type `AbsolutePath` + +The `AbsolutePath` type ensures that paths are absolute, which helps reduce issues and maintenance costs associated with relative paths. Relative paths can be problematic as they introduce additional variables and complexities, making code analysis, integration, refactoring, and testing more difficult. By using absolute paths, software architecture can be improved, similar to how avoiding global variables can enhance code quality. It is recommended to use relative paths only at the outskirts of an application. + +### Trait `AsPath` + +This trait is used to avoid redundant allocation of memory by providing a reference to a Path. It is implemented only for types that can either be referenced or are references to Path itself. Unlike `TryIntoPath`, it does not allocate memory on the heap. However, `TryIntoPath` is implemented for a wider range of types because it is not restricted from allocating memory. Unlike `AsRef< Path >`, `AsPath` is implemented for a wider number of types, including those that are not directly convertible to a Path using `AsRef`. This is because `AsPath` is designed to provide a more flexible interface for path-like types, accommodating various representations that can logically be treated as paths. + +### Trait `TryIntoPath` + +This trait is used to convert any path-like type into an owned `PathBuf`. Unlike `TryIntoCowPath`, it always returns an owned `PathBuf`, so there is no need to differentiate between borrowed and owned paths at runtime. Unlike `AsPath`, it is implemented for a wider range of path-like types, similar to `TryIntoCowPath`. + +### Trait `TryIntoCowPath` + +This trait is designed to avoid redundant memory allocation. Unlike `TryIntoPath`, it does not allocate memory on the heap if it’s not necessary. Unlike `AsPath`, it is implemented for a wider number of path-like types, similar to `TryIntoPath`. The drawback is the necessity to differentiate borrowed and owned paths at runtime. + + diff --git a/module/core/pth/src/as_path.rs b/module/core/pth/src/as_path.rs new file mode 100644 index 0000000000..d5d1ae37f6 --- /dev/null +++ b/module/core/pth/src/as_path.rs @@ -0,0 +1,71 @@ +/// Define a private namespace for all its items. +mod private +{ + #[ allow( unused_imports, clippy::wildcard_imports ) ] + use crate::*; + #[ cfg( feature = "no_std" ) ] + extern crate std; + + use std::path::Path; + + /// A trait for converting various types into a reference to a `Path`. + /// + /// This trait is used to avoid redundant allocation of memory by providing a reference to a `Path`. + /// It is implemented only for types that can either be referenced or are references to `Path` itself. + /// Unlike `TryIntoPath`, it does not allocate memory on the heap. However, `TryIntoPath` is implemented for a wider range of types because it is not restricted from allocating memory. + /// Unlike `AsRef`, `AsPath` is implemented for a wider number of types, including those that are not directly convertible to a `Path` using `AsRef`. + /// This is because `AsPath` is designed to provide a more flexible interface for path-like types, accommodating various representations that can logically be treated as paths. + pub trait AsPath + { + /// Converts the implementing type into a reference to a `Path`. + /// + /// # Returns + /// + /// A reference to a `Path`. + fn as_path( &self ) -> &Path; + } + + /// Implementation of `AsPath` for `str`. + impl AsPath for str + { + fn as_path( &self ) -> &Path + { + Path::new( self ) + } + } + + /// Implementation of `AsPath` for `Path`. + impl AsPath for Path + { + fn as_path( &self ) -> &Path + { + self + } + } + + /// Implementation of `AsPath` for `Utf8Path`. + #[cfg( feature = "path_utf8" )] + impl AsPath for Utf8Path + { + fn as_path( &self ) -> &Path + { + self.as_std_path() + } + } + + /// Blanket implementation of `AsPath` for all types that implement `AsRef`. + impl< T > AsPath for T + where + T : AsRef< Path >, + { + fn as_path( &self ) -> &Path + { + self.as_ref() + } + } +} + +crate::mod_interface! +{ + orphan use AsPath; +} \ No newline at end of file diff --git a/module/core/pth/src/lib.rs b/module/core/pth/src/lib.rs new file mode 100644 index 0000000000..b86755dacb --- /dev/null +++ b/module/core/pth/src/lib.rs @@ -0,0 +1,65 @@ +// module/core/pth/src/lib.rs +#![ cfg_attr( feature = "no_std", no_std ) ] +#![ doc( html_logo_url = "https://raw.githubusercontent.com/Wandalen/wTools/master/asset/img/logo_v3_trans_square.png" ) ] +#![ doc( html_favicon_url = "https://raw.githubusercontent.com/Wandalen/wTools/alpha/asset/img/logo_v3_trans_square_icon_small_v2.ico" ) ] +#![ doc( html_root_url = "https://docs.rs/pth/latest/pth/" ) ] +#![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] +#![ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] + +#[ cfg( feature = "enabled" ) ] +use ::mod_interface::mod_interface; + +#[ cfg( feature="no_std" ) ] +#[ macro_use ] +extern crate alloc; + +// qqq : xxx : implement `pth::absolute::join` function or add option to `pth::path::join` +// Desired Signature Idea 1: `pub fn join(p1: T1, p2: T2) -> io::Result` (extendable for more args or tuples) +// Desired Signature Idea 2: `pub fn join(paths: Paths, options: JoinOptions) -> io::Result` where JoinOptions includes absolute handling. +// Behavior: +// 1. Takes multiple path-like items (e.g., via tuple, slice, or multiple args). +// 2. Finds the rightmost item that represents an absolute path. +// 3. If an absolute path is found, it joins all path segments *from that absolute path onwards*. +// 4. If *no* absolute path is found, it joins *all* segments relative to the current working directory (implicitly using `CurrentPath` if needed). +// 5. The final joined path must be canonicalized and returned as an `AbsolutePath`. +// 6. Return an `io::Error` if input is invalid or joining/canonicalization fails. +// Examples (assuming CurrentPath resolves relative paths): +// - `pth::absolute::join("/abs/a", "rel/b")` -> `Ok(AbsolutePath::from("/abs/a/rel/b"))` +// - `pth::absolute::join("rel/a", "/abs/b", "rel/c")` -> `Ok(AbsolutePath::from("/abs/b/rel/c"))` +// - `pth::absolute::join("rel/a", "/abs/b", "/abs/c", "rel/d")` -> `Ok(AbsolutePath::from("/abs/c/rel/d"))` +// - `pth::absolute::join("rel/a", "rel/b")` -> `Ok(AbsolutePath::from(current_dir.join("rel/a/rel/b")))` +// - `pth::absolute::join("/abs/a/..", "b")` -> `Ok(AbsolutePath::from("/b"))` + +/// Own namespace of the module. Contains items public within this layer, but not propagated. +mod private {} + +#[ cfg( feature = "enabled" ) ] +mod_interface! +{ + + /// Basic functionality. + layer path; + + /// AsPath trait. + layer as_path; + /// TryIntoPath trait. + layer try_into_path; + /// TryIntoPath trait. + layer try_into_cow_path; + + /// Transitive TryFrom and TryInto. + layer transitive; + + #[ cfg( feature = "path_utf8" ) ] + own use ::camino::{ Utf8Path, Utf8PathBuf }; + + // #[ cfg( not( feature = "no_std" ) ) ] + // own use ::std::path::{ PathBuf, Path, Component }; + + #[ cfg( not( feature = "no_std" ) ) ] + own use ::std::path::*; + + #[ cfg( not( feature = "no_std" ) ) ] + own use ::std::borrow::Cow; + +} \ No newline at end of file diff --git a/module/core/proper_path_tools/src/path.rs b/module/core/pth/src/path.rs similarity index 89% rename from module/core/proper_path_tools/src/path.rs rename to module/core/pth/src/path.rs index b11df6f466..d5ef26b032 100644 --- a/module/core/proper_path_tools/src/path.rs +++ b/module/core/pth/src/path.rs @@ -1,7 +1,10 @@ -/// Internal namespace. - +/// Define a private namespace for all its items. +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] + use crate::*; + #[ cfg( feature = "no_std" ) ] extern crate std; @@ -14,8 +17,8 @@ mod private /// # Returns: /// /// - `bool` : Returns `true` if the path contains unescaped glob pattern characters ( `*`, `?`, `[`, `{` ), - /// otherwise `false`. The function takes into account escape sequences, and only considers glob characters - /// outside of escape sequences. + /// otherwise `false`. The function takes into account escape sequences, and only considers glob characters + /// outside of escape sequences. /// /// # Behavior: /// @@ -25,7 +28,7 @@ mod private /// # Examples: /// /// ``` - /// use proper_path_tools::path; + /// use pth::path; /// /// assert_eq!( path::is_glob( "file.txt" ), false ); // No glob patterns /// assert_eq!( path::is_glob( "*.txt" ), true ); // Contains unescaped glob character * @@ -33,8 +36,8 @@ mod private /// assert_eq!( path::is_glob( "file[0-9].txt" ), true ); // Unescaped brackets indicate a glob pattern /// assert_eq!( path::is_glob( "file\\[0-9].txt" ), false ); // Escaped brackets, not a glob pattern /// ``` - // qqq : xxx : should probably be Path + #[ must_use ] pub fn is_glob( path : &str ) -> bool { let mut chars = path.chars().peekable(); @@ -42,6 +45,7 @@ mod private let mut in_brackets = false; let mut in_braces = false; + #[ allow( clippy::while_let_on_iterator ) ] while let Some( c ) = chars.next() { if is_escaped @@ -96,9 +100,9 @@ mod private /// Normalizes a given filesystem path by syntactically removing occurrences of `.` and properly handling `..` components. /// /// This function iterates over the components of the input path and applies the following rules: - /// - For `..` (ParentDir) components, it removes the last normal (non-special) segment from the normalized path. If the last segment is another `..` or if there are no preceding normal segments and the path does not start with the root directory (`/`), it preserves the `..` to represent moving up in the directory hierarchy. + /// - For `..` (`ParentDir`) components, it removes the last normal (non-special) segment from the normalized path. If the last segment is another `..` or if there are no preceding normal segments and the path does not start with the root directory (`/`), it preserves the `..` to represent moving up in the directory hierarchy. /// - For paths starting with the root directory followed by `..`, it retains these `..` components to accurately reflect paths that navigate upwards from the root. - /// - Skips `.` (CurDir) components as they represent the current directory and don't affect the path's normalization. + /// - Skips `.` (`CurDir`) components as they represent the current directory and don't affect the path's normalization. /// - Retains all other components unchanged, including normal segments and the root directory. /// /// The normalization process is purely syntactical and does not interact with the file system. @@ -110,7 +114,7 @@ mod private /// /// ``` /// use std::path::{ Path, PathBuf }; - /// use proper_path_tools::path as path; + /// use pth::path as path; /// /// let path = Path::new( "/a/b/./c/../d" ); /// let normalized_path = path::normalize( path ); @@ -126,7 +130,6 @@ mod private /// /// A `PathBuf` containing the normalized path. /// - pub fn normalize< P : AsRef< std::path::Path > >( path : P ) -> std::path::PathBuf { use std::path::{ Component, PathBuf }; @@ -161,11 +164,7 @@ mod private { components.pop(); } - Some( Component::RootDir ) => - { - components.push( Component::ParentDir ); - } - Some( Component::ParentDir ) | None => + Some( Component::RootDir | Component::ParentDir ) | None => { components.push( Component::ParentDir ); } @@ -183,14 +182,15 @@ mod private normalized.push( "." ); } - for component in components.iter() + for component in &components { normalized.push( component.as_os_str() ); } // Convert back to a PathBuf using "/" as the separator for consistency #[ cfg( target_os = "windows" ) ] - let normalized = PathBuf::from( normalized.to_string_lossy().replace( "\\", "/" ) ); + let normalized = PathBuf::from( normalized.to_string_lossy().replace( '\\', "/" ) ); + // fix clippy normalized } @@ -199,6 +199,8 @@ mod private // qqq : for Petro : for Bohdan : why that transofrmation is necessary. give several examples of input and output /// Returns the canonical, absolute form of the path with all intermediate components normalized and symbolic links resolved. /// This function does not touch fs. + /// # Errors + /// qqq: doc pub fn canonicalize( path : impl AsRef< std::path::Path > ) -> std::io::Result< std::path::PathBuf > { #[ cfg( target_os = "windows" ) ] @@ -220,7 +222,7 @@ mod private #[ cfg( target_os = "windows" ) ] let path = { - const VERBATIM_PREFIX : &str = r#"\\?\"#; + const VERBATIM_PREFIX : &str = r"\\?\"; // is necessary because of the normalization step that replaces the backslash with a slash. const VERBATIM_PREFIX_MIRRORS_EDGE : &str = "//?/"; let p = path.display().to_string(); @@ -230,7 +232,8 @@ mod private } else { - path.into() + // fix clippy + path } }; @@ -260,11 +263,12 @@ mod private /// # Examples /// /// ``` - /// use proper_path_tools::path::unique_folder_name; + /// use pth::path::unique_folder_name; /// let folder_name = unique_folder_name().unwrap(); /// println!( "Generated folder name: {}", folder_name ); /// ``` - + /// # Errors + /// qqq: doc #[ cfg( feature = "path_unique_folder_name" ) ] pub fn unique_folder_name() -> std::result::Result< std::string::String, std::time::SystemTimeError > { @@ -277,7 +281,9 @@ mod private // Thread-local static variable for a counter std::thread_local! { - static COUNTER : std::cell::Cell< usize > = std::cell::Cell::new( 0 ); + // fix clippy + #[ allow( clippy::missing_const_for_thread_local ) ] + static COUNTER : core::cell::Cell< usize > = core::cell::Cell::new( 0 ); } // Increment and get the current value of the counter safely @@ -293,38 +299,41 @@ mod private let pid = std::process::id(); let tid : String = std::format!( "{:?}", std::thread::current().id() ) .chars() - .filter( | c | c.is_digit( 10 ) ) + .filter( char::is_ascii_digit ) .collect(); // dbg!( &tid ); - Ok( std::format!( "{}_{}_{}_{}", timestamp, pid, tid, count ) ) + Ok( std::format!( "{timestamp}_{pid}_{tid}_{count}" ) ) } + /// Joins a list of file system paths into a single absolute path. /// /// This function takes a list of file system paths and joins them into a single path, - /// normalizing and simplifying them as it goes. The result is returned as a PathBuf. + /// normalizing and simplifying them as it goes. The result is returned as a `PathBuf`. /// /// Examples: /// /// ``` /// use std::path::PathBuf; - /// use proper_path_tools::path; + /// use pth::path; /// /// let paths = vec![ PathBuf::from( "a/b/c" ), PathBuf::from( "/d/e" ), PathBuf::from( "f/g" ) ]; - /// let joined = path::join_paths( paths.iter().map( | p | p.as_path() ) ); + /// let joined = path::iter_join( paths.iter().map( | p | p.as_path() ) ); /// assert_eq!( joined, std::path::PathBuf::from( "/d/e/f/g" ) ); /// /// let paths = vec![ PathBuf::from( "" ), PathBuf::from( "a/b" ), PathBuf::from( "" ), PathBuf::from( "c" ), PathBuf::from( "" ) ]; - /// let joined = path::join_paths( paths.iter().map( | p | p.as_path() ) ); + /// let joined = path::iter_join( paths.iter().map( | p | p.as_path() ) ); /// assert_eq!( joined, std::path::PathBuf::from( PathBuf::from( "/a/b/c" ) ) ); /// /// ``` + /// + /// # Panics + /// qqq: doc // qqq : make macro paths_join!( ... ) - pub fn join_paths< 'a, I >( paths : I ) -> std::path::PathBuf + pub fn iter_join< 'a ,I, P >( paths : I ) -> PathBuf where - // AsPath : AsRef< std::path::Path >, - // I : Iterator< Item = AsPath >, - I : Iterator< Item = &'a std::path::Path >, + I : Iterator< Item = P >, + P : TryIntoCowPath< 'a >, { #[ cfg( feature = "no_std" ) ] extern crate alloc; @@ -337,8 +346,12 @@ mod private for path in paths { - let mut path = path.to_string_lossy().replace( '\\', "/" ); - path = path.replace( ':', "" ); + // let mut path = path.to_string_lossy().replace( '\\', "/" ); + // qqq : xxx : avoid unwrap + let path = path.try_into_cow_path().unwrap().to_string_lossy().replace( '\\', "/" ); + // qqq : xxx : avoid converting to String, keep it Path + + // path = path.replace( ':', "" ); // qqq : this is a bug let mut added_slah = false; @@ -377,6 +390,7 @@ mod private } ".." => { + #[ allow( clippy::if_not_else ) ] if result != "/" { if added_slah @@ -398,7 +412,8 @@ mod private { result.push( '/' ); } - } else + } + else { result.push_str( &components[ idx.. ].to_vec().join( "/" ) ); break; @@ -411,7 +426,8 @@ mod private if result.ends_with( '/' ) { result.push_str( component ); - } else + } + else { result.push( '/' ); result.push_str( component ); @@ -449,7 +465,7 @@ mod private /// # Examples /// /// ``` - /// use proper_path_tools::path::exts; + /// use pth::path::exts; /// /// let path = "/path/to/file.tar.gz"; /// let extensions = exts( path ); @@ -457,7 +473,7 @@ mod private /// ``` /// /// ``` - /// use proper_path_tools::path::exts; + /// use pth::path::exts; /// /// let empty_path = ""; /// let extensions = exts( empty_path ); @@ -465,7 +481,6 @@ mod private /// assert_eq!( extensions, expected ); /// ``` /// - // qqq : xxx : should return iterator pub fn exts( path : impl AsRef< std::path::Path > ) -> std::vec::Vec< std::string::String > { @@ -488,7 +503,7 @@ mod private let extensions = &file_name_str[ dot_index + 1.. ]; - return extensions.split( '.' ).map( | s | s.to_string() ).collect() + return extensions.split( '.' ).map( std::string::ToString::to_string ).collect() } } } @@ -512,7 +527,7 @@ mod private /// /// ``` /// use std::path::PathBuf; - /// use proper_path_tools::path::without_ext; + /// use pth::path::without_ext; /// /// let path = "/path/to/file.txt"; /// let modified_path = without_ext(path); @@ -521,13 +536,14 @@ mod private /// /// ``` /// use std::path::PathBuf; - /// use proper_path_tools::path::without_ext; + /// use pth::path::without_ext; /// /// let empty_path = ""; /// let modified_path = without_ext(empty_path); /// assert_eq!(modified_path, None); /// ``` /// + #[ allow( clippy::manual_let_else ) ] pub fn without_ext( path : impl AsRef< std::path::Path > ) -> core::option::Option< std::path::PathBuf > { use std::path::{ Path, PathBuf }; @@ -543,11 +559,8 @@ mod private let path_buf = Path::new( path.as_ref() ); - let parent = match path_buf.parent() - { - Some( parent ) => parent, - None => return None, - }; + // fix clippy + let parent = path_buf.parent()?; let file_stem = match path_buf.file_stem() { Some( name ) => @@ -569,7 +582,7 @@ mod private let mut full_path = parent.to_path_buf(); full_path.push( file_stem ); - Some( PathBuf::from( full_path.to_string_lossy().replace( "\\", "/" ) ) ) + Some( PathBuf::from( full_path.to_string_lossy().replace( '\\', "/" ) ) ) } /// Replaces the existing path extension with the provided extension. @@ -591,7 +604,7 @@ mod private /// /// ``` /// use std::path::PathBuf; - /// use proper_path_tools::path::change_ext; + /// use pth::path::change_ext; /// /// let path = "/path/to/file.txt"; /// let modified_path = change_ext( path, "json" ); @@ -600,7 +613,7 @@ mod private /// /// ``` /// use std::path::PathBuf; - /// use proper_path_tools::path::change_ext; + /// use pth::path::change_ext; /// /// let empty_path = ""; /// let modified_path = change_ext( empty_path, "txt" ); @@ -619,7 +632,8 @@ mod private if ext.is_empty() { Some( without_ext ) - } else + } + else { Some( PathBuf::from( format!( "{}.{}", without_ext.to_string_lossy(), ext ) ) ) } @@ -637,19 +651,18 @@ mod private /// # Returns /// /// * `Option` - The common directory path shared by all paths, if it exists. - /// If no common directory path exists, returns `None`. + /// If no common directory path exists, returns `None`. /// /// # Examples /// /// ``` - /// use proper_path_tools::path::path_common; + /// use pth::path::path_common; /// /// let paths = vec![ "/a/b/c", "/a/b/d", "/a/b/e" ]; /// let common_path = path_common( paths.into_iter() ); /// assert_eq!( common_path, Some( "/a/b/".to_string() ) ); /// ``` /// - // xxx : qqq : should probably be PathBuf? pub fn path_common< 'a, I >( paths : I ) -> Option< std::string::String > where @@ -661,7 +674,7 @@ mod private #[ cfg( feature = "no_std" ) ] use alloc::{ string::{ String, ToString }, vec::Vec }; - let orig_paths : Vec< String > = paths.map( | path | path.to_string() ).collect(); + let orig_paths : Vec< String > = paths.map( std::string::ToString::to_string ).collect(); if orig_paths.is_empty() { @@ -673,7 +686,7 @@ mod private let mut paths = orig_paths.clone(); // Iterate over paths to count directory frequencies - for path in paths.iter_mut() + for path in &mut paths { path_remove_dots( path ); path_remove_double_dots( path ); @@ -685,7 +698,7 @@ mod private { // Construct directory path - let mut dir_path = dirs[ 0..i + 1 ].join( "/" ); + let mut dir_path = dirs[ 0..=i ].join( "/" ); // Increment frequency count @@ -704,7 +717,7 @@ mod private .into_iter() .filter( | ( _, freq ) | *freq == paths.len() ) .map( | ( dir, _ ) | dir ) - .max_by_key( | dir | dir.len() ) + .max_by_key( std::string::String::len ) .unwrap_or_default(); let mut result = common_dir.to_string(); @@ -740,7 +753,6 @@ mod private /// /// * `path` - A mutable reference to a string representing the path to be cleaned. /// - // xxx : qqq : should probably be Path? fn path_remove_dots( path : &mut std::string::String ) { @@ -765,7 +777,6 @@ mod private /// /// * `path` - A mutable reference to a string representing the path to be cleaned. /// - // xxx : qqq : should probably be Path? fn path_remove_double_dots( path : &mut std::string::String ) { @@ -834,7 +845,7 @@ mod private /// /// let file_path = "/home/user/documents/file.txt"; /// let new_path = "/mnt/storage"; - /// let rebased_path = proper_path_tools::path::rebase( file_path, new_path, None ).unwrap(); + /// let rebased_path = pth::path::rebase( file_path, new_path, None ).unwrap(); /// assert_eq!( rebased_path, PathBuf::from( "/mnt/storage/home/user/documents/file.txt" ) ); /// ``` /// @@ -846,10 +857,11 @@ mod private /// let file_path = "/home/user/documents/file.txt"; /// let new_path = "/mnt/storage"; /// let old_path = "/home/user"; - /// let rebased_path = proper_path_tools::path::rebase( file_path, new_path, Some( old_path ) ).unwrap(); + /// let rebased_path = pth::path::rebase( file_path, new_path, Some( old_path ) ).unwrap(); /// assert_eq!( rebased_path, PathBuf::from( "/mnt/storage/documents/file.txt" ) ); /// ``` - /// + /// # Panics + /// qqq: doc pub fn rebase< T : AsRef< std::path::Path > > ( file_path : T, @@ -900,7 +912,7 @@ mod private /// /// let from = "/a/b"; /// let to = "/a/c/d"; - /// let relative_path = proper_path_tools::path::path_relative( from, to ); + /// let relative_path = pth::path::path_relative( from, to ); /// assert_eq!( relative_path, PathBuf::from( "../c/d" ) ); /// ``` pub fn path_relative< T : AsRef< std::path::Path > >( from : T, to : T ) -> std::path::PathBuf @@ -1008,7 +1020,7 @@ mod private /// # Examples /// /// ``` - /// use proper_path_tools::path::ext; + /// use pth::path::ext; /// /// let path = "/path/to/file.txt"; /// let extension = ext( path ); @@ -1016,7 +1028,7 @@ mod private /// ``` /// /// ``` - /// use proper_path_tools::path::ext; + /// use pth::path::ext; /// /// let empty_path = ""; /// let extension = ext( empty_path ); @@ -1047,17 +1059,20 @@ mod private crate::mod_interface! { - orphan use ext; - orphan use exts; - orphan use change_ext; - orphan use path_relative; - orphan use rebase; - orphan use path_common; - orphan use join_paths; - orphan use without_ext; - orphan use is_glob; - orphan use normalize; - orphan use canonicalize; + orphan use + { + ext, + exts, + change_ext, + path_relative, + rebase, + path_common, + iter_join, + without_ext, + is_glob, + normalize, + canonicalize, + }; #[ cfg( feature = "path_unique_folder_name" ) ] orphan use unique_folder_name; @@ -1071,4 +1086,7 @@ crate::mod_interface! /// Describe native path. Use to pass path to the platfrom. layer native_path; + /// Convenient joining. + layer joining; + } diff --git a/module/core/proper_path_tools/src/path/absolute_path.rs b/module/core/pth/src/path/absolute_path.rs similarity index 50% rename from module/core/proper_path_tools/src/path/absolute_path.rs rename to module/core/pth/src/path/absolute_path.rs index 60a3587a81..dd0af7a665 100644 --- a/module/core/proper_path_tools/src/path/absolute_path.rs +++ b/module/core/pth/src/path/absolute_path.rs @@ -1,53 +1,54 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { - + #[ allow( clippy::wildcard_imports ) ] use crate::*; - use std:: { - // borrow::Cow, path::{ Path, PathBuf }, io, }; - use core:: { fmt, - ops:: - { - Deref, - DerefMut, - }, + ops::{ Deref, DerefMut }, }; - - #[cfg(feature="no_std")] + #[ cfg( feature = "no_std" ) ] extern crate std; - + #[ cfg( feature = "no_std" ) ] + use alloc::string::String; #[ cfg( feature = "derive_serde" ) ] use serde::{ Serialize, Deserialize }; - #[ cfg( feature = "path_utf8" ) ] - use camino::{ Utf8Path, Utf8PathBuf }; + // #[ cfg( feature = "path_utf8" ) ] + // use camino::{ Utf8Path, Utf8PathBuf }; - /// Absolute path. + /// A new type representing an absolute path. + /// + /// The `AbsolutePath` type ensures that paths are absolute, which helps reduce issues and maintenance costs associated with relative paths. + /// Relative paths can be problematic as they introduce additional variables and complexities, making code analysis, integration, refactoring, and testing more difficult. + /// By using absolute paths, software architecture can be improved, similar to how avoiding global variables can enhance code quality. + /// It is recommended to use relative paths only at the outskirts of an application. #[ cfg_attr( feature = "derive_serde", derive( Serialize, Deserialize ) ) ] #[ derive( Debug, Default, Clone, Ord, PartialOrd, Eq, PartialEq, Hash ) ] pub struct AbsolutePath( PathBuf ); impl AbsolutePath { - - /// Returns the Path without its final component, if there is one. - /// Returns None if the path terminates in a root or prefix, or if it's the empty string. + /// Returns the parent directory as an `AbsolutePath`, if it exists. + /// + /// Returns `None` if the path terminates in a root or prefix, or if it's the empty string. #[ inline ] pub fn parent( &self ) -> Option< AbsolutePath > { self.0.parent().map( PathBuf::from ).map( AbsolutePath ) } - /// Creates an owned `AbsolutePath` with path adjoined to self. + /// Creates an owned `AbsolutePath` by joining a given path to `self`. + /// # Panics + /// qqq: doc #[ inline ] + #[ must_use ] pub fn join< P >( &self, path : P ) -> AbsolutePath where P : AsRef< Path >, @@ -55,13 +56,7 @@ mod private Self::try_from( self.0.join( path ) ).unwrap() } - // /// Converts a `AbsolutePath` to a `Cow` - // pub fn to_string_lossy( &self ) -> Cow< '_, str > - // { - // self.0.to_string_lossy() - // } - - /// Determines whether base is a prefix of self. + /// Checks if the path starts with a given base path. /// /// Only considers whole path components to match. #[ inline ] @@ -70,13 +65,58 @@ mod private self.0.starts_with( base ) } - /// Returns inner type which is PathBuf. - #[ inline( always ) ] + /// Returns the inner `PathBuf`. + #[inline(always)] + #[ must_use ] pub fn inner( self ) -> PathBuf { self.0 } + /// Creates an `AbsolutePath` from an iterator over items that implement `TryIntoCowPath`. + /// + /// This function joins all path segments into a single path and attempts to convert it + /// into an `AbsolutePath`. The resulting path must be absolute. + /// + /// # Arguments + /// + /// * `iter` - An iterator over path segments. + /// + /// # Returns + /// + /// * `Ok(AbsolutePath)` if the joined path is absolute. + /// * `Err(io::Error)` if the joined path is not absolute. + /// # Errors + /// qqq: doc + #[ allow( clippy::should_implement_trait ) ] + pub fn from_iter< 'a, I, P >( iter : I ) -> Result< Self, io::Error > + where + I : Iterator< Item = P >, + P : TryIntoCowPath< 'a >, + { + let joined_path = iter_join( iter ); + AbsolutePath::try_from( joined_path ) + } + + /// Joins path components into a `PathBuf`. + /// + /// This function leverages the `PathJoined` trait to join multiple path components into a single `PathBuf`. + /// + /// # Arguments + /// + /// * `paths` - A tuple of path components implementing the `PathJoined` trait. + /// + /// # Returns + /// + /// * `Ok(PathBuf)` - The joined path as a `PathBuf`. + /// * `Err(io::Error)` - An error if any component fails to convert. + /// # Errors + /// qqq: doc + pub fn from_paths< Paths : PathJoined >( paths : Paths ) -> Result< Self, io::Error > + { + Self::try_from( paths.iter_join()? ) + } + } impl fmt::Display for AbsolutePath @@ -91,8 +131,6 @@ mod private #[ inline ] fn is_absolute( path : &Path ) -> bool { - // None - not absolute - // with `.` or `..` at the first component - not absolute !path.components().next().is_some_and( | c | c.as_os_str() == "." || c.as_os_str() == ".." ) } @@ -103,7 +141,7 @@ mod private #[ inline ] fn try_from( src : PathBuf ) -> Result< Self, Self::Error > { - < Self as TryFrom< &Path > >::try_from( &src.as_path() ) + < Self as TryFrom< &Path > >::try_from( src.as_path() ) } } @@ -114,11 +152,10 @@ mod private #[ inline ] fn try_from( src : &PathBuf ) -> Result< Self, Self::Error > { - < Self as TryFrom< &Path > >::try_from( &src.as_path() ) + < Self as TryFrom< &Path > >::try_from( src.as_path() ) } } - // xxx : qqq : use Into< Path > impl TryFrom< &Path > for AbsolutePath { type Error = std::io::Error; @@ -126,13 +163,11 @@ mod private #[ inline ] fn try_from( src : &Path ) -> Result< Self, Self::Error > { - // < Self as TryFrom< &str > >::try_from( src.to_string_lossy() ) let path = path::canonicalize( src )?; - // xxx if !is_absolute( &path ) { - return Err( io::Error::new( io::ErrorKind::InvalidData, "Path expected to be absolute, but it's not {path}" ) ) + return Err( io::Error::new( io::ErrorKind::Other, format!( "Path expected to be absolute, but it's not {path:?}" ) ) ); } Ok( Self( path ) ) @@ -150,19 +185,30 @@ mod private } } -// impl TryFrom< &str > for AbsolutePath -// { -// type Error = std::io::Error; -// // type Error = PathError; -// -// #[ inline( always ) ] -// fn try_from( src : &str ) -> Result< Self, Self::Error > -// { -// Self::try_from( AbsolutePath::try_from( src )? ) -// } -// } - - #[ cfg( feature = "path_utf8" ) ] + impl< 'a > TryFrom< &'a String > for AbsolutePath + { + type Error = std::io::Error; + + #[ inline ] + fn try_from( src : &'a String ) -> Result< Self, Self::Error > + { + < Self as TryFrom< &Path > >::try_from( src.as_ref() ) + } + } + + #[ allow( clippy::extra_unused_lifetimes ) ] + impl< 'a > TryFrom< String > for AbsolutePath + { + type Error = std::io::Error; + + #[ inline ] + fn try_from( src : String ) -> Result< Self, Self::Error > + { + < Self as TryFrom< &Path > >::try_from( src.as_ref() ) + } + } + + #[cfg( feature = "path_utf8" )] impl TryFrom< Utf8PathBuf > for AbsolutePath { type Error = std::io::Error; @@ -174,7 +220,7 @@ mod private } } - #[ cfg( feature = "path_utf8" ) ] + #[cfg( feature = "path_utf8" )] impl TryFrom< &Utf8PathBuf > for AbsolutePath { type Error = std::io::Error; @@ -186,7 +232,7 @@ mod private } } - #[ cfg( feature = "path_utf8" ) ] + #[cfg( feature = "path_utf8" )] impl TryFrom< &Utf8Path > for AbsolutePath { type Error = std::io::Error; @@ -210,21 +256,18 @@ mod private impl< 'a > TryFrom< &'a AbsolutePath > for &'a str { type Error = std::io::Error; + #[ inline ] fn try_from( src : &'a AbsolutePath ) -> Result< &'a str, Self::Error > { - src - .to_str() - .ok_or_else - ( - move || io::Error::new( io::ErrorKind::Other, format!( "Can't convert &PathBuf into &str {src}" ) ) - ) + src.to_str().ok_or_else( || io::Error::new( io::ErrorKind::Other, format!( "Can't convert &PathBuf into &str {src}" ) ) ) } } impl TryFrom< &AbsolutePath > for String { type Error = std::io::Error; + #[ inline ] fn try_from( src : &AbsolutePath ) -> Result< String, Self::Error > { @@ -233,34 +276,23 @@ mod private } } -// impl TryFrom< Utf8PathBuf > for AbsolutePath -// { -// type Error = std::io::Error; -// -// fn try_from( src : Utf8PathBuf ) -> Result< Self, Self::Error > -// { -// AbsolutePath::try_from( src.as_std_path() ) -// } -// } - -// impl TryFrom< &Utf8Path > for AbsolutePath -// { -// type Error = std::io::Error; -// -// fn try_from( src : &Utf8Path ) -> Result< Self, Self::Error > -// { -// AbsolutePath::try_from( src.as_std_path() ) -// } -// } - - // // xxx : use derives - // impl AsRef< Path > for AbsolutePath - // { - // fn as_ref( &self ) -> &Path - // { - // self.0.as_ref() - // } - // } + impl TryIntoPath for AbsolutePath + { + #[ inline ] + fn try_into_path( self ) -> Result< PathBuf, io::Error > + { + Ok( self.0 ) + } + } + + impl< 'a > TryIntoCowPath< 'a > for AbsolutePath + { + #[ inline ] + fn try_into_cow_path( self ) -> Result< Cow<'a, Path>, io::Error > + { + Ok( Cow::Owned( self.0 ) ) + } + } impl AsRef< Path > for AbsolutePath { @@ -283,6 +315,7 @@ mod private impl Deref for AbsolutePath { type Target = Path; + #[ inline ] fn deref( &self ) -> &Self::Target { @@ -298,42 +331,9 @@ mod private &mut self.0 } } - -// /// Convertable into absolute path entity should implement the trait. -// pub trait TryIntoAbsolutePath -// { -// /// Error returned if conversion is failed. -// type Error; -// /// Method to convert the type into absolute path. -// fn into_absolute_path( self ) -> Result< AbsolutePath, Self::Error >; -// } -// -// // impl TryIntoAbsolutePath for AbsolutePath -// // { -// // type Error = std::io::Error; -// // #[ inline ] -// // fn into_absolute_path( self ) -> Result< AbsolutePath, Self::Error > -// // { -// // Ok( self ) -// // } -// // } -// -// impl< TryIntoAbsolutePathType > TryIntoAbsolutePath for TryIntoAbsolutePathType -// where -// TryIntoAbsolutePathType : TryInto< AbsolutePath >, -// { -// type Error = < Self as TryInto< AbsolutePath > >::Error; -// #[ inline ] -// fn into_absolute_path( self ) -> Result< AbsolutePath, Self::Error > -// { -// self.try_into() -// } -// } - } crate::mod_interface! { exposed use AbsolutePath; - // exposed use TryIntoAbsolutePath; -} +} \ No newline at end of file diff --git a/module/core/proper_path_tools/src/path/canonical_path.rs b/module/core/pth/src/path/canonical_path.rs similarity index 81% rename from module/core/proper_path_tools/src/path/canonical_path.rs rename to module/core/pth/src/path/canonical_path.rs index 45886ded78..b45be827c0 100644 --- a/module/core/proper_path_tools/src/path/canonical_path.rs +++ b/module/core/pth/src/path/canonical_path.rs @@ -1,7 +1,8 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use std:: @@ -21,14 +22,18 @@ mod private }, }; - #[cfg(feature="no_std")] + // qqq : xxx : redo + #[ cfg( feature="no_std" ) ] extern crate std; + #[ cfg( feature="no_std" ) ] + use alloc::string::String; + #[ cfg( feature = "derive_serde" ) ] use serde::{ Serialize, Deserialize }; - #[ cfg( feature = "path_utf8" ) ] - use camino::{ Utf8Path, Utf8PathBuf }; + // #[ cfg( feature = "path_utf8" ) ] + // use camino::{ Utf8Path, Utf8PathBuf }; /// Caninical path. #[ cfg_attr( feature = "derive_serde", derive( Serialize, Deserialize ) ) ] @@ -47,7 +52,10 @@ mod private } /// Creates an owned `CanonicalPath` with path adjoined to self. + /// # Panics + /// qqq: doc #[ inline ] + #[ must_use ] pub fn join< P >( &self, path : P ) -> CanonicalPath where P : AsRef< Path >, @@ -70,8 +78,9 @@ mod private self.0.starts_with( base ) } - /// Returns inner type which is PathBuf. + /// Returns inner type which is `PathBuf`. #[ inline( always ) ] + #[ must_use ] pub fn inner( self ) -> PathBuf { self.0 @@ -111,6 +120,29 @@ mod private } } + impl< 'a > TryFrom< &'a String > for CanonicalPath + { + type Error = std::io::Error; + + #[ inline ] + fn try_from( src : &'a String ) -> Result< Self, Self::Error > + { + < Self as TryFrom< &Path > >::try_from( src.as_ref() ) + } + } + + #[ allow( clippy::extra_unused_lifetimes ) ] + impl< 'a > TryFrom< String > for CanonicalPath + { + type Error = std::io::Error; + + #[ inline ] + fn try_from( src : String ) -> Result< Self, Self::Error > + { + < Self as TryFrom< &Path > >::try_from( src.as_ref() ) + } + } + impl TryFrom< PathBuf > for CanonicalPath { type Error = std::io::Error; @@ -213,6 +245,32 @@ mod private } } + impl TryIntoPath for CanonicalPath + { + #[ inline ] + fn try_into_path( self ) -> Result< PathBuf, io::Error > + { + Ok( self.0 ) + } + } + + impl< 'a > TryIntoCowPath< 'a > for CanonicalPath + { + #[ inline ] + fn try_into_cow_path( self ) -> Result< Cow<'a, Path>, io::Error > + { + Ok( Cow::Owned( self.0 ) ) + } + } + + // impl AsPath for CanonicalPath + // { + // fn as_path( &self ) -> &Path + // { + // self.0.as_path() + // } + // } + // impl TryFrom< Utf8PathBuf > for CanonicalPath // { // type Error = std::io::Error; diff --git a/module/core/pth/src/path/current_path.rs b/module/core/pth/src/path/current_path.rs new file mode 100644 index 0000000000..5fd1700ff2 --- /dev/null +++ b/module/core/pth/src/path/current_path.rs @@ -0,0 +1,108 @@ +/// Define a private namespace for all its items. +mod private +{ + + #[ allow( clippy::wildcard_imports ) ] + use crate::*; + #[ cfg( not( feature = "no_std" ) ) ] + use std:: + { + env, + io, + }; + + /// Symbolize current path. + #[ derive( Clone, Copy, Debug, Default, PartialEq, Eq ) ] + pub struct CurrentPath; + + #[ cfg( feature = "path_utf8" ) ] + #[ cfg( not( feature = "no_std" ) ) ] + impl TryFrom< CurrentPath > for Utf8PathBuf + { + #[ cfg( not( feature = "no_std" ) ) ] + type Error = std::io::Error; + + #[ inline ] + fn try_from( src : CurrentPath ) -> Result< Self, Self::Error > + { + Utf8PathBuf::try_from( PathBuf::try_from( src )? ) + .map_err + ( + | err | + { + #[ cfg( not( feature = "no_std" ) ) ] + std::io::Error::new + ( + std::io::ErrorKind::NotFound, + format!( "Cant convert to utf8 {err}" ), + ) + } + ) + } + } + + #[ cfg( not( feature = "no_std" ) ) ] + impl TryFrom< CurrentPath > for PathBuf + { + #[ cfg( not( feature = "no_std" ) ) ] + type Error = std::io::Error; + + #[ inline ] + fn try_from( _ : CurrentPath ) -> Result< Self, Self::Error > + { + env::current_dir() + } + } + + #[ cfg( not( feature = "no_std" ) ) ] + impl TryFrom< CurrentPath > for AbsolutePath + { + #[ cfg( not( feature = "no_std" ) ) ] + type Error = std::io::Error; + + #[ inline ] + fn try_from( src : CurrentPath ) -> Result< Self, Self::Error > + { + AbsolutePath::try_from( PathBuf::try_from( src )? ) + } + } + + impl TryIntoPath for &CurrentPath + { + fn try_into_path( self ) -> Result< PathBuf, io::Error > + { + env::current_dir() + } + } + + impl TryIntoPath for CurrentPath + { + fn try_into_path( self ) -> Result< PathBuf, io::Error > + { + env::current_dir() + } + } + + impl< 'a > TryIntoCowPath< 'a > for CurrentPath + { + fn try_into_cow_path( self ) -> Result< Cow<'a, Path>, io::Error > + { + let current_dir = env::current_dir()?; + Ok( Cow::Owned( current_dir ) ) + } + } + + impl< 'a > TryIntoCowPath< 'a > for &CurrentPath + { + fn try_into_cow_path( self ) -> Result< Cow<'a, Path>, io::Error > + { + TryIntoCowPath::try_into_cow_path( *self ) + } + } + +} + +crate::mod_interface! +{ + exposed use CurrentPath; +} diff --git a/module/core/pth/src/path/joining.rs b/module/core/pth/src/path/joining.rs new file mode 100644 index 0000000000..73eacc7304 --- /dev/null +++ b/module/core/pth/src/path/joining.rs @@ -0,0 +1,196 @@ +mod private +{ + #[ allow( clippy::wildcard_imports ) ] + use crate::*; + use std::{ io, path::PathBuf }; + + /// Joins path components into a `PathBuf`. + /// + /// This function leverages the `PathJoined` trait to join multiple path components into a single `PathBuf`. + /// + /// # Arguments + /// + /// * `paths` - A tuple of path components implementing the `PathJoined` trait. + /// + /// # Returns + /// + /// * `Ok(PathBuf)` - The joined path as a `PathBuf`. + /// * `Err(io::Error)` - An error if any component fails to convert. + /// # Errors + /// qqq: doc + pub fn join< Paths : PathJoined >( paths : Paths ) -> Result< PathBuf, io::Error > + { + paths.iter_join() + } + + /// A trait for joining path components into a `PathBuf`. + /// + /// This trait provides a method to join multiple path components into a single `PathBuf`. + /// It is implemented for tuples of varying lengths, allowing for flexible combinations of path components. + /// Each component must implement the `TryIntoCowPath` trait, enabling conversion into a `Cow`. + pub trait PathJoined + { + /// Joins the path components into a single `PathBuf`. + /// + /// # Returns + /// + /// * `Ok(PathBuf)` - The joined path as a `PathBuf`. + /// * `Err(io::Error)` - An error if any component fails to convert. + /// # Errors + /// qqq: doc + fn iter_join( self ) -> Result< PathBuf, io::Error >; + } + + // // Implementation for an Iterator over items implementing TryIntoCowPath + // impl< 'a, I, T > PathJoined for I + // where + // I : Iterator< Item = T >, + // T : TryIntoCowPath< 'a >, + // { + // fn iter_join( self ) -> Result< PathBuf, io::Error > + // { + // let mut result = PathBuf::new(); + // for item in self + // { + // result.push( item.try_into_cow_path()?.as_ref() ); + // } + // Ok( result ) + // } + // } + + // Implementation for a tuple of length 1 + impl< 'a, T1 > PathJoined for ( T1, ) + where + T1 : TryIntoCowPath< 'a >, + { + #[ inline ] + fn iter_join( self ) -> Result< PathBuf, io::Error > + { + let ( p1, ) = self; + let mut result = PathBuf::new(); + result.push( p1.try_into_cow_path()?.as_ref() ); + Ok( result ) + } + } + + // Implementation for a tuple of length 2 + impl< 'a, T1, T2 > PathJoined for ( T1, T2 ) + where + T1 : TryIntoCowPath< 'a >, + T2 : TryIntoCowPath< 'a >, + { + #[ inline ] + fn iter_join( self ) -> Result< PathBuf, io::Error > + { + let ( p1, p2 ) = self; + let mut result = PathBuf::new(); + result.push( p1.try_into_cow_path()?.as_ref() ); + result.push( p2.try_into_cow_path()?.as_ref() ); + Ok( result ) + } + } + + // Implementation for a tuple of length 3 + impl< 'a, T1, T2, T3 > PathJoined for ( T1, T2, T3 ) + where + T1 : TryIntoCowPath< 'a >, + T2 : TryIntoCowPath< 'a >, + T3 : TryIntoCowPath< 'a >, + { + #[ inline ] + fn iter_join( self ) -> Result< PathBuf, io::Error > + { + let ( p1, p2, p3 ) = self; + let mut result = PathBuf::new(); + result.push( p1.try_into_cow_path()?.as_ref() ); + result.push( p2.try_into_cow_path()?.as_ref() ); + result.push( p3.try_into_cow_path()?.as_ref() ); + Ok( result ) + } + } + + // Implementation for a tuple of length 4 + impl< 'a, T1, T2, T3, T4 > PathJoined for ( T1, T2, T3, T4 ) + where + T1 : TryIntoCowPath< 'a >, + T2 : TryIntoCowPath< 'a >, + T3 : TryIntoCowPath< 'a >, + T4 : TryIntoCowPath< 'a >, + { + #[ inline ] + fn iter_join( self ) -> Result< PathBuf, io::Error > + { + let ( p1, p2, p3, p4 ) = self; + let mut result = PathBuf::new(); + result.push( p1.try_into_cow_path()?.as_ref() ); + result.push( p2.try_into_cow_path()?.as_ref() ); + result.push( p3.try_into_cow_path()?.as_ref() ); + result.push( p4.try_into_cow_path()?.as_ref() ); + Ok( result ) + } + } + + // Implementation for a tuple of length 5 + impl< 'a, T1, T2, T3, T4, T5 > PathJoined for ( T1, T2, T3, T4, T5 ) + where + T1 : TryIntoCowPath< 'a >, + T2 : TryIntoCowPath< 'a >, + T3 : TryIntoCowPath< 'a >, + T4 : TryIntoCowPath< 'a >, + T5 : TryIntoCowPath< 'a >, + { + #[ inline ] + fn iter_join( self ) -> Result< PathBuf, io::Error > + { + let ( p1, p2, p3, p4, p5 ) = self; + let mut result = PathBuf::new(); + result.push( p1.try_into_cow_path()?.as_ref() ); + result.push( p2.try_into_cow_path()?.as_ref() ); + result.push( p3.try_into_cow_path()?.as_ref() ); + result.push( p4.try_into_cow_path()?.as_ref() ); + result.push( p5.try_into_cow_path()?.as_ref() ); + Ok( result ) + } + } + + // Implementation for slices + impl< 'a, T > PathJoined for &'a [ T ] + where + T : TryIntoCowPath< 'a > + Clone, + { + #[ inline ] + fn iter_join( self ) -> Result< PathBuf, io::Error > + { + let mut result = PathBuf::new(); + for item in self + { + result.push( item.clone().try_into_cow_path()?.as_ref() ); + } + Ok( result ) + } + } + + // Implementation for arrays + impl< 'a, T, const N : usize > PathJoined for [ T; N ] + where + T : TryIntoCowPath< 'a > + Clone, + { + #[ inline ] + fn iter_join( self ) -> Result< PathBuf, io::Error > + { + let mut result = PathBuf::new(); + for item in &self + { + result.push( item.clone().try_into_cow_path()?.as_ref() ); + } + Ok( result ) + } + } + +} + +crate::mod_interface! +{ + orphan use join; + exposed use PathJoined; +} diff --git a/module/core/proper_path_tools/src/path/native_path.rs b/module/core/pth/src/path/native_path.rs similarity index 82% rename from module/core/proper_path_tools/src/path/native_path.rs rename to module/core/pth/src/path/native_path.rs index df2e7ca559..56a249c457 100644 --- a/module/core/proper_path_tools/src/path/native_path.rs +++ b/module/core/pth/src/path/native_path.rs @@ -1,7 +1,8 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use std:: @@ -21,14 +22,17 @@ mod private }, }; - #[cfg(feature="no_std")] + #[ cfg( feature="no_std" ) ] extern crate std; + #[ cfg( feature="no_std" ) ] + use alloc::string::String; + #[ cfg( feature = "derive_serde" ) ] use serde::{ Serialize, Deserialize }; - #[ cfg( feature = "path_utf8" ) ] - use camino::{ Utf8Path, Utf8PathBuf }; + // #[ cfg( feature = "path_utf8" ) ] + // use camino::{ Utf8Path, Utf8PathBuf }; /// Caninical path. #[ cfg_attr( feature = "derive_serde", derive( Serialize, Deserialize ) ) ] @@ -47,7 +51,10 @@ mod private } /// Creates an owned `NativePath` with path adjoined to self. + /// # Panics + /// qqq: doc #[ inline ] + #[ must_use ] pub fn join< P >( &self, path : P ) -> NativePath where P : AsRef< Path >, @@ -70,8 +77,9 @@ mod private self.0.starts_with( base ) } - /// Returns inner type which is PathBuf. + /// Returns inner type which is `PathBuf`. #[ inline( always ) ] + #[ must_use ] pub fn inner( self ) -> PathBuf { self.0 @@ -111,6 +119,29 @@ mod private } } + impl< 'a > TryFrom< &'a String > for NativePath + { + type Error = std::io::Error; + + #[ inline ] + fn try_from( src : &'a String ) -> Result< Self, Self::Error > + { + < Self as TryFrom< &Path > >::try_from( src.as_ref() ) + } + } + + #[ allow( clippy::extra_unused_lifetimes ) ] + impl< 'a > TryFrom< String > for NativePath + { + type Error = std::io::Error; + + #[ inline ] + fn try_from( src : String ) -> Result< Self, Self::Error > + { + < Self as TryFrom< &Path > >::try_from( src.as_ref() ) + } + } + impl TryFrom< PathBuf > for NativePath { type Error = std::io::Error; @@ -228,6 +259,32 @@ mod private } } + impl TryIntoPath for NativePath + { + #[ inline ] + fn try_into_path( self ) -> Result< PathBuf, io::Error > + { + Ok( self.0 ) + } + } + + impl< 'a > TryIntoCowPath< 'a > for NativePath + { + #[ inline ] + fn try_into_cow_path( self ) -> Result< Cow<'a, Path>, io::Error > + { + Ok( Cow::Owned( self.0 ) ) + } + } + + // impl AsPath for NativePath + // { + // fn as_path( &self ) -> &Path + // { + // self.0.as_path() + // } + // } + // impl TryFrom< Utf8PathBuf > for NativePath // { // type Error = std::io::Error; diff --git a/module/core/proper_path_tools/src/transitive.rs b/module/core/pth/src/transitive.rs similarity index 96% rename from module/core/proper_path_tools/src/transitive.rs rename to module/core/pth/src/transitive.rs index 9de7eef34b..ca1988f502 100644 --- a/module/core/proper_path_tools/src/transitive.rs +++ b/module/core/pth/src/transitive.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { // xxx : move to derive_tools @@ -49,7 +49,7 @@ mod private /// # Example /// /// ```rust - /// use proper_path_tools::TransitiveTryFrom; + /// use pth::TransitiveTryFrom; /// use std::convert::TryFrom; /// /// struct InitialType; @@ -99,6 +99,9 @@ mod private /// # Example /// /// See the trait-level documentation for an example. + /// + /// # Errors + /// qqq: doc #[ inline( always ) ] fn transitive_try_from< Transitive >( src : Initial ) -> Result< Self, Error > where @@ -132,7 +135,7 @@ mod private /// # Example /// /// ```rust - /// use proper_path_tools::TransitiveTryInto; + /// use pth::TransitiveTryInto; /// use std::convert::TryInto; /// /// struct InitialType; @@ -178,6 +181,8 @@ mod private /// # Example /// /// See the trait-level documentation for an example. + /// # Errors + /// qqq: doc #[ inline( always ) ] fn transitive_try_into< Transitive >( self ) -> Result< Final, Error > where diff --git a/module/core/pth/src/try_into_cow_path.rs b/module/core/pth/src/try_into_cow_path.rs new file mode 100644 index 0000000000..66ae97d892 --- /dev/null +++ b/module/core/pth/src/try_into_cow_path.rs @@ -0,0 +1,115 @@ +/// Define a private namespace for all its items. +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] +mod private +{ + #[ allow( clippy::wildcard_imports ) ] + use crate::*; + use std:: + { + borrow::Cow, + io, + path::{ Component, Path, PathBuf }, + }; + // use camino::{ Utf8Path, Utf8PathBuf }; + + /// A trait for converting various types into a `Cow`. + /// + /// This trait is designed to avoid redundant memory allocation. + /// Unlike `TryIntoPath`, it does not allocate memory on the heap if it's not necessary. + /// Unlike `AsPath`, it is implemented for a wider number of path-like types, similar to `TryIntoPath`. + /// The drawback is the necessity to differentiate borrowed and owned paths at runtime. + pub trait TryIntoCowPath<'a> + { + /// Converts the implementing type into a `Cow`. + /// + /// # Returns + /// + /// * `Ok(Cow)` - A `Cow` that may be either borrowed or owned, depending on the input type. + /// * `Err(io::Error)` - An error if the conversion fails. + /// # Errors + /// qqq: doc + fn try_into_cow_path( self ) -> Result< Cow<'a, Path>, io::Error >; + } + + /// Implementation of `TryIntoCowPath` for `String`. + impl<'a> TryIntoCowPath<'a> for &'a str + { + fn try_into_cow_path( self ) -> Result< Cow<'a, Path>, io::Error > + { + Ok( Cow::Borrowed( self.as_path() ) ) + } + } + + /// Implementation of `TryIntoCowPath` for `String`. + impl<'a> TryIntoCowPath<'a> for String + { + fn try_into_cow_path( self ) -> Result< Cow<'a, Path>, io::Error > + { + Ok( Cow::Owned( PathBuf::from( self ) ) ) + } + } + + /// Implementation of `TryIntoCowPath` for `PathBuf`. + impl<'a> TryIntoCowPath<'a> for PathBuf + { + fn try_into_cow_path( self ) -> Result< Cow<'a, Path>, io::Error > + { + Ok( Cow::Owned( self ) ) + } + } + + /// Implementation of `TryIntoCowPath` for a reference to `Path`. + impl<'a> TryIntoCowPath<'a> for &'a Path + { + fn try_into_cow_path( self ) -> Result< Cow<'a, Path>, io::Error > + { + Ok( Cow::Borrowed( self ) ) + } + } + + /// Implementation of `TryIntoCowPath` for a reference to `Utf8Path`. + #[cfg( feature = "path_utf8" )] + impl< 'a > TryIntoCowPath< 'a > for &'a Utf8Path + { + fn try_into_cow_path( self ) -> Result< Cow<'a, Path>, io::Error > + { + Ok( Cow::Borrowed( self.as_std_path() ) ) + } + } + + /// Implementation of `TryIntoCowPath` for `Utf8PathBuf`. + #[cfg( feature = "path_utf8" )] + impl<'a> TryIntoCowPath<'a> for Utf8PathBuf + { + fn try_into_cow_path( self ) -> Result< Cow<'a, Path>, io::Error > + { + Ok( Cow::Owned( self.as_std_path().to_path_buf() ) ) + } + } + + /// Implementation of `TryIntoCowPath` for `std::path::Component`. + impl<'a> TryIntoCowPath<'a> for Component<'a> + { + fn try_into_cow_path( self ) -> Result< Cow<'a, Path>, io::Error > + { + Ok( Cow::Owned( PathBuf::from( self.as_os_str() ) ) ) + } + } + + /// Blanket implementation of `TryIntoCowPath` for references to types implementing `AsPath`. + impl<'a, T> TryIntoCowPath< 'a > for &'a T + where + T : AsPath, + { + fn try_into_cow_path( self ) -> Result< Cow<'a, Path>, io::Error > + { + Ok( Cow::Borrowed( self.as_path() ) ) + } + } + +} + +crate::mod_interface! +{ + orphan use TryIntoCowPath; +} \ No newline at end of file diff --git a/module/core/pth/src/try_into_path.rs b/module/core/pth/src/try_into_path.rs new file mode 100644 index 0000000000..85efc902d9 --- /dev/null +++ b/module/core/pth/src/try_into_path.rs @@ -0,0 +1,111 @@ +/// Define a private namespace for all its items. +mod private +{ + #[ allow( unused_imports, clippy::wildcard_imports ) ] + use crate::*; + use std:: + { + io, + path::{ Component, Path, PathBuf }, + }; + // use camino::{ Utf8Path, Utf8PathBuf }; + + /// A trait for converting various types into a `PathBuf`. + /// + /// This trait is used to convert any path-like type into an owned `PathBuf`. + /// Unlike `TryIntoCowPath`, it always returns an owned `PathBuf`, so there is no need to differentiate between borrowed and owned paths at runtime. + /// Unlike `AsPath`, it is implemented for a wider range of path-like types, similar to `TryIntoCowPath`. + pub trait TryIntoPath + { + /// Converts the implementing type into a `PathBuf`. + /// + /// # Returns + /// + /// * `Ok(PathBuf)` - The owned path buffer. + /// * `Err(io::Error)` - An error if the conversion fails. + /// # Errors + /// qqq: doc + fn try_into_path( self ) -> Result< PathBuf, io::Error >; + } + + /// Implementation of `TryIntoPath` for `&str`. + impl TryIntoPath for &str + { + fn try_into_path( self ) -> Result< PathBuf, io::Error > + { + Ok( PathBuf::from( self ) ) + } + } + + /// Implementation of `TryIntoPath` for `String`. + impl TryIntoPath for String + { + fn try_into_path( self ) -> Result< PathBuf, io::Error > + { + Ok( PathBuf::from( self ) ) + } + } + + /// Implementation of `TryIntoPath` for a reference to `Path`. + impl TryIntoPath for &Path + { + fn try_into_path( self ) -> Result< PathBuf, io::Error > + { + Ok( self.to_path_buf() ) + } + } + + /// Implementation of `TryIntoPath` for `PathBuf`. + impl TryIntoPath for PathBuf + { + fn try_into_path( self ) -> Result< PathBuf, io::Error > + { + Ok( self ) + } + } + + /// Implementation of `TryIntoPath` for a reference to `Utf8Path`. + #[cfg( feature = "path_utf8" )] + impl TryIntoPath for &Utf8Path + { + fn try_into_path( self ) -> Result< PathBuf, io::Error > + { + Ok( self.as_std_path().to_path_buf() ) + } + } + + /// Implementation of `TryIntoPath` for `Utf8PathBuf`. + #[cfg( feature = "path_utf8" )] + impl TryIntoPath for Utf8PathBuf + { + fn try_into_path( self ) -> Result< PathBuf, io::Error > + { + Ok( self.as_std_path().to_path_buf() ) + } + } + + /// Implementation of `TryIntoPath` for `std::path::Component`. + impl TryIntoPath for Component<'_> + { + fn try_into_path( self ) -> Result< PathBuf, io::Error > + { + Ok( self.as_os_str().into() ) + } + } + + /// Blanket implementation of `TryIntoPath` for references to types implementing `AsRef`. + impl< T > TryIntoPath for &T + where + T : AsRef< Path >, + { + fn try_into_path( self ) -> Result< PathBuf, io::Error > + { + Ok( self.as_ref().to_path_buf() ) + } + } +} + +crate::mod_interface! +{ + orphan use TryIntoPath; +} \ No newline at end of file diff --git a/module/core/proper_path_tools/tests/experiment.rs b/module/core/pth/tests/experiment.rs similarity index 93% rename from module/core/proper_path_tools/tests/experiment.rs rename to module/core/pth/tests/experiment.rs index 29e2cd3eba..3116a9c61b 100644 --- a/module/core/proper_path_tools/tests/experiment.rs +++ b/module/core/pth/tests/experiment.rs @@ -1,8 +1,9 @@ +//! Experiment include!( "../../../../module/step/meta/src/module/terminal.rs" ); #[ allow( unused_imports ) ] -use proper_path_tools as the_module; +use pth as the_module; #[ allow( unused_imports ) ] use test_tools::exposed::*; diff --git a/module/core/pth/tests/inc/absolute_path_test.rs b/module/core/pth/tests/inc/absolute_path_test.rs new file mode 100644 index 0000000000..6d15a1fb2b --- /dev/null +++ b/module/core/pth/tests/inc/absolute_path_test.rs @@ -0,0 +1,5 @@ +use super::*; + +mod basic_test; +mod from_paths_test; +mod try_from_test; diff --git a/module/core/proper_path_tools/tests/inc/absolute_path.rs b/module/core/pth/tests/inc/absolute_path_test/basic_test.rs similarity index 91% rename from module/core/proper_path_tools/tests/inc/absolute_path.rs rename to module/core/pth/tests/inc/absolute_path_test/basic_test.rs index 247be8c4b4..c4563ab9f8 100644 --- a/module/core/proper_path_tools/tests/inc/absolute_path.rs +++ b/module/core/pth/tests/inc/absolute_path_test/basic_test.rs @@ -1,4 +1,3 @@ -#[ allow( unused_imports ) ] use super::*; use the_module:: @@ -8,9 +7,6 @@ use the_module:: PathBuf, }; -// #[ cfg( feature = "path_utf8" ) ] -// use the_module::Utf8PathBuf; - #[ test ] fn basic() { @@ -28,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(); @@ -36,7 +32,8 @@ 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() { @@ -45,7 +42,8 @@ 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() { let path = Path::new( "/path/to/some/file.txt" ); @@ -53,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(); @@ -61,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(); @@ -69,8 +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"; @@ -78,7 +75,8 @@ 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() { let rel_path_buf = PathBuf::from( "src/main.rs" ); @@ -86,7 +84,8 @@ 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() { let rel_path = Path::new( "src/main.rs" ); @@ -95,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(); @@ -103,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/absolute_path_test/from_paths_test.rs b/module/core/pth/tests/inc/absolute_path_test/from_paths_test.rs new file mode 100644 index 0000000000..3e5bd05dd4 --- /dev/null +++ b/module/core/pth/tests/inc/absolute_path_test/from_paths_test.rs @@ -0,0 +1,92 @@ +use super::*; + +// xxx : make it working + +#[ test ] +fn test_from_paths_single_absolute_segment() +{ + use the_module::AbsolutePath; + use std::convert::TryFrom; + + let segments = vec![ "/single" ]; + let got = AbsolutePath::from_iter( segments.iter().map( |s| *s ) ).unwrap(); + let exp = AbsolutePath::try_from( "/single" ).unwrap(); + + assert_eq!( got, exp ); +} + +#[ test ] +fn test_from_paths_multiple_segments() +{ + use the_module::AbsolutePath; + use std::convert::TryFrom; + + let segments = vec![ "/path", "to", "file" ]; + let got = AbsolutePath::from_iter( segments.iter().map( |s| *s ) ).unwrap(); + let exp = AbsolutePath::try_from( "/path/to/file" ).unwrap(); + + assert_eq!( got, exp ); +} + +#[ test ] +fn test_from_paths_empty_segments() +{ + use the_module::AbsolutePath; + + let segments : Vec< &str > = vec![]; + let result = AbsolutePath::from_iter( segments.iter().map( | s | *s ) ); + + assert!( result.is_err(), "Expected an error for empty segments" ); +} + +#[ test ] +fn test_from_paths_with_dot_segments() +{ + use the_module::AbsolutePath; + use std::convert::TryFrom; + + let segments = vec![ "/path", ".", "to", "file" ]; + let got = AbsolutePath::from_iter( segments.iter().map( |s| *s ) ).unwrap(); + let exp = AbsolutePath::try_from( "/path/to/file" ).unwrap(); + + assert_eq!( got, exp ); +} + +#[ test ] +fn test_from_paths_with_dotdot_segments() +{ + use the_module::AbsolutePath; + use std::convert::TryFrom; + + let segments = vec![ "/path", "to", "..", "file" ]; + let got = AbsolutePath::from_iter( segments.iter().map( |s| *s ) ).unwrap(); + let exp = AbsolutePath::try_from( "/path/file" ).unwrap(); + + assert_eq!( got, exp ); +} + +#[ test ] +fn test_from_paths_with_trailing_slash() +{ + use the_module::AbsolutePath; + use std::convert::TryFrom; + + let segments = vec![ "/path", "to", "file/" ]; + let got = AbsolutePath::from_iter( segments.iter().map( |s| *s ) ).unwrap(); + let exp = AbsolutePath::try_from( "/path/to/file/" ).unwrap(); + + assert_eq!( got, exp ); +} + +#[ test ] +fn test_from_paths_with_mixed_slashes() +{ + use the_module::AbsolutePath; + use std::convert::TryFrom; + + let segments = vec![ "/path\\to", "file" ]; + let got = AbsolutePath::from_iter( segments.iter().map( |s| *s ) ).unwrap(); + let exp = AbsolutePath::try_from( "/path/to/file" ).unwrap(); + + assert_eq!( got, exp ); +} diff --git a/module/core/pth/tests/inc/absolute_path_test/try_from_test.rs b/module/core/pth/tests/inc/absolute_path_test/try_from_test.rs new file mode 100644 index 0000000000..ee1aa2b3a1 --- /dev/null +++ b/module/core/pth/tests/inc/absolute_path_test/try_from_test.rs @@ -0,0 +1,55 @@ +use super::*; +use std::convert::TryFrom; + +#[ test ] +fn try_from_absolute_path_test() +{ + use std::path::{ Path, PathBuf }; + use the_module::AbsolutePath; + + // Create an AbsolutePath instance + let absolute_path = AbsolutePath::try_from( "/absolute/path" ).unwrap(); + + // Test conversion to &str + let path_str : &str = TryFrom::try_from( &absolute_path ).unwrap(); + println!( "&str from AbsolutePath: {:?}", path_str ); + assert_eq!( path_str, "/absolute/path" ); + + // Test conversion to String + let path_string : String = TryFrom::try_from( &absolute_path ).unwrap(); + println!( "String from AbsolutePath: {:?}", path_string ); + assert_eq!( path_string, "/absolute/path" ); + + // Test conversion to PathBuf + let path_buf : PathBuf = TryFrom::try_from( absolute_path.clone() ).unwrap(); + println!( "PathBuf from AbsolutePath: {:?}", path_buf ); + assert_eq!( path_buf, PathBuf::from( "/absolute/path" ) ); + + // Test conversion to &Path + let path_ref : &Path = absolute_path.as_ref(); + println!( "&Path from AbsolutePath: {:?}", path_ref ); + assert_eq!( path_ref, Path::new( "/absolute/path" ) ); + + // Test conversion from &String + let string_path : String = String::from( "/absolute/path" ); + let absolute_path_from_string : AbsolutePath = TryFrom::try_from( &string_path ).unwrap(); + println!( "AbsolutePath from &String: {:?}", absolute_path_from_string ); + assert_eq!( absolute_path_from_string, absolute_path ); + + // Test conversion from String + let absolute_path_from_owned_string : AbsolutePath = TryFrom::try_from( string_path.clone() ).unwrap(); + println!( "AbsolutePath from String: {:?}", absolute_path_from_owned_string ); + assert_eq!( absolute_path_from_owned_string, absolute_path ); + + // Test conversion from &Path + let path_ref : &Path = Path::new( "/absolute/path" ); + let absolute_path_from_path_ref : AbsolutePath = TryFrom::try_from( path_ref ).unwrap(); + println!( "AbsolutePath from &Path: {:?}", absolute_path_from_path_ref ); + assert_eq!( absolute_path_from_path_ref, absolute_path ); + + // Test conversion from PathBuf + let path_buf_instance : PathBuf = PathBuf::from( "/absolute/path" ); + let absolute_path_from_path_buf : AbsolutePath = TryFrom::try_from( path_buf_instance.clone() ).unwrap(); + println!( "AbsolutePath from PathBuf: {:?}", absolute_path_from_path_buf ); + assert_eq!( absolute_path_from_path_buf, absolute_path ); +} \ No newline at end of file diff --git a/module/core/pth/tests/inc/as_path_test.rs b/module/core/pth/tests/inc/as_path_test.rs new file mode 100644 index 0000000000..340a6540ca --- /dev/null +++ b/module/core/pth/tests/inc/as_path_test.rs @@ -0,0 +1,103 @@ +use super::*; + +#[ test ] +fn as_path_test() +{ + use std::path::{ Component, Path, PathBuf }; + #[ cfg( feature = "path_utf8" ) ] + use the_module::{ Utf8Path, Utf8PathBuf }; + use the_module::{ AsPath, AbsolutePath, CanonicalPath, NativePath, CurrentPath }; + + // Test with &str + let path_str : &str = "/some/path"; + let path : &Path = AsPath::as_path( path_str ); + println!( "Path from &str: {:?}", path ); + + // Test with &String + let string_path : String = String::from( "/another/path" ); + let path : &Path = AsPath::as_path( &string_path ); + println!( "Path from &String: {:?}", path ); + + // Test with String + let path : &Path = AsPath::as_path( &string_path ); + println!( "Path from String: {:?}", path ); + + // Test with &Path + let path_ref : &Path = Path::new( "/yet/another/path" ); + let path : &Path = AsPath::as_path( path_ref ); + println!( "Path from &Path: {:?}", path ); + + // Test with &PathBuf + let path_buf : PathBuf = PathBuf::from( "/yet/another/path" ); + let path : &Path = AsPath::as_path( &path_buf ); + println!( "Path from &PathBuf: {:?}", path ); + + // Test with PathBuf + let path : &Path = AsPath::as_path( &path_buf ); + println!( "Path from PathBuf: {:?}", path ); + + // Test with &AbsolutePath + let absolute_path : AbsolutePath = AbsolutePath::try_from( "/absolute/path" ).unwrap(); + let path : &Path = AsPath::as_path( &absolute_path ); + println!( "Path from &AbsolutePath: {:?}", path ); + + // Test with AbsolutePath + let path : &Path = AsPath::as_path( &absolute_path ); + println!( "Path from AbsolutePath: {:?}", path ); + + // Test with &CanonicalPath + let canonical_path = CanonicalPath::try_from( "/canonical/path" ).unwrap(); + let path : &Path = AsPath::as_path( &canonical_path ); + println!( "Path from &CanonicalPath: {:?}", path ); + + // Test with CanonicalPath + let path : &Path = AsPath::as_path( &canonical_path ); + println!( "Path from CanonicalPath: {:?}", path ); + + // Test with &NativePath + let native_path = NativePath::try_from( PathBuf::from( "/native/path" ) ).unwrap(); + let path : &Path = AsPath::as_path( &native_path ); + println!( "Path from &NativePath: {:?}", path ); + + // Test with NativePath + let path : &Path = AsPath::as_path( &native_path ); + println!( "Path from NativePath: {:?}", path ); + + // Test with &Component + let root_component : Component< '_ > = Component::RootDir; + let path : &Path = AsPath::as_path( &root_component ); + println!( "Path from &Component: {:?}", path ); + + // Test with Component + let path : &Path = AsPath::as_path( &root_component ); + println!( "Path from Component: {:?}", path ); + + // Test with Component + let path = Path::new( "/component/path" ); + for component in path.components() + { + let path : &Path = AsPath::as_path( &component ); + println!( "Path from Component: {:?}", path ); + } + + #[ cfg( feature = "path_utf8" ) ] + { + // Test with &Utf8Path + let utf8_path = Utf8Path::new( "/utf8/path" ); + let path : &Path = AsPath::as_path( &utf8_path ); + println!( "Path from &Utf8Path: {:?}", path ); + + // Test with Utf8Path + let path : &Path = AsPath::as_path( &utf8_path ); + println!( "Path from Utf8Path: {:?}", path ); + + // Test with &Utf8PathBuf + let utf8_path_buf = Utf8PathBuf::from( "/utf8/pathbuf" ); + let path : &Path = AsPath::as_path( &utf8_path_buf ); + println!( "Path from &Utf8PathBuf: {:?}", path ); + + // Test with Utf8PathBuf + let path : &Path = AsPath::as_path( &utf8_path_buf ); + println!( "Path from Utf8PathBuf: {:?}", path ); + } +} diff --git a/module/core/proper_path_tools/tests/inc/current_path.rs b/module/core/pth/tests/inc/current_path.rs similarity index 85% rename from module/core/proper_path_tools/tests/inc/current_path.rs rename to module/core/pth/tests/inc/current_path.rs index 628873a346..88703f0ec6 100644 --- a/module/core/proper_path_tools/tests/inc/current_path.rs +++ b/module/core/pth/tests/inc/current_path.rs @@ -1,6 +1,7 @@ #[ allow( unused_imports ) ] use super::*; +#[ cfg( not( feature="no_std" ) ) ] use the_module:: { AbsolutePath, @@ -12,6 +13,7 @@ use the_module:: use the_module::Utf8PathBuf; #[ test ] +#[ cfg( not( feature="no_std" ) ) ] fn basic() { @@ -24,6 +26,7 @@ fn basic() println!( "absolute_path : {absolute_path:?}" ); #[ cfg( feature = "path_utf8" ) ] + #[ cfg( not( feature="no_std" ) ) ] { let cd = the_module::CurrentPath; let utf8_path : Utf8PathBuf = cd.try_into().unwrap(); diff --git a/module/core/proper_path_tools/tests/inc/mod.rs b/module/core/pth/tests/inc/mod.rs similarity index 54% rename from module/core/proper_path_tools/tests/inc/mod.rs rename to module/core/pth/tests/inc/mod.rs index 58e8721710..2c2b0d726d 100644 --- a/module/core/proper_path_tools/tests/inc/mod.rs +++ b/module/core/pth/tests/inc/mod.rs @@ -1,8 +1,15 @@ -#[allow(unused_imports)] use super::*; +use test_tools::exposed::*; + +mod as_path_test; +mod try_into_path_test; +mod try_into_cow_path_test; + +mod absolute_path_test; +mod path_join_fn_test; +mod path_join_trait_test; -mod absolute_path; mod current_path; mod path_canonicalize; mod path_change_ext; @@ -10,12 +17,11 @@ mod path_common; mod path_ext; mod path_exts; mod path_is_glob; -mod path_join; mod path_normalize; mod path_relative; mod rebase_path; mod transitive; mod without_ext; -#[cfg(feature = "path_unique_folder_name")] +#[ cfg( feature = "path_unique_folder_name" ) ] mod path_unique_folder_name; diff --git a/module/core/proper_path_tools/tests/inc/path_canonicalize.rs b/module/core/pth/tests/inc/path_canonicalize.rs similarity index 66% rename from module/core/proper_path_tools/tests/inc/path_canonicalize.rs rename to module/core/pth/tests/inc/path_canonicalize.rs index 64cd4665f2..ae94013a4c 100644 --- a/module/core/proper_path_tools/tests/inc/path_canonicalize.rs +++ b/module/core/pth/tests/inc/path_canonicalize.rs @@ -7,10 +7,10 @@ use the_module::path; fn assumptions() { - assert_eq!( PathBuf::from( "c:/src/" ).is_absolute(), true ); - assert_eq!( PathBuf::from( "/c/src/" ).is_absolute(), false ); - assert_eq!( PathBuf::from( "/c:/src/" ).is_absolute(), false ); - assert_eq!( PathBuf::from( "/c/src/" ).is_absolute(), false ); + // assert_eq!( PathBuf::from( "c:/src/" ).is_absolute(), false ); // qqq : xxx : this assumption is false on linux + // assert_eq!( PathBuf::from( "/c/src/" ).is_absolute(), true ); // qqq : xxx : this assumption is false, seems + // assert_eq!( PathBuf::from( "/c:/src/" ).is_absolute(), true ); // qqq : xxx : this assumption is false, too + // assert_eq!( PathBuf::from( "/c/src/" ).is_absolute(), true ); // qqq : xxx : this assumption is false, too } @@ -23,11 +23,11 @@ fn basic() assert_eq!( got.unwrap(), exp ); let got = path::canonicalize( PathBuf::from( "\\src" ) ); - let exp = PathBuf::from( "/src" ); + let exp = PathBuf::from( "\\src" ); assert_eq!( got.unwrap(), exp ); let got = path::canonicalize( PathBuf::from( "\\src\\" ) ); - let exp = PathBuf::from( "/src/" ); + let exp = PathBuf::from( "\\src\\" ); assert_eq!( got.unwrap(), exp ); let got = path::canonicalize( PathBuf::from( "/src" ) ); diff --git a/module/core/proper_path_tools/tests/inc/path_change_ext.rs b/module/core/pth/tests/inc/path_change_ext.rs similarity index 100% rename from module/core/proper_path_tools/tests/inc/path_change_ext.rs rename to module/core/pth/tests/inc/path_change_ext.rs diff --git a/module/core/proper_path_tools/tests/inc/path_common.rs b/module/core/pth/tests/inc/path_common.rs similarity index 100% rename from module/core/proper_path_tools/tests/inc/path_common.rs rename to module/core/pth/tests/inc/path_common.rs diff --git a/module/core/proper_path_tools/tests/inc/path_ext.rs b/module/core/pth/tests/inc/path_ext.rs similarity index 100% rename from module/core/proper_path_tools/tests/inc/path_ext.rs rename to module/core/pth/tests/inc/path_ext.rs diff --git a/module/core/proper_path_tools/tests/inc/path_exts.rs b/module/core/pth/tests/inc/path_exts.rs similarity index 100% rename from module/core/proper_path_tools/tests/inc/path_exts.rs rename to module/core/pth/tests/inc/path_exts.rs diff --git a/module/core/proper_path_tools/tests/inc/path_is_glob.rs b/module/core/pth/tests/inc/path_is_glob.rs similarity index 100% rename from module/core/proper_path_tools/tests/inc/path_is_glob.rs rename to module/core/pth/tests/inc/path_is_glob.rs diff --git a/module/core/proper_path_tools/tests/inc/path_join.rs b/module/core/pth/tests/inc/path_join_fn_test.rs similarity index 76% rename from module/core/proper_path_tools/tests/inc/path_join.rs rename to module/core/pth/tests/inc/path_join_fn_test.rs index fa526ee19d..f5a2acd005 100644 --- a/module/core/proper_path_tools/tests/inc/path_join.rs +++ b/module/core/pth/tests/inc/path_join_fn_test.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; fn join_empty() { let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "".into(), vec![ "".into() ] ); - let result = the_module::path::join_paths( paths.iter().map( |p| p.as_path() ) ); + let result = the_module::path::iter_join( paths.iter().map( |p| p.as_path() ) ); assert_eq! ( result, @@ -21,7 +21,7 @@ fn join_empty() fn join_several_empties() { let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "".into(), vec![ "".into(), "".into() ] ); - let result = the_module::path::join_paths( paths.iter().map( |p| p.as_path() ) ); + let result = the_module::path::iter_join( paths.iter().map( |p| p.as_path() ) ); assert_eq! ( result, @@ -37,7 +37,7 @@ fn join_several_empties() fn root_with_absolute() { let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "/a/b".into(), vec![ "/".into(), "/a/b".into() ] ); - let result = the_module::path::join_paths( paths.iter().map( |p| p.as_path() ) ); + let result = the_module::path::iter_join( paths.iter().map( |p| p.as_path() ) ); assert_eq! ( result, @@ -53,7 +53,7 @@ fn root_with_absolute() fn root_with_relative() { let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "/a/b".into(), vec![ "/".into(), "a/b".into() ] ); - let result = the_module::path::join_paths( paths.iter().map( |p| p.as_path() ) ); + let result = the_module::path::iter_join( paths.iter().map( |p| p.as_path() ) ); assert_eq! ( result, @@ -69,7 +69,7 @@ fn root_with_relative() fn dir_with_absolute() { let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "/a/b".into(), vec![ "/dir".into(), "/a/b".into() ] ); - let result = the_module::path::join_paths( paths.iter().map( |p| p.as_path() ) ); + let result = the_module::path::iter_join( paths.iter().map( |p| p.as_path() ) ); assert_eq! ( result, @@ -85,7 +85,7 @@ fn dir_with_absolute() fn dir_with_relative() { let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "/dir/a/b".into(), vec![ "/dir".into(), "a/b".into() ] ); - let result = the_module::path::join_paths( paths.iter().map( |p| p.as_path() ) ); + let result = the_module::path::iter_join( paths.iter().map( |p| p.as_path() ) ); assert_eq! ( result, @@ -101,7 +101,7 @@ fn dir_with_relative() fn trailed_dir_with_absolute() { let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "/a/b".into(), vec![ "/dir/".into(), "/a/b".into() ] ); - let result = the_module::path::join_paths( paths.iter().map( |p| p.as_path() ) ); + let result = the_module::path::iter_join( paths.iter().map( |p| p.as_path() ) ); assert_eq! ( result, @@ -117,7 +117,7 @@ fn trailed_dir_with_absolute() fn trailed_dir_with_relative() { let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "/dir/a/b".into(), vec![ "/dir/".into(), "a/b".into() ] ); - let result = the_module::path::join_paths( paths.iter().map( |p| p.as_path() ) ); + let result = the_module::path::iter_join( paths.iter().map( |p| p.as_path() ) ); assert_eq! ( result, @@ -133,7 +133,7 @@ fn trailed_dir_with_relative() fn dir_with_down() { let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "/a/b".into(), vec![ "/dir".into(), "../a/b".into() ] ); - let result = the_module::path::join_paths( paths.iter().map( |p| p.as_path() ) ); + let result = the_module::path::iter_join( paths.iter().map( |p| p.as_path() ) ); assert_eq! ( result, @@ -149,7 +149,7 @@ fn dir_with_down() fn trailed_dir_with_down() { let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "/dir/a/b".into(), vec![ "/dir/".into(), "../a/b".into() ] ); - let result = the_module::path::join_paths( paths.iter().map( |p| p.as_path() ) ); + let result = the_module::path::iter_join( paths.iter().map( |p| p.as_path() ) ); assert_eq! ( result, @@ -165,7 +165,7 @@ fn trailed_dir_with_down() fn dir_with_several_down() { let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "/a/b".into(), vec![ "/dir/dir2".into(), "../../a/b".into() ] ); - let result = the_module::path::join_paths( paths.iter().map( |p| p.as_path() ) ); + let result = the_module::path::iter_join( paths.iter().map( |p| p.as_path() ) ); assert_eq! ( result, @@ -181,7 +181,7 @@ fn dir_with_several_down() fn trailed_dir_with_several_down() { let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "/a/b".into(), vec![ "/dir/".into(), "../../a/b".into() ] ); - let result = the_module::path::join_paths( paths.iter().map( |p| p.as_path() ) ); + let result = the_module::path::iter_join( paths.iter().map( |p| p.as_path() ) ); assert_eq! ( result, @@ -197,7 +197,7 @@ fn trailed_dir_with_several_down() fn dir_with_several_down_go_out_of_root() { let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "/../a/b".into(), vec![ "/dir".into(), "../../a/b".into() ] ); - let result = the_module::path::join_paths( paths.iter().map( |p| p.as_path() ) ); + let result = the_module::path::iter_join( paths.iter().map( |p| p.as_path() ) ); assert_eq! ( result, @@ -213,7 +213,7 @@ fn dir_with_several_down_go_out_of_root() fn trailed_absolute_with_trailed_down() { let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "/a/b/".into(), vec![ "/a/b/".into(), "../".into() ] ); - let result = the_module::path::join_paths( paths.iter().map( |p| p.as_path() ) ); + let result = the_module::path::iter_join( paths.iter().map( |p| p.as_path() ) ); assert_eq! ( result, @@ -229,7 +229,7 @@ fn trailed_absolute_with_trailed_down() fn absolute_with_trailed_down() { let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "/a/".into(), vec![ "/a/b".into(), "../".into() ] ); - let result = the_module::path::join_paths( paths.iter().map( |p| p.as_path() ) ); + let result = the_module::path::iter_join( paths.iter().map( |p| p.as_path() ) ); assert_eq! ( result, @@ -245,7 +245,7 @@ fn absolute_with_trailed_down() fn trailed_absolute_with_down() { let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "/a/b".into(), vec![ "/a/b/".into(), "..".into() ] ); - let result = the_module::path::join_paths( paths.iter().map( |p| p.as_path() ) ); + let result = the_module::path::iter_join( paths.iter().map( |p| p.as_path() ) ); assert_eq! ( result, @@ -261,7 +261,7 @@ fn trailed_absolute_with_down() fn trailed_absolute_with_trailed_here() { let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "/a/b/".into(), vec![ "/a/b/".into(), "./".into() ] ); - let result = the_module::path::join_paths( paths.iter().map( |p| p.as_path() ) ); + let result = the_module::path::iter_join( paths.iter().map( |p| p.as_path() ) ); assert_eq! ( result, @@ -277,7 +277,7 @@ fn trailed_absolute_with_trailed_here() fn absolute_with_trailed_here() { let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "/a/b/".into(), vec![ "/a/b".into(), "./".into() ] ); - let result = the_module::path::join_paths( paths.iter().map( |p| p.as_path() ) ); + let result = the_module::path::iter_join( paths.iter().map( |p| p.as_path() ) ); assert_eq! ( result, @@ -293,7 +293,7 @@ fn absolute_with_trailed_here() fn trailed_absolute_with_here() { let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "/a/b".into(), vec![ "/a/b/".into(), ".".into() ] ); - let result = the_module::path::join_paths( paths.iter().map( |p| p.as_path() ) ); + let result = the_module::path::iter_join( paths.iter().map( |p| p.as_path() ) ); assert_eq! ( result, @@ -309,7 +309,7 @@ fn trailed_absolute_with_here() fn join_with_empty() { let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "/a/b/c".into(), vec![ "".into(), "a/b".into(), "".into(), "c".into(), "".into() ] ); - let result = the_module::path::join_paths( paths.iter().map( |p| p.as_path() ) ); + let result = the_module::path::iter_join( paths.iter().map( |p| p.as_path() ) ); assert_eq! ( result, @@ -324,8 +324,8 @@ fn join_with_empty() #[ test ] fn join_windows_os_paths() { - let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "/c/foo/bar/".into(), vec![ "c:\\".into(), "foo\\".into(), "bar\\".into() ] ); - let result = the_module::path::join_paths( paths.iter().map( |p| p.as_path() ) ); + let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "/c:/foo/bar/".into(), vec![ "c:\\".into(), "foo\\".into(), "bar\\".into() ] ); + let result = the_module::path::iter_join( paths.iter().map( |p| p.as_path() ) ); assert_eq! ( result, @@ -341,7 +341,7 @@ fn join_windows_os_paths() fn join_unix_os_paths() { let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "/baz/foo".into(), vec![ "/bar/".into(), "/baz".into(), "foo/".into(), ".".into() ] ); - let result = the_module::path::join_paths( paths.iter().map( |p| p.as_path() ) ); + let result = the_module::path::iter_join( paths.iter().map( |p| p.as_path() ) ); assert_eq! ( result, @@ -357,7 +357,7 @@ fn join_unix_os_paths() fn join_unix_os_paths_2() { let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "/baz/foo/z".into(), vec![ "/bar/".into(), "/baz".into(), "foo/".into(), ".".into(), "z".into() ] ); - let result = the_module::path::join_paths( paths.iter().map( |p| p.as_path() ) ); + let result = the_module::path::iter_join( paths.iter().map( |p| p.as_path() ) ); assert_eq! ( result, @@ -373,7 +373,7 @@ fn join_unix_os_paths_2() fn more_complicated_cases_1() { let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "/aa/bb//cc".into(), vec![ "/aa".into(), "bb//".into(), "cc".into() ] ); - let result = the_module::path::join_paths( paths.iter().map( |p| p.as_path() ) ); + let result = the_module::path::iter_join( paths.iter().map( |p| p.as_path() ) ); assert_eq! ( result, @@ -389,7 +389,7 @@ fn more_complicated_cases_1() fn more_complicated_cases_2() { let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "/bb/cc".into(), vec![ "/aa".into(), "/bb".into(), "cc".into() ] ); - let result = the_module::path::join_paths( paths.iter().map( |p| p.as_path() ) ); + let result = the_module::path::iter_join( paths.iter().map( |p| p.as_path() ) ); assert_eq! ( result, @@ -405,7 +405,7 @@ fn more_complicated_cases_2() fn more_complicated_cases_3() { let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "//aa/bb//cc//".into(), vec![ "//aa".into(), "bb//".into(), "cc//".into() ] ); - let result = the_module::path::join_paths( paths.iter().map( |p| p.as_path() ) ); + let result = the_module::path::iter_join( paths.iter().map( |p| p.as_path() ) ); assert_eq! ( result, @@ -421,7 +421,7 @@ fn more_complicated_cases_3() fn more_complicated_cases_4() { let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "/aa/bb//cc".into(), vec![ "/aa".into(), "bb//".into(), "cc".into(), ".".into() ] ); - let result = the_module::path::join_paths( paths.iter().map( |p| p.as_path() ) ); + let result = the_module::path::iter_join( paths.iter().map( |p| p.as_path() ) ); assert_eq! ( result, @@ -437,7 +437,7 @@ fn more_complicated_cases_4() fn more_complicated_cases_5() { let ( expected, paths ) : ( PathBuf, Vec< PathBuf > ) = ( "//b//d/..e".into(), vec![ "/".into(), "a".into(), "//b//".into(), "././c".into(), "../d".into(), "..e".into() ] ); - let result = the_module::path::join_paths( paths.iter().map( |p| p.as_path() ) ); + let result = the_module::path::iter_join( paths.iter().map( |p| p.as_path() ) ); assert_eq! ( result, diff --git a/module/core/pth/tests/inc/path_join_trait_test.rs b/module/core/pth/tests/inc/path_join_trait_test.rs new file mode 100644 index 0000000000..74f302166b --- /dev/null +++ b/module/core/pth/tests/inc/path_join_trait_test.rs @@ -0,0 +1,206 @@ +use super::*; +use std:: +{ + borrow::Cow, + io, + path::{ Path, PathBuf }, +}; + +#[ test ] +fn basic() -> Result< (), io::Error > +{ + use the_module::PathJoined; + use std::path::PathBuf; + + let path1 : &str = "/some"; + let path2 : String = "path".into(); + let path3 : PathBuf = "to/file".into(); + let path4 : &str = "extra"; + let path5 : String = "components".into(); + + // Test with a tuple of length 1 + let joined1 : PathBuf = ( path1, ).iter_join()?; + println!( "Joined PathBuf (1): {:?}", joined1 ); + + // Test with a tuple of length 2 + let joined2 : PathBuf = ( path1, path2.clone() ).iter_join()?; + println!( "Joined PathBuf (2): {:?}", joined2 ); + + // Test with a tuple of length 3 + let joined3 : PathBuf = ( path1, path2.clone(), path3.clone() ).iter_join()?; + println!( "Joined PathBuf (3): {:?}", joined3 ); + + // Test with a tuple of length 4 + let joined4 : PathBuf = ( path1, path2.clone(), path3.clone(), path4 ).iter_join()?; + println!( "Joined PathBuf (4): {:?}", joined4 ); + + // Test with a tuple of length 5 + let joined5 : PathBuf = ( path1, path2, path3, path4, path5 ).iter_join()?; + println!( "Joined PathBuf (5): {:?}", joined5 ); + + Ok( () ) +} + +#[ test ] +fn array_join_paths_test() -> Result< (), io::Error > +{ + use the_module::{ PathJoined, TryIntoCowPath }; + use std::path::PathBuf; + + // Define a slice of path components + let path_components : [ &str; 3 ] = [ "/some", "path", "to/file" ]; + // Join the path components into a PathBuf + let joined : PathBuf = path_components.iter_join()?; + println!( "Joined PathBuf from slice: {:?}", joined ); + let expected = PathBuf::from( "/some/path/to/file" ); + assert_eq!( joined, expected ); + + Ok( () ) +} + +#[ test ] +fn slice_join_paths_test() -> Result< (), io::Error > +{ + use the_module::{ PathJoined, TryIntoCowPath }; + use std::path::PathBuf; + + // Define a slice of path components + let path_components : [ &str; 3 ] = [ "/some", "path", "to/file" ]; + let slice : &[ &str ] = &path_components[ .. ]; + // Join the path components into a PathBuf + let joined : PathBuf = slice.iter_join()?; + println!( "Joined PathBuf from slice: {:?}", joined ); + let expected = PathBuf::from( "/some/path/to/file" ); + assert_eq!( joined, expected ); + + Ok( () ) +} + +#[ test ] +fn all_types() -> Result< (), io::Error > +{ + use std::path::Path; + use the_module::{ AbsolutePath, CanonicalPath, NativePath, CurrentPath }; + use the_module::{ PathJoined, AsPath, TryIntoPath }; + + // AbsolutePath and CurrentPath + { + let absolute_path = AbsolutePath::try_from( "/absolute/path" ).unwrap(); + let current_path = CurrentPath; + let joined = ( absolute_path.clone(), current_path ).iter_join()?; + let expected = current_path.try_into_path()?; + println!( "Joined PathBuf: {:?}", joined ); + assert_eq!( joined, expected ); + } + + // // CurrentPath and AbsolutePath + // { + // let absolute_path = AbsolutePath::try_from( "/absolute/path" ).unwrap(); + // let current_path = CurrentPath; + // let joined = ( current_path, absolute_path.clone() ).iter_join()?; + // let expected = absolute_path.as_path().to_path_buf(); + // println!( "Joined PathBuf: {:?}", joined ); + // assert_eq!( joined, expected ); + // } + // // qqq : qqq2 : for Denys : bad + + // AbsolutePath and Component + { + let absolute_path = AbsolutePath::try_from( "/absolute/path" ).unwrap(); + let component = Path::new( "/component/path" ).components().next().unwrap(); + println!( "component : {component:?}" ); + let joined = ( absolute_path, component ).iter_join()?; + let expected = component.as_path(); + println!( "Joined PathBuf: {:?}", joined ); + assert_eq!( joined, expected ); + } + + // AbsolutePath and &str + { + let absolute_path = AbsolutePath::try_from( "/absolute/path" ).unwrap(); + let path_str : &str = "additional/str"; + let joined = ( absolute_path, path_str ).iter_join()?; + let expected = PathBuf::from( "/absolute/path/additional/str" ); + println!( "Joined PathBuf: {:?}", joined ); + assert_eq!( joined, expected ); + } + + // AbsolutePath and NativePath + { + let absolute_path = AbsolutePath::try_from( "/absolute/path" ).unwrap(); + let native_path = NativePath::try_from( PathBuf::from( "/native/path" ) ).unwrap(); + let joined = ( absolute_path, native_path ).iter_join()?; + let expected = PathBuf::from( "/native/path" ); + println!( "Joined PathBuf: {:?}", joined ); + assert_eq!( joined, expected ); + } + + // AbsolutePath and CanonicalPath + { + let absolute_path = AbsolutePath::try_from( "/absolute/path" ).unwrap(); + let canonical_path = CanonicalPath::try_from( "/canonical/path" ).unwrap(); + let joined = ( absolute_path, canonical_path ).iter_join()?; + let expected = PathBuf::from( "/canonical/path" ); + println!( "Joined PathBuf: {:?}", joined ); + assert_eq!( joined, expected ); + } + + // NativePath and CurrentPath + { + let native_path = NativePath::try_from( PathBuf::from( "/native/path" ) ).unwrap(); + let current_path = CurrentPath; + let joined = ( native_path, current_path ).iter_join()?; + let expected = current_path.try_into_path()?; + println!( "Joined PathBuf: {:?}", joined ); + assert_eq!( joined, expected ); + } + + // CanonicalPath and Component + { + let canonical_path = CanonicalPath::try_from( "/canonical/path" ).unwrap(); + let component = Path::new( "/component/path" ).components().next().unwrap(); + println!( "component : {component:?}" ); + let joined = ( canonical_path, component ).iter_join()?; + let expected = component.as_path(); + // let expected = PathBuf::from( "/canonical/component" ); + println!( "Joined PathBuf: {:?}", joined ); + assert_eq!( joined, expected ); + } + + Ok( () ) +} + +#[ test ] +fn join_function_test() -> Result< (), io::Error > +{ + use the_module::path; + use std::path::PathBuf; + + // Test joining a tuple of path components + let path1 : &str = "/some"; + let path2 : String = "path".into(); + let path3 : PathBuf = "to/file".into(); + + // Use the join function to join the path components + let joined : PathBuf = path::join( ( path1, path2.clone(), path3.clone() ) )?; + println!( "Joined PathBuf: {:?}", joined ); + // Verify the expected outcome + let expected = PathBuf::from( "/some/path/to/file" ); + assert_eq!( joined, expected ); + + // Test joining a tuple of length 2 + let joined : PathBuf = path::join( ( path1, path2.clone() ) )?; + println!( "Joined PathBuf (2 components): {:?}", joined ); + // Verify the expected outcome + let expected = PathBuf::from( "/some/path" ); + assert_eq!( joined, expected ); + + // Test joining a tuple of length 1 + let joined : PathBuf = path::join( ( path1, ) )?; + println!( "Joined PathBuf (1 component): {:?}", joined ); + // Verify the expected outcome + let expected = PathBuf::from( "/some" ); + assert_eq!( joined, expected ); + + Ok( () ) +} \ No newline at end of file diff --git a/module/core/proper_path_tools/tests/inc/path_normalize.rs b/module/core/pth/tests/inc/path_normalize.rs similarity index 100% rename from module/core/proper_path_tools/tests/inc/path_normalize.rs rename to module/core/pth/tests/inc/path_normalize.rs diff --git a/module/core/proper_path_tools/tests/inc/path_relative.rs b/module/core/pth/tests/inc/path_relative.rs similarity index 100% rename from module/core/proper_path_tools/tests/inc/path_relative.rs rename to module/core/pth/tests/inc/path_relative.rs diff --git a/module/core/proper_path_tools/tests/inc/path_unique_folder_name.rs b/module/core/pth/tests/inc/path_unique_folder_name.rs similarity index 100% rename from module/core/proper_path_tools/tests/inc/path_unique_folder_name.rs rename to module/core/pth/tests/inc/path_unique_folder_name.rs diff --git a/module/core/proper_path_tools/tests/inc/rebase_path.rs b/module/core/pth/tests/inc/rebase_path.rs similarity index 100% rename from module/core/proper_path_tools/tests/inc/rebase_path.rs rename to module/core/pth/tests/inc/rebase_path.rs diff --git a/module/core/proper_path_tools/tests/inc/transitive.rs b/module/core/pth/tests/inc/transitive.rs similarity index 96% rename from module/core/proper_path_tools/tests/inc/transitive.rs rename to module/core/pth/tests/inc/transitive.rs index e0b2da7acc..8224024e5b 100644 --- a/module/core/proper_path_tools/tests/inc/transitive.rs +++ b/module/core/pth/tests/inc/transitive.rs @@ -4,7 +4,7 @@ use super::*; #[ test ] fn basic_from() { - use proper_path_tools::TransitiveTryFrom; + use pth::TransitiveTryFrom; use std::convert::TryFrom; struct InitialType; @@ -42,7 +42,7 @@ fn basic_from() #[ test ] fn test_transitive_try_into() { - use proper_path_tools::TransitiveTryInto; + use pth::TransitiveTryInto; // Define NewType1 wrapping a String #[ derive( Debug, PartialEq ) ] diff --git a/module/core/pth/tests/inc/try_into_cow_path_test.rs b/module/core/pth/tests/inc/try_into_cow_path_test.rs new file mode 100644 index 0000000000..73a3910c52 --- /dev/null +++ b/module/core/pth/tests/inc/try_into_cow_path_test.rs @@ -0,0 +1,124 @@ +use super::*; + +#[ test ] +fn try_into_cow_path_test() +{ + use std:: + { + borrow::Cow, + path::{ Component, Path, PathBuf }, + }; + #[ cfg( feature = "path_utf8" ) ] + use the_module::{ Utf8Path, Utf8PathBuf }; + use the_module:: + { + TryIntoCowPath, AbsolutePath, CanonicalPath, NativePath, CurrentPath, + }; + + // Test with &str + let path_str : &str = "/some/path"; + let cow_path : Cow< '_ , Path > = TryIntoCowPath::try_into_cow_path( path_str ).unwrap(); + println!( "Cow from &str: {:?}", cow_path ); + + // Test with &String + let string_path : String = String::from( "/another/path" ); + let cow_path : Cow< '_ , Path > = TryIntoCowPath::try_into_cow_path( &string_path ).unwrap(); + println!( "Cow from &String: {:?}", cow_path ); + + // Test with String + let cow_path : Cow< '_ , Path > = TryIntoCowPath::try_into_cow_path( string_path.clone() ).unwrap(); + println!( "Cow from String: {:?}", cow_path ); + + // Test with &Path + let path : &Path = Path::new( "/yet/another/path" ); + let cow_path : Cow< '_ , Path > = TryIntoCowPath::try_into_cow_path( path ).unwrap(); + println!( "Cow from &Path: {:?}", cow_path ); + + // Test with &PathBuf + let path_buf : PathBuf = PathBuf::from( "/yet/another/path" ); + let cow_path : Cow< '_ , Path > = TryIntoCowPath::try_into_cow_path( &path_buf ).unwrap(); + println!( "Cow from &PathBuf: {:?}", cow_path ); + + // Test with PathBuf + let cow_path : Cow< '_ , Path > = TryIntoCowPath::try_into_cow_path( path_buf.clone() ).unwrap(); + println!( "Cow from PathBuf: {:?}", cow_path ); + + // Test with &AbsolutePath + let absolute_path : AbsolutePath = AbsolutePath::try_from( "/absolute/path" ).unwrap(); + let cow_path : Cow< '_ , Path > = TryIntoCowPath::try_into_cow_path( &absolute_path ).unwrap(); + println!( "Cow from &AbsolutePath: {:?}", cow_path ); + + // Test with AbsolutePath + let cow_path : Cow< '_ , Path > = TryIntoCowPath::try_into_cow_path( absolute_path.clone() ).unwrap(); + println!( "Cow from AbsolutePath: {:?}", cow_path ); + + // Test with &CanonicalPath + let canonical_path = CanonicalPath::try_from( "/canonical/path" ).unwrap(); + let cow_path : Cow< '_ , Path > = TryIntoCowPath::try_into_cow_path( &canonical_path ).unwrap(); + println!( "Cow from &CanonicalPath: {:?}", cow_path ); + + // Test with CanonicalPath + let cow_path : Cow< '_ , Path > = TryIntoCowPath::try_into_cow_path( canonical_path.clone() ).unwrap(); + println!( "Cow from CanonicalPath: {:?}", cow_path ); + + // Test with &NativePath + let native_path = NativePath::try_from( PathBuf::from( "/native/path" ) ).unwrap(); + let cow_path : Cow< '_ , Path > = TryIntoCowPath::try_into_cow_path( &native_path ).unwrap(); + println!( "Cow from &NativePath: {:?}", cow_path ); + + // Test with NativePath + let cow_path : Cow< '_ , Path > = TryIntoCowPath::try_into_cow_path( native_path.clone() ).unwrap(); + println!( "Cow from NativePath: {:?}", cow_path ); + + // Test with &CurrentPath + let current_path = CurrentPath; + let cow_path : Cow< '_ , Path > = TryIntoCowPath::try_into_cow_path( ¤t_path ).unwrap(); + println!( "Cow from &CurrentPath: {:?}", cow_path ); + assert!( cow_path.to_string_lossy().len() > 1 ); + + // Test with CurrentPath + let cow_path : Cow< '_ , Path > = TryIntoCowPath::try_into_cow_path( current_path ).unwrap(); + println!( "Cow from CurrentPath: {:?}", cow_path ); + assert!( cow_path.to_string_lossy().len() > 1 ); + + // Test with &Component + let root_component : Component< '_ > = Component::RootDir; + let cow_path : Cow< '_ , Path > = TryIntoCowPath::try_into_cow_path( &root_component ).unwrap(); + println!( "Cow from &Component: {:?}", cow_path ); + assert!( cow_path.to_string_lossy().len() >= 1 ); + + // Test with Component + let cow_path : Cow< '_ , Path > = TryIntoCowPath::try_into_cow_path( root_component ).unwrap(); + println!( "Cow from Component: {:?}", cow_path ); + assert!( cow_path.to_string_lossy().len() >= 1 ); + + // Test with Component + let path = Path::new( "/component/path" ); + for component in path.components() + { + let cow_path : Cow< '_ , Path > = TryIntoCowPath::try_into_cow_path( component ).unwrap(); + println!( "Cow from Component: {:?}", cow_path ); + assert!( cow_path.to_string_lossy().len() >= 1 ); + } + + #[ cfg( feature = "path_utf8" ) ] + { + // Test with &Utf8Path + let utf8_path = Utf8Path::new( "/utf8/path" ); + let cow_path : Cow< '_ , Path > = TryIntoCowPath::try_into_cow_path( &utf8_path ).unwrap(); + println!( "Cow from &Utf8Path: {:?}", cow_path ); + + // Test with Utf8Path + let cow_path : Cow< '_ , Path > = TryIntoCowPath::try_into_cow_path( utf8_path ).unwrap(); + println!( "Cow from Utf8Path: {:?}", cow_path ); + + // Test with &Utf8PathBuf + let utf8_path_buf = Utf8PathBuf::from( "/utf8/pathbuf" ); + let cow_path : Cow< '_ , Path > = TryIntoCowPath::try_into_cow_path( &utf8_path_buf ).unwrap(); + println!( "Cow from &Utf8PathBuf: {:?}", cow_path ); + + // Test with Utf8PathBuf + let cow_path : Cow< '_ , Path > = TryIntoCowPath::try_into_cow_path( utf8_path_buf.clone() ).unwrap(); + println!( "Cow from Utf8PathBuf: {:?}", cow_path ); + } +} diff --git a/module/core/pth/tests/inc/try_into_path_test.rs b/module/core/pth/tests/inc/try_into_path_test.rs new file mode 100644 index 0000000000..b7623d5c60 --- /dev/null +++ b/module/core/pth/tests/inc/try_into_path_test.rs @@ -0,0 +1,117 @@ +use super::*; + +#[ test ] +fn try_into_path_test() +{ + use std::path::{ Component, Path, PathBuf }; + #[ cfg( feature = "path_utf8" ) ] + use the_module::{ Utf8Path, Utf8PathBuf }; + use the_module::{ TryIntoPath, AbsolutePath, CanonicalPath, NativePath, CurrentPath }; + + // Test with &str + let path_str : &str = "/some/path"; + let path_buf : PathBuf = TryIntoPath::try_into_path( path_str ).unwrap(); + println!( "PathBuf from &str: {:?}", path_buf ); + + // Test with &String + let string_path : String = String::from( "/another/path" ); + let path_buf : PathBuf = TryIntoPath::try_into_path( &string_path ).unwrap(); + println!( "PathBuf from &String: {:?}", path_buf ); + + // Test with String + let path_buf : PathBuf = TryIntoPath::try_into_path( string_path.clone() ).unwrap(); + println!( "PathBuf from String: {:?}", path_buf ); + + // Test with &Path + let path : &Path = Path::new( "/yet/another/path" ); + let path_buf : PathBuf = TryIntoPath::try_into_path( path ).unwrap(); + println!( "PathBuf from &Path: {:?}", path_buf ); + + // Test with &PathBuf + let path_buf_instance : PathBuf = PathBuf::from( "/yet/another/path" ); + let path_buf : PathBuf = TryIntoPath::try_into_path( &path_buf_instance ).unwrap(); + println!( "PathBuf from &PathBuf: {:?}", path_buf ); + + // Test with PathBuf + let path_buf : PathBuf = TryIntoPath::try_into_path( path_buf_instance.clone() ).unwrap(); + println!( "PathBuf from PathBuf: {:?}", path_buf ); + + // Test with &AbsolutePath + let absolute_path : AbsolutePath = AbsolutePath::try_from( "/absolute/path" ).unwrap(); + let path_buf : PathBuf = TryIntoPath::try_into_path( &absolute_path ).unwrap(); + println!( "PathBuf from &AbsolutePath: {:?}", path_buf ); + + // Test with AbsolutePath + let path_buf : PathBuf = TryIntoPath::try_into_path( absolute_path.clone() ).unwrap(); + println!( "PathBuf from AbsolutePath: {:?}", path_buf ); + + // Test with &CanonicalPath + let canonical_path = CanonicalPath::try_from( "/canonical/path" ).unwrap(); + let path_buf : PathBuf = TryIntoPath::try_into_path( &canonical_path ).unwrap(); + println!( "PathBuf from &CanonicalPath: {:?}", path_buf ); + + // Test with CanonicalPath + let path_buf : PathBuf = TryIntoPath::try_into_path( canonical_path.clone() ).unwrap(); + println!( "PathBuf from CanonicalPath: {:?}", path_buf ); + + // Test with &NativePath + let native_path = NativePath::try_from( PathBuf::from( "/native/path" ) ).unwrap(); + let path_buf : PathBuf = TryIntoPath::try_into_path( &native_path ).unwrap(); + println!( "PathBuf from &NativePath: {:?}", path_buf ); + + // Test with NativePath + let path_buf : PathBuf = TryIntoPath::try_into_path( native_path.clone() ).unwrap(); + println!( "PathBuf from NativePath: {:?}", path_buf ); + + // Test with &CurrentPath + let current_path = CurrentPath; + let path_buf : PathBuf = TryIntoPath::try_into_path( ¤t_path ).unwrap(); + println!( "PathBuf from &CurrentPath: {:?}", path_buf ); + assert!( path_buf.to_string_lossy().len() > 1 ); + + // Test with CurrentPath + let path_buf : PathBuf = TryIntoPath::try_into_path( current_path ).unwrap(); + println!( "PathBuf from CurrentPath: {:?}", path_buf ); + assert!( path_buf.to_string_lossy().len() > 1 ); + + // Test with &Component + let root_component : Component< '_ > = Component::RootDir; + let path_buf : PathBuf = TryIntoPath::try_into_path( &root_component ).unwrap(); + println!( "PathBuf from &Component: {:?}", path_buf ); + assert!( path_buf.to_string_lossy().len() >= 1 ); + + // Test with Component + let path_buf : PathBuf = TryIntoPath::try_into_path( root_component ).unwrap(); + println!( "PathBuf from Component: {:?}", path_buf ); + assert!( path_buf.to_string_lossy().len() >= 1 ); + + // Test with Component + let path = Path::new( "/component/path" ); + for component in path.components() + { + let path_buf : PathBuf = TryIntoPath::try_into_path( component ).unwrap(); + println!( "PathBuf from Component: {:?}", path_buf ); + assert!( path_buf.to_string_lossy().len() >= 1 ); + } + + #[ cfg( feature = "path_utf8" ) ] + { + // Test with &Utf8Path + let utf8_path = Utf8Path::new( "/utf8/path" ); + let path_buf : PathBuf = TryIntoPath::try_into_path( &utf8_path ).unwrap(); + println!( "PathBuf from &Utf8Path: {:?}", path_buf ); + + // Test with Utf8Path + let path_buf : PathBuf = TryIntoPath::try_into_path( utf8_path ).unwrap(); + println!( "PathBuf from Utf8Path: {:?}", path_buf ); + + // Test with &Utf8PathBuf + let utf8_path_buf = Utf8PathBuf::from( "/utf8/pathbuf" ); + let path_buf : PathBuf = TryIntoPath::try_into_path( &utf8_path_buf ).unwrap(); + println!( "PathBuf from &Utf8PathBuf: {:?}", path_buf ); + + // Test with Utf8PathBuf + let path_buf : PathBuf = TryIntoPath::try_into_path( utf8_path_buf.clone() ).unwrap(); + println!( "PathBuf from Utf8PathBuf: {:?}", path_buf ); + } +} diff --git a/module/core/proper_path_tools/tests/inc/without_ext.rs b/module/core/pth/tests/inc/without_ext.rs similarity index 100% rename from module/core/proper_path_tools/tests/inc/without_ext.rs rename to module/core/pth/tests/inc/without_ext.rs diff --git a/module/core/pth/tests/smoke_test.rs b/module/core/pth/tests/smoke_test.rs new file mode 100644 index 0000000000..c9b1b4daae --- /dev/null +++ b/module/core/pth/tests/smoke_test.rs @@ -0,0 +1,13 @@ +//! Smoke testing of the package. + +#[ 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/core/pth/tests/tests.rs b/module/core/pth/tests/tests.rs new file mode 100644 index 0000000000..7ec129f839 --- /dev/null +++ b/module/core/pth/tests/tests.rs @@ -0,0 +1,9 @@ +//! All tests. +#![ allow( unused_imports ) ] + +include!( "../../../../module/step/meta/src/module/terminal.rs" ); + +use pth as the_module; + +#[ cfg( feature = "enabled" ) ] +mod inc; diff --git a/module/core/reflect_tools/Cargo.toml b/module/core/reflect_tools/Cargo.toml index 80718672c4..a496466509 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.3.0" +version = "0.6.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/reflect_tools/License b/module/core/reflect_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/core/reflect_tools/License +++ b/module/core/reflect_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/core/reflect_tools/Readme.md index 624cab03ac..a669ce27d4 100644 --- a/module/core/reflect_tools/Readme.md +++ b/module/core/reflect_tools/Readme.md @@ -1,7 +1,7 @@ # Module :: reflect_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_reflect_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_reflect_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/reflect_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/reflect_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Freflect_tools%2Fexamples%2Freflect_tools_trivial.rs,RUN_POSTFIX=--example%20reflect_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_reflect_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_reflect_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/reflect_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/reflect_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Freflect_tools%2Fexamples%2Freflect_tools_trivial.rs,RUN_POSTFIX=--example%20module%2Fcore%2Freflect_tools%2Fexamples%2Freflect_tools_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) ### Basic use-case diff --git a/module/core/reflect_tools/src/reflect.rs b/module/core/reflect_tools/src/reflect.rs index 0cde174ac9..3d363b1c09 100644 --- a/module/core/reflect_tools/src/reflect.rs +++ b/module/core/reflect_tools/src/reflect.rs @@ -52,7 +52,7 @@ // qqq : make the example working. use tests for inpsisrations -/// Internal namespace. +/// Define a private namespace for all its items. mod private { } diff --git a/module/core/reflect_tools/src/reflect/axiomatic.rs b/module/core/reflect_tools/src/reflect/axiomatic.rs index dcc6f044c8..2a092dfd0b 100644 --- a/module/core/reflect_tools/src/reflect/axiomatic.rs +++ b/module/core/reflect_tools/src/reflect/axiomatic.rs @@ -4,7 +4,7 @@ use super::*; -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use super::*; diff --git a/module/core/reflect_tools/src/reflect/entity_array.rs b/module/core/reflect_tools/src/reflect/entity_array.rs index afe3363b7c..3a9e592116 100644 --- a/module/core/reflect_tools/src/reflect/entity_array.rs +++ b/module/core/reflect_tools/src/reflect/entity_array.rs @@ -4,7 +4,7 @@ use super::*; -/// Internal namespace. +/// Define a private namespace for all its items. pub mod private { use super::*; diff --git a/module/core/reflect_tools/src/reflect/entity_hashmap.rs b/module/core/reflect_tools/src/reflect/entity_hashmap.rs index 97d9a821c9..21f7a04f35 100644 --- a/module/core/reflect_tools/src/reflect/entity_hashmap.rs +++ b/module/core/reflect_tools/src/reflect/entity_hashmap.rs @@ -4,7 +4,7 @@ use super::*; -/// Internal namespace. +/// Define a private namespace for all its items. pub mod private { use super::*; diff --git a/module/core/reflect_tools/src/reflect/entity_hashset.rs b/module/core/reflect_tools/src/reflect/entity_hashset.rs index ba48a7a189..84803f0c77 100644 --- a/module/core/reflect_tools/src/reflect/entity_hashset.rs +++ b/module/core/reflect_tools/src/reflect/entity_hashset.rs @@ -4,7 +4,7 @@ use super::*; -/// Internal namespace. +/// Define a private namespace for all its items. pub mod private { use super::*; diff --git a/module/core/reflect_tools/src/reflect/entity_slice.rs b/module/core/reflect_tools/src/reflect/entity_slice.rs index e396f8bcf9..1584c874f2 100644 --- a/module/core/reflect_tools/src/reflect/entity_slice.rs +++ b/module/core/reflect_tools/src/reflect/entity_slice.rs @@ -4,7 +4,7 @@ use super::*; -/// Internal namespace. +/// Define a private namespace for all its items. pub mod private { use super::*; diff --git a/module/core/reflect_tools/src/reflect/entity_vec.rs b/module/core/reflect_tools/src/reflect/entity_vec.rs index c559136729..ec74a41b00 100644 --- a/module/core/reflect_tools/src/reflect/entity_vec.rs +++ b/module/core/reflect_tools/src/reflect/entity_vec.rs @@ -4,7 +4,7 @@ use super::*; -/// Internal namespace. +/// Define a private namespace for all its items. pub mod private { use super::*; diff --git a/module/core/reflect_tools/src/reflect/fields.rs b/module/core/reflect_tools/src/reflect/fields.rs index 428c95237b..811b9835d2 100644 --- a/module/core/reflect_tools/src/reflect/fields.rs +++ b/module/core/reflect_tools/src/reflect/fields.rs @@ -2,7 +2,7 @@ //! Iterator over fields. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { @@ -112,6 +112,10 @@ mod private mod vec; mod hmap; mod bmap; +mod hset; +mod bset; +mod deque; +mod llist; #[ doc( inline ) ] #[ allow( unused_imports ) ] diff --git a/module/core/reflect_tools/src/reflect/fields/bset.rs b/module/core/reflect_tools/src/reflect/fields/bset.rs new file mode 100644 index 0000000000..e68d4d7e4b --- /dev/null +++ b/module/core/reflect_tools/src/reflect/fields/bset.rs @@ -0,0 +1,65 @@ +//! +//! Implement fields for BTreeSet. +//! + +use crate::*; +use std::borrow::Cow; +use collection_tools::BTreeSet; + +impl< V, Borrowed > Fields< usize, &'_ Borrowed > for BTreeSet< V > +where + Borrowed : std::borrow::ToOwned + 'static + ?Sized, + V : std::borrow::Borrow< Borrowed >, +{ + + type Key< 'k > = usize + where Self : 'k, usize : 'k; + + type Val< 'v > = &'v Borrowed + where Self : 'v, V : 'v; + + fn fields< 's >( &'s self ) -> impl IteratorTrait< Item = ( Self::Key< 's >, Self::Val< 's > ) > + { + self.iter().enumerate().map( move | ( key, val ) | ( key, val.borrow() ) ) + } + +} + +impl< V, Borrowed > Fields< usize, Option< Cow< '_, Borrowed > > > for BTreeSet< V > +where + Borrowed : std::borrow::ToOwned + 'static + ?Sized, + V : std::borrow::Borrow< Borrowed >, +{ + + type Key< 'k > = usize + where Self : 'k, usize : 'k; + + type Val< 'v > = Option< Cow< 'v, Borrowed > > + where Self : 'v; + + fn fields< 's >( &'s self ) -> impl IteratorTrait< Item = ( Self::Key< 's >, Self::Val< 's > ) > + { + self.iter().enumerate().map( move | ( key, val ) | ( key, Some( Cow::Borrowed( val.borrow() ) ) ) ) + } + +} + +impl< V, Borrowed, Marker > Fields< usize, OptionalCow< '_, Borrowed, Marker > > for BTreeSet< V > +where + Borrowed : std::borrow::ToOwned + 'static + ?Sized, + V : std::borrow::Borrow< Borrowed >, + Marker : Clone + Copy + 'static, +{ + + type Key< 'k > = usize + where Self : 'k, usize : 'k; + + type Val< 'v > = OptionalCow< 'v, Borrowed, Marker > + where Self : 'v; + + fn fields< 's >( &'s self ) -> impl IteratorTrait< Item = ( Self::Key< 's >, Self::Val< 's > ) > + { + self.iter().enumerate().map( move | ( key, val ) | ( key, OptionalCow::from( val.borrow() ) ) ) + } + +} diff --git a/module/core/reflect_tools/src/reflect/fields/deque.rs b/module/core/reflect_tools/src/reflect/fields/deque.rs new file mode 100644 index 0000000000..734255ad1a --- /dev/null +++ b/module/core/reflect_tools/src/reflect/fields/deque.rs @@ -0,0 +1,65 @@ +//! +//! Implement fields for Deque. +//! + +use crate::*; +use std::borrow::Cow; +use collection_tools::VecDeque; + +impl< V, Borrowed > Fields< usize, &'_ Borrowed > for VecDeque< V > +where + Borrowed : std::borrow::ToOwned + 'static + ?Sized, + V : std::borrow::Borrow< Borrowed >, +{ + + type Key< 'k > = usize + where Self : 'k, usize : 'k; + + type Val< 'v > = &'v Borrowed + where Self : 'v, V : 'v; + + fn fields< 's >( &'s self ) -> impl IteratorTrait< Item = ( Self::Key< 's >, Self::Val< 's > ) > + { + self.iter().enumerate().map( move | ( key, val ) | ( key, val.borrow() ) ) + } + +} + +impl< V, Borrowed > Fields< usize, Option< Cow< '_, Borrowed > > > for VecDeque< V > +where + Borrowed : std::borrow::ToOwned + 'static + ?Sized, + V : std::borrow::Borrow< Borrowed >, +{ + + type Key< 'k > = usize + where Self : 'k, usize : 'k; + + type Val< 'v > = Option< Cow< 'v, Borrowed > > + where Self : 'v; + + fn fields< 's >( &'s self ) -> impl IteratorTrait< Item = ( Self::Key< 's >, Self::Val< 's > ) > + { + self.iter().enumerate().map( move | ( key, val ) | ( key, Some( Cow::Borrowed( val.borrow() ) ) ) ) + } + +} + +impl< V, Borrowed, Marker > Fields< usize, OptionalCow< '_, Borrowed, Marker > > for VecDeque< V > +where + Borrowed : std::borrow::ToOwned + 'static + ?Sized, + V : std::borrow::Borrow< Borrowed >, + Marker : Clone + Copy + 'static, +{ + + type Key< 'k > = usize + where Self : 'k, usize : 'k; + + type Val< 'v > = OptionalCow< 'v, Borrowed, Marker > + where Self : 'v; + + fn fields< 's >( &'s self ) -> impl IteratorTrait< Item = ( Self::Key< 's >, Self::Val< 's > ) > + { + self.iter().enumerate().map( move | ( key, val ) | ( key, OptionalCow::from( val.borrow() ) ) ) + } + +} diff --git a/module/core/reflect_tools/src/reflect/fields/hset.rs b/module/core/reflect_tools/src/reflect/fields/hset.rs new file mode 100644 index 0000000000..cfc01be06e --- /dev/null +++ b/module/core/reflect_tools/src/reflect/fields/hset.rs @@ -0,0 +1,65 @@ +//! +//! Implement fields for HashSet. +//! + +use crate::*; +use std::borrow::Cow; +use std::collections::HashSet; + +impl< V, Borrowed > Fields< usize, &'_ Borrowed > for HashSet< V > +where + Borrowed : std::borrow::ToOwned + 'static + ?Sized, + V : std::borrow::Borrow< Borrowed >, +{ + + type Key< 'k > = usize + where Self : 'k, usize : 'k; + + type Val< 'v > = &'v Borrowed + where Self : 'v, V : 'v; + + fn fields< 's >( &'s self ) -> impl IteratorTrait< Item = ( Self::Key< 's >, Self::Val< 's > ) > + { + self.iter().enumerate().map( move | ( key, val ) | ( key, val.borrow() ) ) + } + +} + +impl< V, Borrowed > Fields< usize, Option< Cow< '_, Borrowed > > > for HashSet< V > +where + Borrowed : std::borrow::ToOwned + 'static + ?Sized, + V : std::borrow::Borrow< Borrowed >, +{ + + type Key< 'k > = usize + where Self : 'k, usize : 'k; + + type Val< 'v > = Option< Cow< 'v, Borrowed > > + where Self : 'v; + + fn fields< 's >( &'s self ) -> impl IteratorTrait< Item = ( Self::Key< 's >, Self::Val< 's > ) > + { + self.iter().enumerate().map( move | ( key, val ) | ( key, Some( Cow::Borrowed( val.borrow() ) ) ) ) + } + +} + +impl< V, Borrowed, Marker > Fields< usize, OptionalCow< '_, Borrowed, Marker > > for HashSet< V > +where + Borrowed : std::borrow::ToOwned + 'static + ?Sized, + V : std::borrow::Borrow< Borrowed >, + Marker : Clone + Copy + 'static, +{ + + type Key< 'k > = usize + where Self : 'k, usize : 'k; + + type Val< 'v > = OptionalCow< 'v, Borrowed, Marker > + where Self : 'v; + + fn fields< 's >( &'s self ) -> impl IteratorTrait< Item = ( Self::Key< 's >, Self::Val< 's > ) > + { + self.iter().enumerate().map( move | ( key, val ) | ( key, OptionalCow::from( val.borrow() ) ) ) + } + +} diff --git a/module/core/reflect_tools/src/reflect/fields/llist.rs b/module/core/reflect_tools/src/reflect/fields/llist.rs new file mode 100644 index 0000000000..40ca1ced98 --- /dev/null +++ b/module/core/reflect_tools/src/reflect/fields/llist.rs @@ -0,0 +1,65 @@ +//! +//! Implement fields for LinkedList. +//! + +use crate::*; +use std::borrow::Cow; +use collection_tools::LinkedList; + +impl< V, Borrowed > Fields< usize, &'_ Borrowed > for LinkedList< V > +where + Borrowed : std::borrow::ToOwned + 'static + ?Sized, + V : std::borrow::Borrow< Borrowed >, +{ + + type Key< 'k > = usize + where Self : 'k, usize : 'k; + + type Val< 'v > = &'v Borrowed + where Self : 'v, V : 'v; + + fn fields< 's >( &'s self ) -> impl IteratorTrait< Item = ( Self::Key< 's >, Self::Val< 's > ) > + { + self.iter().enumerate().map( move | ( key, val ) | ( key, val.borrow() ) ) + } + +} + +impl< V, Borrowed > Fields< usize, Option< Cow< '_, Borrowed > > > for LinkedList< V > +where + Borrowed : std::borrow::ToOwned + 'static + ?Sized, + V : std::borrow::Borrow< Borrowed >, +{ + + type Key< 'k > = usize + where Self : 'k, usize : 'k; + + type Val< 'v > = Option< Cow< 'v, Borrowed > > + where Self : 'v; + + fn fields< 's >( &'s self ) -> impl IteratorTrait< Item = ( Self::Key< 's >, Self::Val< 's > ) > + { + self.iter().enumerate().map( move | ( key, val ) | ( key, Some( Cow::Borrowed( val.borrow() ) ) ) ) + } + +} + +impl< V, Borrowed, Marker > Fields< usize, OptionalCow< '_, Borrowed, Marker > > for LinkedList< V > +where + Borrowed : std::borrow::ToOwned + 'static + ?Sized, + V : std::borrow::Borrow< Borrowed >, + Marker : Clone + Copy + 'static, +{ + + type Key< 'k > = usize + where Self : 'k, usize : 'k; + + type Val< 'v > = OptionalCow< 'v, Borrowed, Marker > + where Self : 'v; + + fn fields< 's >( &'s self ) -> impl IteratorTrait< Item = ( Self::Key< 's >, Self::Val< 's > ) > + { + self.iter().enumerate().map( move | ( key, val ) | ( key, OptionalCow::from( val.borrow() ) ) ) + } + +} diff --git a/module/core/reflect_tools/src/reflect/fields/vec.rs b/module/core/reflect_tools/src/reflect/fields/vec.rs index bdf3977601..0a18259738 100644 --- a/module/core/reflect_tools/src/reflect/fields/vec.rs +++ b/module/core/reflect_tools/src/reflect/fields/vec.rs @@ -9,6 +9,7 @@ use collection_tools::Vec; impl< V, Borrowed > Fields< usize, &'_ Borrowed > for Vec< V > where Borrowed : std::borrow::ToOwned + 'static + ?Sized, + // Borrowed : ?Sized + 'static, V : std::borrow::Borrow< Borrowed >, { @@ -28,6 +29,7 @@ where impl< V, Borrowed > Fields< usize, Option< Cow< '_, Borrowed > > > for Vec< V > where Borrowed : std::borrow::ToOwned + 'static + ?Sized, + // Borrowed : ?Sized + 'static, V : std::borrow::Borrow< Borrowed >, { @@ -39,6 +41,7 @@ where fn fields< 's >( &'s self ) -> impl IteratorTrait< Item = ( Self::Key< 's >, Self::Val< 's > ) > { + // self.iter().enumerate().map( move | ( key, val ) | ( key, Some( Cow::Borrowed( &val ) ) ) ) self.iter().enumerate().map( move | ( key, val ) | ( key, Some( Cow::Borrowed( val.borrow() ) ) ) ) } @@ -47,6 +50,7 @@ where impl< V, Borrowed, Marker > Fields< usize, OptionalCow< '_, Borrowed, Marker > > for Vec< V > where Borrowed : std::borrow::ToOwned + 'static + ?Sized, + // Borrowed : ?Sized + 'static, V : std::borrow::Borrow< Borrowed >, Marker : Clone + Copy + 'static, { diff --git a/module/core/reflect_tools/src/reflect/primitive.rs b/module/core/reflect_tools/src/reflect/primitive.rs index 986291afb8..23ce9a125e 100644 --- a/module/core/reflect_tools/src/reflect/primitive.rs +++ b/module/core/reflect_tools/src/reflect/primitive.rs @@ -2,7 +2,7 @@ //! Define primitive and data types. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { diff --git a/module/core/reflect_tools/src/reflect/wrapper.rs b/module/core/reflect_tools/src/reflect/wrapper.rs index defbe192f1..8481bce1c7 100644 --- a/module/core/reflect_tools/src/reflect/wrapper.rs +++ b/module/core/reflect_tools/src/reflect/wrapper.rs @@ -2,12 +2,12 @@ //! Collection of wrappers. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { } -mod maybe_as; +mod optional_cow; #[ doc( inline ) ] #[ allow( unused_imports ) ] @@ -40,7 +40,7 @@ pub mod exposed #[ allow( unused_imports ) ] pub use super:: { - maybe_as::OptionalCow, + optional_cow::OptionalCow, }; } diff --git a/module/core/reflect_tools/src/reflect/wrapper/aref.rs b/module/core/reflect_tools/src/reflect/wrapper/aref.rs new file mode 100644 index 0000000000..7e6afeb049 --- /dev/null +++ b/module/core/reflect_tools/src/reflect/wrapper/aref.rs @@ -0,0 +1,116 @@ +//! +//! It's often necessary to wrap something inot a local structure and this file contains a resusable local structure for wrapping. +//! + +// use core::fmt; +use core::ops::{ Deref }; + +/// Converter into universal wrapper with transparent option of copy on write reference emphasizing a specific aspect of identity of its internal type. +pub trait IntoRef< 'a, T, Marker > +{ + /// Converter into universal wrapper with transparent option of copy on write reference emphasizing a specific aspect of identity of its internal type. + fn into_ref( self ) -> Ref< 'a, T, Marker >; +} + +impl< 'a, T, Marker > IntoRef< 'a, T, Marker > for &'a T +{ + #[ inline( always ) ] + fn into_ref( self ) -> Ref< 'a, T, Marker > + { + Ref::< 'a, T, Marker >::new( self ) + } +} + +/// Transparent reference wrapper emphasizing a specific aspect of identity of its internal type. +#[ allow( missing_debug_implementations ) ] +#[ repr( transparent ) ] +pub struct Ref< 'a, T, Marker >( pub &'a T, ::core::marker::PhantomData< fn() -> Marker > ) +where + ::core::marker::PhantomData< fn( Marker ) > : Copy, + &'a T : Copy, +; + +impl< 'a, T, Marker > Clone for Ref< 'a, T, Marker > +{ + #[ inline( always ) ] + fn clone( &self ) -> Self + { + Self::new( self.0 ) + } +} + +impl< 'a, T, Marker > Copy for Ref< 'a, T, Marker > {} + +impl< 'a, T, Marker > Ref< 'a, T, Marker > +{ + + /// Just a constructor. + #[ inline( always ) ] + pub fn new( src : &'a T ) -> Self + { + Self( src, ::core::marker::PhantomData ) + } + + /// Just a constructor. + #[ inline( always ) ] + pub fn inner( self ) -> &'a T + { + self.0 + } + +} + +impl< 'a, T, Marker > AsRef< T > for Ref< 'a, T, Marker > +{ + fn as_ref( &self ) -> &T + { + &self.0 + } +} + +impl< 'a, T, Marker > Deref for Ref< 'a, T, Marker > +{ + type Target = T; + fn deref( &self ) -> &Self::Target + { + &self.0 + } +} + +impl< 'a, T, Marker > From< &'a T > for Ref< 'a, T, Marker > +{ + fn from( src : &'a T ) -> Self + { + Ref::new( src ) + } +} + +// impl< 'a, T, Marker > From< Ref< 'a, T, Marker > > for &'a T +// { +// fn from( wrapper : Ref< 'a, T, Marker > ) -> &'a T +// { +// wrapper.0 +// } +// } + +// impl< 'a, T, Marker > Default for Ref< 'a, T, Marker > +// where +// T : Default, +// { +// fn default() -> Self +// { +// Ref( &T::default() ) +// } +// } + +// impl< 'a, T, Marker > fmt::Debug for Ref< 'a, T, Marker > +// where +// T : fmt::Debug, +// { +// fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result +// { +// f.debug_struct( "Ref" ) +// .field( "0", &self.0 ) +// .finish() +// } +// } diff --git a/module/core/reflect_tools/src/reflect/wrapper/maybe_as.rs b/module/core/reflect_tools/src/reflect/wrapper/maybe_as.rs index f1351f8c92..d9c4a910c3 100644 --- a/module/core/reflect_tools/src/reflect/wrapper/maybe_as.rs +++ b/module/core/reflect_tools/src/reflect/wrapper/maybe_as.rs @@ -6,43 +6,63 @@ use core::fmt; use std::borrow::Cow; use core::ops::{ Deref }; -/// Universal wrapper with transparent option of copy on write reference emphasizing a specific aspect of identity of its internal type. -#[ repr( transparent ) ] -pub struct OptionalCow< 'a, T, Marker >( pub Option< Cow< 'a, T > >, ::core::marker::PhantomData< fn() -> Marker > ) +/// Converter into universal wrapper with transparent option of copy on write reference emphasizing a specific aspect of identity of its internal type. +pub trait IntoMaybeAs< 'a, T, Marker > where - T : std::borrow::ToOwned + ?Sized, - Marker : Clone + Copy + 'static, -; + T : Clone, +{ + /// Converter into universal wrapper with transparent option of copy on write reference emphasizing a specific aspect of identity of its internal type. + fn into_maybe_as( self ) -> MaybeAs< 'a, T, Marker >; +} -impl< 'a, T, Marker > OptionalCow< 'a, T, Marker > +impl< 'a, T, Marker > IntoMaybeAs< 'a, T, Marker > for T where - T : std::borrow::ToOwned + ?Sized, - Marker : Clone + Copy + 'static, + T : Clone, { - - /// Check is it borrowed. #[ inline( always ) ] - pub fn is_borrowed( &self ) -> bool + fn into_maybe_as( self ) -> MaybeAs< 'a, T, Marker > { - if self.0.is_none() - { - return false; - } - match self.0.as_ref().unwrap() - { - Cow::Borrowed( _ ) => true, - Cow::Owned( _ ) => false, - } + MaybeAs::< 'a, T, Marker >::new( self ) } +} - /// Check does it have some value. +impl< 'a, T, Marker > IntoMaybeAs< 'a, T, Marker > for &'a T +where + T : Clone, +{ #[ inline( always ) ] - pub fn is_some( &self ) -> bool + fn into_maybe_as( self ) -> MaybeAs< 'a, T, Marker > { - return self.0.is_some() + MaybeAs::< 'a, T, Marker >::new_with_ref( self ) } +} + +// xxx +// impl< 'a, T, Marker > IntoMaybeAs< 'a, T, Marker > for () +// where +// T : Clone, +// { +// #[ inline( always ) ] +// fn into_maybe_as( self ) -> MaybeAs< 'a, T, Marker > +// { +// MaybeAs::< 'a, T, Marker >( None ) +// } +// } - /// Constructor returning none. +/// Universal wrapper with transparent option of copy on write reference emphasizing a specific aspect of identity of its internal type. +#[ repr( transparent ) ] +#[ derive( Clone ) ] +pub struct MaybeAs< 'a, T, Marker >( pub Option< Cow< 'a, T > >, ::core::marker::PhantomData< fn() -> Marker > ) +where + T : Clone, +; + +impl< 'a, T, Marker > MaybeAs< 'a, T, Marker > +where + T : Clone, +{ + + /// Just a constructor. #[ inline( always ) ] pub fn none() -> Self { @@ -51,12 +71,11 @@ where /// Just a constructor. #[ inline( always ) ] - pub fn new( src : < T as std::borrow::ToOwned >::Owned ) -> Self + pub fn new( src : T ) -> Self { Self( Some( Cow::Owned( src ) ), ::core::marker::PhantomData ) } - // xxx : review /// Just a constructor. #[ inline( always ) ] pub fn new_with_ref( src : &'a T ) -> Self @@ -80,37 +99,10 @@ where } -// impl< 'a, T, Marker > std::borrow::ToOwned for OptionalCow< 'a, T, Marker > -// where -// T : std::borrow::ToOwned + ?Sized, -// { -// type Owned = OptionalCow< 'static, T::Owned, Marker >; -// -// fn to_owned( &self ) -> Self::Owned -// { -// OptionalCow -// ( -// self.0.as_ref().map( | cow | Cow::Owned( cow.to_owned() ) ), -// std::marker::PhantomData -// ) -// } -// } - -impl< 'a, T, Marker > Clone for OptionalCow< 'a, T, Marker > +impl< 'a, T, Marker > AsRef< Option< Cow< 'a, T > > > for MaybeAs< 'a, T, Marker > where - T : std::borrow::ToOwned + ?Sized, - Marker : Clone + Copy + 'static, -{ - fn clone( &self ) -> Self - { - Self( self.0.clone(), ::core::marker::PhantomData ) - } -} - -impl< 'a, T, Marker > AsRef< Option< Cow< 'a, T > > > for OptionalCow< 'a, T, Marker > -where - T : std::borrow::ToOwned + ?Sized, - Marker : Clone + Copy + 'static, + T : Clone, + Self : 'a, { fn as_ref( &self ) -> &Option< Cow< 'a, T > > { @@ -118,10 +110,10 @@ where } } -impl< 'a, T, Marker > Deref for OptionalCow< 'a, T, Marker > +impl< 'a, T, Marker > Deref for MaybeAs< 'a, T, Marker > where - T : std::borrow::ToOwned + ?Sized, - Marker : Clone + Copy + 'static, + T : Clone, + Marker : 'static, { type Target = Option< Cow< 'a, T > >; fn deref( &self ) -> &Option< Cow< 'a, T > > @@ -130,74 +122,121 @@ where } } -impl< 'a, T, Marker > From< Cow< 'a, T > > -for OptionalCow< 'a, T, Marker > +// impl< 'a, T, Marker > AsRef< T > for MaybeAs< 'a, T, Marker > +// where +// T : Clone, +// Self : 'a, +// { +// fn as_ref( &self ) -> &'a T +// { +// match &self.0 +// { +// Some( src ) => +// { +// match src +// { +// Cow::Borrowed( src ) => src, +// Cow::Owned( src ) => &src, +// } +// }, +// None => panic!( "MaybeAs is None" ), +// } +// } +// } +// +// impl< 'a, T, Marker > Deref for MaybeAs< 'a, T, Marker > +// where +// T : Clone, +// { +// type Target = T; +// fn deref( &self ) -> &'a T +// { +// self.as_ref() +// } +// } + +impl< 'a, T, Marker > From< T > +for MaybeAs< 'a, T, Marker > where - T : std::borrow::ToOwned + ?Sized, - Marker : Clone + Copy + 'static, + T : Clone, { - fn from( src : Cow< 'a, T > ) -> Self + fn from( src : T ) -> Self { - OptionalCow::new_with_inner( Some( src ) ) + MaybeAs::new( src ) } } impl< 'a, T, Marker > From< Option< Cow< 'a, T > > > -for OptionalCow< 'a, T, Marker > +for MaybeAs< 'a, T, Marker > where - T : std::borrow::ToOwned + ?Sized, - Marker : Clone + Copy + 'static, + T : Clone, { fn from( src : Option< Cow< 'a, T > > ) -> Self { - OptionalCow::new_with_inner( src ) + MaybeAs::new_with_inner( src ) } } impl< 'a, T, Marker > From< &'a T > -for OptionalCow< 'a, T, Marker > +for MaybeAs< 'a, T, Marker > where - T : std::borrow::ToOwned + ?Sized, - Marker : Clone + Copy + 'static, + T : Clone, { fn from( src : &'a T ) -> Self { - OptionalCow::new_with_ref( src ) + MaybeAs::new_with_ref( src ) } } -impl< 'a, T, Marker > Default for OptionalCow< 'a, T, Marker > +// impl< 'a, T, Marker > From< () > for MaybeAs< 'a, T, Marker > +// where +// T : (), +// { +// fn from( src : &'a T ) -> Self +// { +// MaybeAs( None ) +// } +// } + +// xxx : more from + +// impl< 'a, T, Marker > From< MaybeAs< 'a, T, Marker > > for &'a T +// where +// T : Clone, +// { +// fn from( wrapper : MaybeAs< 'a, T, Marker > ) -> &'a T +// { +// wrapper.0 +// } +// } + +impl< 'a, T, Marker > Default for MaybeAs< 'a, T, Marker > where - T : std::borrow::ToOwned + ?Sized, - < T as std::borrow::ToOwned >::Owned : Default, - Marker : Clone + Copy + 'static, + T : Clone, + T : Default, { fn default() -> Self { - OptionalCow::new( < T as std::borrow::ToOwned >::Owned::default() ) + MaybeAs::new( T::default() ) } } -impl< 'a, T, Marker > fmt::Debug for OptionalCow< 'a, T, Marker > +impl< 'a, T, Marker > fmt::Debug for MaybeAs< 'a, T, Marker > where - T : std::borrow::ToOwned + ?Sized, - < T as std::borrow::ToOwned >::Owned : fmt::Debug, - Marker : Clone + Copy + 'static, T : fmt::Debug, + T : Clone, { fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result { - f.debug_struct( "OptionalCow" ) + f.debug_struct( "MaybeAs" ) .field( "0", &self.0 ) .finish() } } -impl< 'a, T, Marker > PartialEq for OptionalCow< 'a, T, Marker > +impl< 'a, T, Marker > PartialEq for MaybeAs< 'a, T, Marker > where - T : std::borrow::ToOwned + ?Sized, - Marker : Clone + Copy + 'static, - T : PartialEq, + T : Clone + PartialEq, { fn eq( &self, other : &Self ) -> bool { @@ -205,10 +244,8 @@ where } } -impl< 'a, T, Marker > Eq for OptionalCow< 'a, T, Marker > +impl< 'a, T, Marker > Eq for MaybeAs< 'a, T, Marker > where - T : std::borrow::ToOwned + ?Sized, - Marker : Clone + Copy + 'static, - T : Eq, + T : Clone + Eq, { } diff --git a/module/core/reflect_tools/src/reflect/wrapper/optional_cow.rs b/module/core/reflect_tools/src/reflect/wrapper/optional_cow.rs new file mode 100644 index 0000000000..5f329cc459 --- /dev/null +++ b/module/core/reflect_tools/src/reflect/wrapper/optional_cow.rs @@ -0,0 +1,239 @@ +//! +//! It's often necessary to wrap something inot a local structure and this file contains wrapper of `Option< Cow< 'a, T > >`. +//! + +use core::fmt; +use std::borrow::Cow; +use core::ops::{ Deref }; + +/// Universal wrapper with transparent option of copy on write reference emphasizing a specific aspect of identity of its internal type. +#[ repr( transparent ) ] +pub struct OptionalCow< 'a, T, Marker >( pub Option< Cow< 'a, T > >, ::core::marker::PhantomData< fn() -> Marker > ) +where + T : std::borrow::ToOwned + ?Sized, + Marker : Clone + Copy + 'static, +; + +impl< 'a, T, Marker > OptionalCow< 'a, T, Marker > +where + T : std::borrow::ToOwned + ?Sized, + Marker : Clone + Copy + 'static, +{ + + /// Creates owned data from borrowed data, usually by cloning. + #[ inline( always ) ] + pub fn into_owned( &self ) -> < T as std::borrow::ToOwned >::Owned + where + < T as std::borrow::ToOwned >::Owned : Default, + { + match self.0.as_ref() + { + Some( c ) => c.clone().into_owned(), + None => < T as std::borrow::ToOwned >::Owned::default(), + } + } + + /// Check is it borrowed. + #[ inline( always ) ] + pub fn is_borrowed( &self ) -> bool + { + if self.0.is_none() + { + return false; + } + match self.0.as_ref().unwrap() + { + Cow::Borrowed( _ ) => true, + Cow::Owned( _ ) => false, + } + } + + /// Check does it have some value. + #[ inline( always ) ] + pub fn is_some( &self ) -> bool + { + return self.0.is_some() + } + + /// Constructor returning none. + #[ inline( always ) ] + pub fn none() -> Self + { + Self( None, ::core::marker::PhantomData ) + } + + /// Just a constructor. + #[ inline( always ) ] + pub fn new( src : < T as std::borrow::ToOwned >::Owned ) -> Self + { + Self( Some( Cow::Owned( src ) ), ::core::marker::PhantomData ) + } + + // xxx : review + /// Just a constructor. + #[ inline( always ) ] + pub fn new_with_ref( src : &'a T ) -> Self + { + Self( Some( Cow::Borrowed( src ) ), ::core::marker::PhantomData ) + } + + /// Just a constructor. + #[ inline( always ) ] + pub fn new_with_inner( src : Option< Cow< 'a, T > > ) -> Self + { + Self( src, ::core::marker::PhantomData ) + } + + /// Just a constructor. + #[ inline( always ) ] + pub fn inner( self ) -> Option< Cow< 'a, T > > + { + self.0 + } + +} + +// impl< 'a, T, Marker > std::borrow::ToOwned for OptionalCow< 'a, T, Marker > +// where +// T : std::borrow::ToOwned + ?Sized, +// { +// type Owned = OptionalCow< 'static, T::Owned, Marker >; +// +// fn to_owned( &self ) -> Self::Owned +// { +// OptionalCow +// ( +// self.0.as_ref().map( | cow | Cow::Owned( cow.to_owned() ) ), +// std::marker::PhantomData +// ) +// } +// } + +impl< 'a, T, Marker > Clone for OptionalCow< 'a, T, Marker > +where + T : std::borrow::ToOwned + ?Sized, + Marker : Clone + Copy + 'static, +{ + fn clone( &self ) -> Self + { + Self( self.0.clone(), ::core::marker::PhantomData ) + } +} + +impl< 'a, T, Marker > AsRef< Option< Cow< 'a, T > > > for OptionalCow< 'a, T, Marker > +where + T : std::borrow::ToOwned + ?Sized, + Marker : Clone + Copy + 'static, +{ + fn as_ref( &self ) -> &Option< Cow< 'a, T > > + { + &self.0 + } +} + +impl< 'a, T, Marker > Deref for OptionalCow< 'a, T, Marker > +where + T : std::borrow::ToOwned + ?Sized, + Marker : Clone + Copy + 'static, +{ + type Target = Option< Cow< 'a, T > >; + fn deref( &self ) -> &Option< Cow< 'a, T > > + { + self.as_ref() + } +} + +impl< 'a, T, Marker > From< Cow< 'a, T > > +for OptionalCow< 'a, T, Marker > +where + T : std::borrow::ToOwned + ?Sized, + Marker : Clone + Copy + 'static, +{ + fn from( src : Cow< 'a, T > ) -> Self + { + OptionalCow::new_with_inner( Some( src ) ) + } +} + +impl< 'a, T, Marker > From< Option< Cow< 'a, T > > > +for OptionalCow< 'a, T, Marker > +where + T : std::borrow::ToOwned + ?Sized, + Marker : Clone + Copy + 'static, +{ + fn from( src : Option< Cow< 'a, T > > ) -> Self + { + OptionalCow::new_with_inner( src ) + } +} + +impl< 'a, T, Marker > From< &'a T > +for OptionalCow< 'a, T, Marker > +where + T : std::borrow::ToOwned + ?Sized, + Marker : Clone + Copy + 'static, +{ + fn from( src : &'a T ) -> Self + { + OptionalCow::new_with_ref( src ) + } +} + +impl< 'a, T, Marker > Default for OptionalCow< 'a, T, Marker > +where + T : std::borrow::ToOwned + ?Sized, + < T as std::borrow::ToOwned >::Owned : Default, + Marker : Clone + Copy + 'static, +{ + fn default() -> Self + { + OptionalCow::new( < T as std::borrow::ToOwned >::Owned::default() ) + } +} + +impl< 'a, T, Marker > fmt::Debug for OptionalCow< 'a, T, Marker > +where + T : std::borrow::ToOwned + ?Sized, + < T as std::borrow::ToOwned >::Owned : fmt::Debug, + Marker : Clone + Copy + 'static, + T : fmt::Debug, +{ + fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result + { + f.debug_struct( "OptionalCow" ) + .field( "0", &self.0 ) + .finish() + } +} + +impl< 'a, T, Marker > PartialEq for OptionalCow< 'a, T, Marker > +where + T : std::borrow::ToOwned + ?Sized, + Marker : Clone + Copy + 'static, + T : PartialEq, +{ + fn eq( &self, other : &Self ) -> bool + { + self.as_ref() == other.as_ref() + } +} + +impl< 'a, T, Marker > Eq for OptionalCow< 'a, T, Marker > +where + T : std::borrow::ToOwned + ?Sized, + Marker : Clone + Copy + 'static, + T : Eq, +{ +} + +impl< 'a, T, Marker > From< OptionalCow< 'a, T, Marker > > for Option< Cow< 'a, T > > +where + T : std::borrow::ToOwned + ?Sized, + Marker : Clone + Copy + 'static, +{ + #[ inline( always ) ] + fn from( src : OptionalCow< 'a, T, Marker > ) -> Self + { + src.0 + } +} diff --git a/module/core/reflect_tools/tests/inc/fundamental/fields_bset.rs b/module/core/reflect_tools/tests/inc/fundamental/fields_bset.rs new file mode 100644 index 0000000000..abaee19fd5 --- /dev/null +++ b/module/core/reflect_tools/tests/inc/fundamental/fields_bset.rs @@ -0,0 +1,60 @@ +#[ allow( unused_imports ) ] +use super::*; + +use the_module:: +{ + Fields, +}; + +// xxx : implement for other collections + +use std:: +{ + borrow::Cow, +}; + +#[ test ] +fn bset_string_fields() +{ + let collection : BTreeSet< String > = bset! + [ + "a".to_string(), + "b".to_string(), + ]; + + // k, v + let got : BTreeSet< _ > = Fields::< usize, &str >::fields( &collection ).collect(); + assert_eq!( got.len(), 2 ); + let exp = bset![ ( 0, "a" ), ( 1, "b" ) ]; + assert_eq!( got, exp ); + + // k, Option< Cow< '_, str > > + let got : BTreeSet< _ > = Fields::< usize, Option< Cow< '_, str > > >::fields( &collection ).collect(); + assert_eq!( got.len(), 2 ); + let exp = bset![ ( 0, Some( Cow::Borrowed( "a" ) ) ), ( 1, Some( Cow::Borrowed( "b" ) ) ) ]; + assert_eq!( got, exp ); + +} + +#[ test ] +fn bset_str_fields() +{ + let collection : BTreeSet< &str > = bset! + [ + "a", + "b", + ]; + + // k, v + let got : BTreeSet< _ > = Fields::< usize, &str >::fields( &collection ).collect(); + assert_eq!( got.len(), 2 ); + let exp = bset![ ( 0, "a" ), ( 1, "b" ) ]; + assert_eq!( got, exp ); + + // k, Option< Cow< '_, str > > + let got : BTreeSet< _ > = Fields::< usize, Option< Cow< '_, str > > >::fields( &collection ).collect(); + assert_eq!( got.len(), 2 ); + let exp = bset![ ( 0, Some( Cow::Borrowed( "a" ) ) ), ( 1, Some( Cow::Borrowed( "b" ) ) ) ]; + assert_eq!( got, exp ); + +} diff --git a/module/core/reflect_tools/tests/inc/fundamental/fields_deque.rs b/module/core/reflect_tools/tests/inc/fundamental/fields_deque.rs new file mode 100644 index 0000000000..190d8fc57b --- /dev/null +++ b/module/core/reflect_tools/tests/inc/fundamental/fields_deque.rs @@ -0,0 +1,73 @@ +#[ allow( unused_imports ) ] +use super::*; + +use the_module:: +{ + Fields, + OptionalCow, +}; + +// xxx : implement for other collections + +use std:: +{ + borrow::Cow, +}; + +#[ test ] +fn deque_string_fields() +{ + let collection = deque! + [ + "a".to_string(), + "b".to_string(), + ]; + + // k, v + let got : VecDeque< _ > = Fields::< usize, &str >::fields( &collection ).collect(); + assert_eq!( got.len(), 2 ); + let exp = deque![ ( 0, "a" ), ( 1, "b" ) ]; + assert_eq!( got, exp ); + + // k, Option< Cow< '_, str > > + let got : VecDeque< _ > = Fields::< usize, Option< Cow< '_, str > > >::fields( &collection ).collect(); + assert_eq!( got.len(), 2 ); + let exp = deque![ ( 0, Some( Cow::Borrowed( "a" ) ) ), ( 1, Some( Cow::Borrowed( "b" ) ) ) ]; + assert_eq!( got, exp ); + + // k, OptionalCow< '_, str, () > + let got : VecDeque< _ > = Fields::< usize, OptionalCow< '_, str, () > >::fields( &collection ).collect(); + assert_eq!( got.len(), 2 ); + let exp = deque![ ( 0, OptionalCow::from( "a" ) ), ( 1, OptionalCow::from( "b" ) ) ]; + assert_eq!( got, exp ); + +} + +#[ test ] +fn deque_str_fields() +{ + let collection = deque! + [ + "a", + "b", + ]; + + // k, v + let got : VecDeque< _ > = Fields::< usize, &str >::fields( &collection ).collect(); + assert_eq!( got.len(), 2 ); + let exp = deque![ ( 0, "a" ), ( 1, "b" ) ]; + assert_eq!( got, exp ); + + // k, Option< Cow< '_, str > > + let got : VecDeque< _ > = Fields::< usize, Option< Cow< '_, str > > >::fields( &collection ).collect(); + assert_eq!( got.len(), 2 ); + let exp = deque![ ( 0, Some( Cow::Borrowed( "a" ) ) ), ( 1, Some( Cow::Borrowed( "b" ) ) ) ]; + assert_eq!( got, exp ); + + // k, OptionalCow< '_, str, () > + let got : VecDeque< _ > = Fields::< usize, OptionalCow< '_, str, () > >::fields( &collection ).collect(); + assert_eq!( got.len(), 2 ); + let exp = deque![ ( 0, OptionalCow::from( "a" ) ), ( 1, OptionalCow::from( "b" ) ) ]; + assert_eq!( got, exp ); + +} diff --git a/module/core/reflect_tools/tests/inc/fundamental/fields_hset.rs b/module/core/reflect_tools/tests/inc/fundamental/fields_hset.rs new file mode 100644 index 0000000000..fddc44dc94 --- /dev/null +++ b/module/core/reflect_tools/tests/inc/fundamental/fields_hset.rs @@ -0,0 +1,60 @@ +#[ allow( unused_imports ) ] +use super::*; + +use the_module:: +{ + Fields, +}; + +// xxx : implement for other collections + +use std:: +{ + borrow::Cow, +}; + +#[ test ] +fn hset_string_fields() +{ + let collection : HashSet< String > = hset! + [ + "a".to_string(), + "b".to_string(), + ]; + + // k, v + let got : HashSet< _ > = Fields::< usize, &str >::fields( &collection ).collect(); + assert_eq!( got.len(), 2 ); + assert!( got.contains(&( 0, "a" ) ) || got.contains(&( 1, "a" ) ) ); + assert!( got.contains(&( 0, "b" ) ) || got.contains(&( 1, "b" ) ) ); + + // k, Option< Cow< '_, str > > + let got : HashSet< _ > = Fields::< usize, Option< Cow< '_, str > > >::fields( &collection ).collect(); + assert_eq!( got.len(), 2 ); + assert!( got.contains(&( 0, Some( Cow::Borrowed( "a" ) ) ) ) || got.contains(&( 1, Some( Cow::Borrowed( "a" ) ) ) ) ); + assert!( got.contains(&( 0, Some( Cow::Borrowed( "b" ) ) ) ) || got.contains(&( 1, Some( Cow::Borrowed( "b" ) ) ) ) ); + +} + +#[ test ] +fn hset_str_fields() +{ + let collection : HashSet< &str > = hset! + [ + "a", + "b", + ]; + + // k, v + let got : HashSet< _ > = Fields::< usize, &str >::fields( &collection ).collect(); + assert_eq!( got.len(), 2 ); + assert!( got.contains(&( 0, "a" ) ) || got.contains(&( 1, "a" ) ) ); + assert!( got.contains(&( 0, "b" ) ) || got.contains(&( 1, "b" ) ) ); + + // k, Option< Cow< '_, str > > + let got : HashSet< _ > = Fields::< usize, Option< Cow< '_, str > > >::fields( &collection ).collect(); + assert_eq!( got.len(), 2 ); + assert!( got.contains(&( 0, Some( Cow::Borrowed( "a" ) ) ) ) || got.contains(&( 1, Some( Cow::Borrowed( "a" ) ) ) ) ); + assert!( got.contains(&( 0, Some( Cow::Borrowed( "b" ) ) ) ) || got.contains(&( 1, Some( Cow::Borrowed( "b" ) ) ) ) ); + +} diff --git a/module/core/reflect_tools/tests/inc/fundamental/fields_llist.rs b/module/core/reflect_tools/tests/inc/fundamental/fields_llist.rs new file mode 100644 index 0000000000..dc93d87c0a --- /dev/null +++ b/module/core/reflect_tools/tests/inc/fundamental/fields_llist.rs @@ -0,0 +1,73 @@ +#[ allow( unused_imports ) ] +use super::*; + +use the_module:: +{ + Fields, + OptionalCow, +}; + +// xxx : implement for other collections + +use std:: +{ + borrow::Cow, +}; + +#[ test ] +fn llist_string_fields() +{ + let collection = llist! + [ + "a".to_string(), + "b".to_string(), + ]; + + // k, v + let got : LinkedList< _ > = Fields::< usize, &str >::fields( &collection ).collect(); + assert_eq!( got.len(), 2 ); + let exp = llist![ ( 0, "a" ), ( 1, "b" ) ]; + assert_eq!( got, exp ); + + // k, Option< Cow< '_, str > > + let got : LinkedList< _ > = Fields::< usize, Option< Cow< '_, str > > >::fields( &collection ).collect(); + assert_eq!( got.len(), 2 ); + let exp = llist![ ( 0, Some( Cow::Borrowed( "a" ) ) ), ( 1, Some( Cow::Borrowed( "b" ) ) ) ]; + assert_eq!( got, exp ); + + // k, OptionalCow< '_, str, () > + let got : LinkedList< _ > = Fields::< usize, OptionalCow< '_, str, () > >::fields( &collection ).collect(); + assert_eq!( got.len(), 2 ); + let exp = llist![ ( 0, OptionalCow::from( "a" ) ), ( 1, OptionalCow::from( "b" ) ) ]; + assert_eq!( got, exp ); + +} + +#[ test ] +fn llist_str_fields() +{ + let collection = llist! + [ + "a", + "b", + ]; + + // k, v + let got : LinkedList< _ > = Fields::< usize, &str >::fields( &collection ).collect(); + assert_eq!( got.len(), 2 ); + let exp = llist![ ( 0, "a" ), ( 1, "b" ) ]; + assert_eq!( got, exp ); + + // k, Option< Cow< '_, str > > + let got : LinkedList< _ > = Fields::< usize, Option< Cow< '_, str > > >::fields( &collection ).collect(); + assert_eq!( got.len(), 2 ); + let exp = llist![ ( 0, Some( Cow::Borrowed( "a" ) ) ), ( 1, Some( Cow::Borrowed( "b" ) ) ) ]; + assert_eq!( got, exp ); + + // k, OptionalCow< '_, str, () > + let got : LinkedList< _ > = Fields::< usize, OptionalCow< '_, str, () > >::fields( &collection ).collect(); + assert_eq!( got.len(), 2 ); + let exp = llist![ ( 0, OptionalCow::from( "a" ) ), ( 1, OptionalCow::from( "b" ) ) ]; + assert_eq!( got, exp ); + +} diff --git a/module/core/reflect_tools/tests/inc/mod.rs b/module/core/reflect_tools/tests/inc/mod.rs index de4b0b494e..d0ec8fff41 100644 --- a/module/core/reflect_tools/tests/inc/mod.rs +++ b/module/core/reflect_tools/tests/inc/mod.rs @@ -12,6 +12,10 @@ mod fundamental mod fields_vec; mod fields_hmap; mod fields_bmap; + mod fields_bset; + mod fields_deque; + mod fields_hset; + mod fields_llist; } diff --git a/module/core/reflect_tools/tests/smoke_test.rs b/module/core/reflect_tools/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/core/reflect_tools/tests/smoke_test.rs +++ b/module/core/reflect_tools/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/core/reflect_tools_meta/Cargo.toml b/module/core/reflect_tools_meta/Cargo.toml index bd525a2655..cfbfa009de 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.3.0" +version = "0.6.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/reflect_tools_meta/License b/module/core/reflect_tools_meta/License index 6d5ef8559f..72c80c1308 100644 --- a/module/core/reflect_tools_meta/License +++ b/module/core/reflect_tools_meta/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/src/implementation/reflect.rs b/module/core/reflect_tools_meta/src/implementation/reflect.rs index 34d239b8d9..04799d0a5a 100644 --- a/module/core/reflect_tools_meta/src/implementation/reflect.rs +++ b/module/core/reflect_tools_meta/src/implementation/reflect.rs @@ -1,7 +1,7 @@ // use macro_tools::proc_macro2::TokenStream; use crate::*; -use macro_tools::{ Result, attr, diag }; +use macro_tools::{ Result, attr, diag, qt, proc_macro2, syn }; // diff --git a/module/core/strs_tools/Cargo.toml b/module/core/strs_tools/Cargo.toml index 507791fffb..37b6231be0 100644 --- a/module/core/strs_tools/Cargo.toml +++ b/module/core/strs_tools/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "strs_tools" -version = "0.16.0" +version = "0.18.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/strs_tools/License b/module/core/strs_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/core/strs_tools/License +++ b/module/core/strs_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/core/strs_tools/Readme.md index b070a0bd34..2f8bab872e 100644 --- a/module/core/strs_tools/Readme.md +++ b/module/core/strs_tools/Readme.md @@ -1,8 +1,8 @@ -# Module :: strs_tools +# Module :: `strs_tools` - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_strs_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_strs_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/strs_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/strs_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fstrs_tools%2Fexamples%2Fstrs_tools_trivial.rs,RUN_POSTFIX=--example%20strs_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_strs_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_strs_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/strs_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/strs_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fstrs_tools%2Fexamples%2Fstrs_tools_trivial.rs,RUN_POSTFIX=--example%20module%2Fcore%2Fstrs_tools%2Fexamples%2Fstrs_tools_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Tools to manipulate strings. diff --git a/module/core/strs_tools/src/lib.rs b/module/core/strs_tools/src/lib.rs index 72ff01c34c..b3858c911f 100644 --- a/module/core/strs_tools/src/lib.rs +++ b/module/core/strs_tools/src/lib.rs @@ -18,6 +18,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use orphan::*; pub use super::string::orphan::*; @@ -28,6 +29,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use exposed::*; } diff --git a/module/core/strs_tools/src/string/indentation.rs b/module/core/strs_tools/src/string/indentation.rs index b4574f3fbc..8e71ccbcfb 100644 --- a/module/core/strs_tools/src/string/indentation.rs +++ b/module/core/strs_tools/src/string/indentation.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { @@ -57,17 +57,17 @@ mod private { if b.0 > 0 { - a.push_str( "\n" ); + a.push( '\n' ); } a.push_str( prefix ); - a.push_str( &b.1 ); + a.push_str( b.1 ); a.push_str( postfix ); a }); - if src.ends_with( "\n" ) || src.ends_with( "\n\r" ) || src.ends_with( "\r\n" ) + if src.ends_with( '\n' ) || src.ends_with( "\n\r" ) || src.ends_with( "\r\n" ) { - result.push_str( "\n" ); + result.push( '\n' ); result.push_str( prefix ); result.push_str( postfix ); } @@ -85,6 +85,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use orphan::*; pub use private:: @@ -96,6 +97,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use exposed::*; pub use private:: @@ -107,6 +109,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use super::own as indentation; diff --git a/module/core/strs_tools/src/string/isolate.rs b/module/core/strs_tools/src/string/isolate.rs index 50ba7fe294..34e9af9449 100644 --- a/module/core/strs_tools/src/string/isolate.rs +++ b/module/core/strs_tools/src/string/isolate.rs @@ -26,7 +26,7 @@ mod private } /// - /// Adapter for IsolateOptions. + /// Adapter for `IsolateOptions`. /// pub trait IsolateOptionsAdapter< 'a > @@ -144,6 +144,7 @@ mod private /// It produces former. To convert former into options and run algorithm of splitting call `perform()`. /// + #[ must_use ] pub fn isolate<'a>() -> IsolateOptionsFormer<'a> { IsolateOptions::former() @@ -155,6 +156,7 @@ mod private /// It produces former. To convert former into options and run algorithm of splitting call `perform()`. /// + #[ must_use ] pub fn isolate_left<'a>() -> IsolateOptionsFormer<'a> { IsolateOptions::former() @@ -167,6 +169,7 @@ mod private /// It produces former. To convert former into options and run algorithm of splitting call `perform()`. /// + #[ must_use ] pub fn isolate_right<'a>() -> IsolateOptionsFormer<'a> { IsolateOptions::former() @@ -194,6 +197,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use exposed::*; } diff --git a/module/core/strs_tools/src/string/mod.rs b/module/core/strs_tools/src/string/mod.rs index 46552b8124..d473f9eeef 100644 --- a/module/core/strs_tools/src/string/mod.rs +++ b/module/core/strs_tools/src/string/mod.rs @@ -33,7 +33,9 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; + #[ allow( clippy::wildcard_imports ) ] pub use orphan::*; #[ cfg( all( feature = "string_indentation", not( feature = "no_std" ) ) ) ] pub use super::indentation::orphan::*; @@ -52,6 +54,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use exposed::*; } diff --git a/module/core/strs_tools/src/string/number.rs b/module/core/strs_tools/src/string/number.rs index 69f8b2c0d1..fac6b4664d 100644 --- a/module/core/strs_tools/src/string/number.rs +++ b/module/core/strs_tools/src/string/number.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { } @@ -11,14 +11,15 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use orphan::*; pub use private:: { }; - #[ cfg( all( feature = "string_parse_number" ) ) ] + #[ cfg( feature = "string_parse_number" ) ] #[ doc( inline ) ] - #[ allow( unused_imports ) ] + #[ allow( unused_imports, clippy::wildcard_imports ) ] pub use lexical::*; } @@ -26,6 +27,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use exposed::*; pub use private:: @@ -37,6 +39,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use super::own as number; diff --git a/module/core/strs_tools/src/string/parse_request.rs b/module/core/strs_tools/src/string/parse_request.rs index 432b2098b6..e926b4a4cf 100644 --- a/module/core/strs_tools/src/string/parse_request.rs +++ b/module/core/strs_tools/src/string/parse_request.rs @@ -1,7 +1,9 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; + #[ allow( clippy::wildcard_imports ) ] use string:: { split::*, @@ -48,6 +50,7 @@ mod private } } + #[ allow( clippy::from_over_into ) ] impl< T > Into > for OpType< T > { fn into( self ) -> Vec< T > @@ -62,8 +65,11 @@ mod private impl OpType< T > { - /// Append item of OpType to current value. If current type is `Primitive`, then it will be converted to + /// Append item of `OpType` to current value. If current type is `Primitive`, then it will be converted to /// `Vector`. + /// # Panics + /// qqq: doc + #[ must_use ] pub fn append( mut self, item : OpType< T > ) -> OpType< T > { let mut mut_item = item; @@ -156,6 +162,7 @@ mod private /// Options for parser. /// + #[ allow( clippy::struct_excessive_bools ) ] #[ derive( Debug, former::Former ) ] #[ perform( fn parse( mut self ) -> Request< 'a > ) ] pub struct ParseOptions< 'a > @@ -179,7 +186,7 @@ mod private } /// - /// Adapter for ParseOptions. + /// Adapter for `ParseOptions`. /// pub trait ParseOptionsAdapter< 'a > @@ -245,6 +252,7 @@ mod private self.subject_win_paths_maybe } + #[ allow( clippy::assigning_clones, clippy::too_many_lines, clippy::collapsible_if ) ] fn parse( mut self ) -> Request< 'a > where Self : Sized, @@ -474,6 +482,7 @@ mod private /// It produces former. To convert former into options and run algorithm of splitting call `perform()`. /// + #[ must_use ] pub fn request_parse<'a>() -> ParseOptionsFormer<'a> { ParseOptions::former() @@ -488,6 +497,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use orphan::*; pub use private:: @@ -504,6 +514,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use exposed::*; } @@ -512,6 +523,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use super::own as parse_request; @@ -526,6 +538,7 @@ pub mod exposed #[ allow( unused_imports ) ] pub mod prelude { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use private::ParseOptionsAdapter; } diff --git a/module/core/strs_tools/src/string/split.rs b/module/core/strs_tools/src/string/split.rs index 5c9eac10cd..6744e6a020 100644 --- a/module/core/strs_tools/src/string/split.rs +++ b/module/core/strs_tools/src/string/split.rs @@ -72,7 +72,7 @@ mod private { if let Some( x ) = src.find( pat ) { - r.push( ( x, x + pat.len() ) ) + r.push( ( x, x + pat.len() ) ); } } @@ -116,7 +116,7 @@ mod private impl< 'a, D : Searcher + Clone > SplitFastIterator< 'a, D > { - #[ allow( dead_code ) ] + #[ allow( dead_code, clippy::needless_pass_by_value ) ] fn new( o : impl SplitOptionsAdapter< 'a, D > ) -> Self { Self @@ -154,12 +154,10 @@ mod private { return None; } - else - { - self.counter -= 1; - self.stop_empty = true; - return Some( Split { string : "", typ : SplitType::Delimeted } ); - } + + self.counter -= 1; + self.stop_empty = true; + return Some( Split { string : "", typ : SplitType::Delimeted } ); } if start == 0 && end != 0 @@ -170,7 +168,7 @@ mod private let mut next = &self.iterable[ ..start ]; if start == end && self.counter >= 3 { - next = &self.iterable[ ..start + 1 ]; + next = &self.iterable[ ..=start ]; start += 1; } @@ -229,6 +227,7 @@ mod private /// #[ derive( Debug ) ] + #[ allow( clippy::struct_excessive_bools ) ] pub struct SplitIterator< 'a > { iterator : SplitFastIterator< 'a, Vec< &'a str > >, @@ -247,6 +246,7 @@ mod private impl< 'a > SplitIterator< 'a > { + #[ allow( clippy::needless_pass_by_value ) ] fn new( o : impl SplitOptionsAdapter< 'a, Vec< &'a str > > ) -> Self { let iterator; @@ -338,10 +338,7 @@ mod private { return self.next(); } - else - { - return Some( split ); - } + return Some( split ); }, None => { @@ -405,6 +402,7 @@ mod private /// #[ derive( Debug ) ] + #[ allow( clippy::struct_excessive_bools ) ] pub struct SplitOptions< 'a, D > where D : Searcher + Default + Clone, @@ -422,7 +420,8 @@ mod private impl< 'a > SplitOptions< 'a, Vec< &'a str > > { - /// Produces SplitIterator. + /// Produces `SplitIterator`. + #[ must_use ] pub fn split( self ) -> SplitIterator< 'a > where Self : Sized, @@ -435,7 +434,7 @@ mod private where D : Searcher + Default + Clone { - /// Produces SplitFastIterator. + /// Produces `SplitFastIterator`. pub fn split_fast( self ) -> SplitFastIterator< 'a, D > where Self : Sized, @@ -561,9 +560,10 @@ mod private } /// - /// Former for SplitOptions. + /// Former for `SplitOptions`. /// + #[ allow( clippy::struct_excessive_bools ) ] #[ derive( Debug ) ] pub struct SplitOptionsFormer< 'a > { @@ -637,6 +637,7 @@ mod private /// .perform(); /// ``` + #[ must_use ] pub fn split< 'a >() -> SplitOptionsFormer< 'a > { SplitOptionsFormer::new( < &str >::default() ) @@ -651,6 +652,7 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use orphan::*; pub use private:: @@ -668,6 +670,7 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use exposed::*; } @@ -676,6 +679,7 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use super::own as split; @@ -690,6 +694,7 @@ pub mod exposed #[ allow( unused_imports ) ] pub mod prelude { + #[ allow( clippy::wildcard_imports ) ] use super::*; pub use private::SplitOptionsAdapter; } diff --git a/module/core/strs_tools/tests/inc/number_test.rs b/module/core/strs_tools/tests/inc/number_test.rs index cc8bf03006..2c03f223d1 100644 --- a/module/core/strs_tools/tests/inc/number_test.rs +++ b/module/core/strs_tools/tests/inc/number_test.rs @@ -1,5 +1,4 @@ use super::*; - // tests_impls! @@ -10,43 +9,43 @@ tests_impls! /* test.case( "parse" ); */ { - a_id!( the_module::number::parse::< f32, _ >( "1.0" ), Ok( 1.0 ) ); + a_id!( crate::the_module::string::number::parse::< f32, _ >( "1.0" ), Ok( 1.0 ) ); } /* test.case( "parse_partial" ); */ { - a_id!( the_module::number::parse_partial::< i32, _ >( "1a" ), Ok( ( 1, 1 ) ) ); + a_id!( crate::the_module::string::number::parse_partial::< i32, _ >( "1a" ), Ok( ( 1, 1 ) ) ); } /* test.case( "parse_partial_with_options" ); */ { - const FORMAT : u128 = the_module::number::format::STANDARD; - let options = the_module::number::ParseFloatOptions::builder() + const FORMAT : u128 = crate::the_module::string::number::format::STANDARD; + let options = crate::the_module::string::number::ParseFloatOptions::builder() .exponent( b'^' ) .decimal_point( b',' ) .build() .unwrap(); - let got = the_module::number::parse_partial_with_options::< f32, _, FORMAT >( "0", &options ); + let got = crate::the_module::string::number::parse_partial_with_options::< f32, _, FORMAT >( "0", &options ); let exp = Ok( ( 0.0, 1 ) ); a_id!( got, exp ); } /* test.case( "parse_with_options" ); */ { - const FORMAT: u128 = the_module::number::format::STANDARD; - let options = the_module::number::ParseFloatOptions::builder() + const FORMAT: u128 = crate::the_module::string::number::format::STANDARD; + let options = crate::the_module::string::number::ParseFloatOptions::builder() .exponent( b'^' ) .decimal_point( b',' ) .build() .unwrap(); - let got = the_module::number::parse_with_options::< f32, _, FORMAT >( "1,2345", &options ); + let got = crate::the_module::string::number::parse_with_options::< f32, _, FORMAT >( "1,2345", &options ); let exp = Ok( 1.2345 ); a_id!( got, exp ); } /* test.case( "to_string" ); */ { - a_id!( the_module::number::to_string( 5 ), "5" ); + a_id!( crate::the_module::string::number::to_string( 5 ), "5" ); } } diff --git a/module/core/strs_tools/tests/smoke_test.rs b/module/core/strs_tools/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/core/strs_tools/tests/smoke_test.rs +++ b/module/core/strs_tools/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/core/test_tools/.cargo/config.toml b/module/core/test_tools/.cargo/config.toml new file mode 100644 index 0000000000..14735242a8 --- /dev/null +++ b/module/core/test_tools/.cargo/config.toml @@ -0,0 +1,5 @@ + +[build] +rustdocflags = [ + "--cfg", "feature=\"doctest\"", +] diff --git a/module/core/test_tools/Cargo.toml b/module/core/test_tools/Cargo.toml index 2093e6b0b2..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.9.0" +version = "0.14.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -21,71 +21,121 @@ keywords = [ "fundamental", "general-purpose", "testing" ] workspace = true [package.metadata.docs.rs] -features = [ "full" ] +features = [ "normal_build", "enabled" ] all-features = false - - +no-default-features = false +# features = [ "full" ] +# all-features = false # = features [features] -default = [ "enabled" ] -full = [ "enabled" ] +default = [ + "enabled", + "standalone_build", + # "normal_build", + "process_tools", + "process_environment_is_cicd", +] +full = [ + "default" +] +doctest = [] # for doctest shorcaomings resolution +# doctest does not work properly for aggregators no_std = [ - # "error_tools/no_std", - # "meta_tools/no_std", - # "mem_tools/no_std", - # "typing_tools/no_std", - # "data_type/no_std", - # "diagnostics_tools/no_std", - # "process_tools_published/no_std", - # "former_stable/use_alloc", ] use_alloc = [ "no_std", - # "error_tools/use_alloc", - # "meta_tools/use_alloc", - # "mem_tools/use_alloc", - # "typing_tools/use_alloc", - # "data_type/use_alloc", - # "diagnostics_tools/use_alloc", - # "process_tools_published/use_alloc", - # "former_stable/use_alloc", ] enabled = [ - "error_tools/enabled", - "meta_tools/enabled", - "mem_tools/enabled", - "typing_tools/enabled", - "data_type/enabled", - "diagnostics_tools/enabled", - "process_tools_published/enabled", - "collection_tools/enabled", ] # nightly = [ "typing_tools/nightly" ] +normal_build = [ + # "dep:error_tools", + # "dep:collection_tools", + # "dep:impls_index", + # "dep:mem_tools", + # "dep:typing_tools", + # "dep:diagnostics_tools", + # # "dep:process_tools", +] + +# standalone_build vesion of build is used to avoid cyclic dependency +# when crate depend on itself +standalone_build = [ + "standalone_error_tools", + "standalone_collection_tools", + "standalone_impls_index", + "standalone_mem_tools", + "standalone_typing_tools", + "standalone_diagnostics_tools", + # "standalone_process_tools", + # "dep:process_tools", +] +standalone_error_tools = [ "dep:anyhow", "dep:thiserror", "error_typed", "error_untyped" ] +standalone_collection_tools = [ "dep:hashbrown", "collection_constructors", "collection_into_constructors" ] +standalone_impls_index = [ "dep:impls_index_meta" ] +standalone_mem_tools = [] +standalone_typing_tools = [ "typing_implements", "typing_is_slice", "typing_inspect_type" ] +standalone_diagnostics_tools = [ "diagnostics_runtime_assertions", "diagnostics_compiletime_assertions", "diagnostics_memory_layout" ] + +# error_tools +error_typed = [] +error_untyped = [] +# collection_tools +collection_constructors = [] +collection_into_constructors = [] +# typing_tools +typing_inspect_type = [ "inspect_type/enabled" ] +typing_is_slice = [ "is_slice/enabled" ] +typing_implements = [ "implements/enabled" ] +# diagnostics_tools +diagnostics_runtime_assertions = [ "dep:pretty_assertions" ] # run-time assertions +diagnostics_compiletime_assertions = [] # compile-time assertions +diagnostics_memory_layout = [] # +# process_tools +process_tools = [] +process_environment_is_cicd = [] + [dependencies] ## external -paste = "~1.0" # zzz : remove laster -rustversion = "~1.0" -# anyhow = "~1.0" -num-traits = "~0.2" -trybuild = { version = "1.0.85", features = [ "diff" ] } -rand = "0.8.5" +# xxx : make sure we use workspace dependencies only +# trybuild = { version = "1.0.85", features = [ "diff" ] } +trybuild = { workspace = true, features = [ "diff" ] } +rustversion = { workspace = true } +num-traits = { workspace = true } +rand = { workspace = true } +# tempdir = { workspace = true } + +# ## internal +# +# error_tools = { workspace = true, features = [ "full" ], optional = true } +# collection_tools = { workspace = true, features = [ "full" ], optional = true } +# impls_index = { workspace = true, features = [ "full" ], optional = true } +# mem_tools = { workspace = true, features = [ "full" ], optional = true } +# typing_tools = { workspace = true, features = [ "full" ], optional = true } +# diagnostics_tools = { workspace = true, features = [ "full" ], optional = true } +# # process_tools = { workspace = true, features = [ "full" ], optional = true } + -## internal +## transient -error_tools = { workspace = true, features = [ "full" ] } -meta_tools = { workspace = true, features = [ "full" ] } -mem_tools = { workspace = true, features = [ "full" ] } -typing_tools = { workspace = true, features = [ "full" ] } -data_type = { workspace = true, features = [ "full" ] } -diagnostics_tools = { workspace = true, features = [ "full" ] } -process_tools_published = { workspace = true, features = [ "full" ] } -collection_tools = { workspace = true, features = [ "full" ] } -# former_stable = { workspace = true, features = [ "full" ] } +# error_tools +anyhow = { workspace = true, optional = true } +thiserror = { workspace = true, optional = true } +# collection_tools +hashbrown = { workspace = true, optional = true } +# impls_index +impls_index_meta = { workspace = true, optional = true } +# typing_tools +inspect_type = { workspace = true, optional = true } +is_slice = { workspace = true, optional = true } +implements = { workspace = true, optional = true } +# diagnostics_tools +pretty_assertions = { workspace = true, optional = true } [build-dependencies] rustc_version = "0.4" diff --git a/module/core/test_tools/License b/module/core/test_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/core/test_tools/License +++ b/module/core/test_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/core/test_tools/Readme.md index e54c622c81..cdb666da61 100644 --- a/module/core/test_tools/Readme.md +++ b/module/core/test_tools/Readme.md @@ -2,7 +2,7 @@ # Module :: test_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_test_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_test_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/test_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/test_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Ftest_tools%2Fexamples%2Ftest_tools_trivial.rs,RUN_POSTFIX=--example%20test_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_test_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_test_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/test_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/test_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Ftest_tools%2Fexamples%2Ftest_tools_trivial.rs,RUN_POSTFIX=--example%20module%2Fcore%2Ftest_tools%2Fexamples%2Ftest_tools_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Tools for writing and running tests. diff --git a/module/core/test_tools/src/lib.rs b/module/core/test_tools/src/lib.rs index 8baa4d9530..eaf065f365 100644 --- a/module/core/test_tools/src/lib.rs +++ b/module/core/test_tools/src/lib.rs @@ -4,98 +4,285 @@ #![ doc( html_root_url = "https://docs.rs/test_tools/latest/test_tools/" ) ] #![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] +// xxx : remove +//! ```rust +//! println!("-- doc test: printing Cargo feature environment variables --"); +//! for (key, val) in std::env::vars() { +//! if key.starts_with("CARGO_FEATURE_") { +//! println!("{}={}", key, val); +//! } +//! } +//! ``` + +// xxx2 : try to repurpose top-level lib.rs fiel for only top level features + /// Namespace with dependencies. +#[ allow( unused_imports ) ] #[ cfg( feature = "enabled" ) ] +#[ cfg( not( feature = "doctest" ) ) ] pub mod dependency { - // zzz : exclude later - #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use ::paste; + // // zzz : exclude later + // #[ doc( inline ) ] + // pub use ::paste; #[ doc( inline ) ] - #[ allow( unused_imports ) ] pub use ::trybuild; #[ doc( inline ) ] - #[ allow( unused_imports ) ] pub use ::rustversion; - #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use ::error_tools; + pub use ::num_traits; + + #[ cfg( all( feature = "standalone_build", not( feature = "normal_build" ) ) ) ] + #[ cfg( feature = "standalone_diagnostics_tools" ) ] #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use ::meta_tools; + pub use ::pretty_assertions; + #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use ::mem_tools; + pub use super:: + { + error_tools, + collection_tools, + impls_index, + mem_tools, + typing_tools, + diagnostics_tools, + // process_tools, + }; + +} + +mod private {} + +// + +// #[ cfg( feature = "enabled" ) ] +// // #[ cfg( not( feature = "no_std" ) ) ] +// ::meta_tools::mod_interface! +// { +// // #![ debug ] +// +// own use super::dependency::*; +// +// layer test; +// +// // xxx : comment out +// use super::exposed::meta; +// use super::exposed::mem; +// use super::exposed::typing; +// use super::exposed::dt; +// use super::exposed::diagnostics; +// use super::exposed::collection; +// // use super::exposed::process; +// +// // prelude use ::rustversion::{ nightly, stable }; +// +// // // xxx : eliminate need to do such things, putting itself to proper category +// // exposed use super::test::compiletime; +// // exposed use super::test::helper; +// // exposed use super::test::smoke_test; +// +// prelude use ::meta_tools as meta; +// prelude use ::mem_tools as mem; +// prelude use ::typing_tools as typing; +// prelude use ::data_type as dt; +// prelude use ::diagnostics_tools as diagnostics; +// prelude use ::collection_tools as collection; +// // prelude use ::process_tools as process; +// +// use ::collection_tools; // xxx : do that for all dependencies +// +// prelude use ::meta_tools:: +// { +// impls, +// index, +// tests_impls, +// tests_impls_optional, +// tests_index, +// }; +// +// prelude use ::typing_tools::{ implements }; +// +// } + +// xxx : use module namespaces +// #[ cfg( feature = "enabled" ) ] +// #[ cfg( not( feature = "no_std" ) ) ] +// pub use test::{ compiletime, helper, smoke_test }; + +#[ cfg( feature = "enabled" ) ] +#[ cfg( not( feature = "doctest" ) ) ] +pub mod test; + +/// Aggegating submodules without using cargo, but including their entry files directly. +/// +/// We don't want to run doctest of included files, because all of the are relative to submodule. +/// So we disable doctests of such submodules with `#[ cfg( not( doctest ) ) ]`. +#[ cfg( feature = "enabled" ) ] +#[ cfg( not( feature = "doctest" ) ) ] +// #[ cfg( all( feature = "no_std", feature = "use_alloc" ) ) ] +#[ cfg( all( feature = "standalone_build", not( feature = "normal_build" ) ) ) ] +// #[ cfg( any( not( doctest ), not( feature = "standalone_build" ) ) ) ] +mod standalone +{ + // We don't want to run doctest of aggregate + + /// Error tools. + #[ path = "../../../../core/error_tools/src/error/mod.rs" ] + pub mod error_tools; + pub use error_tools as error; + + /// Collection tools. + #[ path = "../../../../core/collection_tools/src/collection/mod.rs" ] + pub mod collection_tools; + pub use collection_tools as collection; + + /// impl and index macros. + #[ path = "../../../../core/impls_index/src/impls_index/mod.rs" ] + pub mod impls_index; + + /// Memory tools. + #[ path = "../../../../core/mem_tools/src/mem.rs" ] + pub mod mem_tools; + pub use mem_tools as mem; + + /// Typing tools. + #[ path = "../../../../core/typing_tools/src/typing.rs" ] + pub mod typing_tools; + pub use typing_tools as typing; + + /// Dagnostics tools. + #[ path = "../../../../core/diagnostics_tools/src/diag/mod.rs" ] + pub mod diagnostics_tools; + pub use diagnostics_tools as diag; + +} + +#[ cfg( feature = "enabled" ) ] +#[ cfg( not( feature = "doctest" ) ) ] +#[ cfg( all( feature = "standalone_build", not( feature = "normal_build" ) ) ) ] +pub use standalone::*; + +#[ cfg( feature = "enabled" ) ] +#[ cfg( not( feature = "doctest" ) ) ] +#[ cfg( not( all( feature = "standalone_build", not( feature = "normal_build" ) ) ) ) ] +pub use :: +{ + error_tools, + collection_tools, + impls_index, + mem_tools, + typing_tools, + diagnostics_tools, +}; + +#[ cfg( feature = "enabled" ) ] +#[ cfg( not( feature = "doctest" ) ) ] +#[ allow( unused_imports ) ] +pub use :: +{ + // process_tools, +}; + +#[ cfg( feature = "enabled" ) ] +#[ cfg( not( feature = "doctest" ) ) ] +#[ doc( inline ) ] +#[ allow( unused_imports ) ] +pub use own::*; + +/// Own namespace of the module. +#[ cfg( feature = "enabled" ) ] +#[ cfg( not( feature = "doctest" ) ) ] +#[ allow( unused_imports ) ] +pub mod own +{ + use super::*; + #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use ::typing_tools; + pub use orphan::*; + #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use ::num_traits; + pub use test::own::*; + #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use ::diagnostics_tools; + pub use + { + error_tools::orphan::*, + collection_tools::orphan::*, + impls_index::orphan::*, + mem_tools::orphan::*, + typing_tools::orphan::*, + diagnostics_tools::orphan::*, + }; + +} + +/// Shared with parent namespace of the module +#[ cfg( feature = "enabled" ) ] +#[ cfg( not( feature = "doctest" ) ) ] +#[ allow( unused_imports ) ] +pub mod orphan +{ + use super::*; #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use ::process_tools_published; + pub use exposed::*; + #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use ::process_tools_published as process_tools; + pub use test::orphan::*; } +/// Exposed namespace of the module. #[ cfg( feature = "enabled" ) ] -// #[ cfg( not( feature = "no_std" ) ) ] -::meta_tools::mod_interface! +#[ cfg( not( feature = "doctest" ) ) ] +#[ allow( unused_imports ) ] +pub mod exposed { - // #![ debug ] + use super::*; - own use super::dependency::*; + #[ doc( inline ) ] + pub use prelude::*; - layer test; + #[ doc( inline ) ] + pub use test::exposed::*; - // xxx : comment out - use super::exposed::meta; - use super::exposed::mem; - use super::exposed::typing; - use super::exposed::dt; - use super::exposed::diagnostics; - use super::exposed::collection; - // use super::exposed::process; + #[ doc( inline ) ] + pub use + { + error_tools::exposed::*, + collection_tools::exposed::*, + impls_index::exposed::*, + mem_tools::exposed::*, + typing_tools::exposed::*, + diagnostics_tools::exposed::*, + }; + +} - // prelude use ::rustversion::{ nightly, stable }; +/// Prelude to use essentials: `use my_module::prelude::*`. +#[ cfg( feature = "enabled" ) ] +#[ cfg( not( feature = "doctest" ) ) ] +#[ allow( unused_imports ) ] +pub mod prelude +{ + use super::*; - // // xxx : eliminate need to do such things, putting itself to proper category - // exposed use super::test::compiletime; - // exposed use super::test::helper; - // exposed use super::test::smoke_test; + #[ doc( inline ) ] + pub use test::prelude::*; - prelude use ::meta_tools as meta; - prelude use ::mem_tools as mem; - prelude use ::typing_tools as typing; - prelude use ::data_type as dt; - prelude use ::diagnostics_tools as diagnostics; - prelude use ::collection_tools as collection; - // prelude use ::process_tools as process; + pub use ::rustversion::{ nightly, stable }; - prelude use ::meta_tools:: + #[ doc( inline ) ] + pub use { - impls, - index, - tests_impls, - tests_impls_optional, - tests_index, + error_tools::prelude::*, + collection_tools::prelude::*, + impls_index::prelude::*, + mem_tools::prelude::*, + typing_tools::prelude::*, + diagnostics_tools::prelude::*, }; - prelude use ::typing_tools::{ implements }; } - -// xxx : use module namespaces -// #[ cfg( feature = "enabled" ) ] -// #[ cfg( not( feature = "no_std" ) ) ] -// pub use test::{ compiletime, helper, smoke_test }; diff --git a/module/core/test_tools/src/test/asset.rs b/module/core/test_tools/src/test/asset.rs index 410707ed36..c32cf9cb91 100644 --- a/module/core/test_tools/src/test/asset.rs +++ b/module/core/test_tools/src/test/asset.rs @@ -3,7 +3,7 @@ //! Test asset helper. //! -/// Internal namespace. +/// Define a private namespace for all its items. // #[ cfg( not( feature = "no_std" ) ) ] mod private { @@ -34,14 +34,73 @@ mod private } +// // +// // #[ cfg( not( feature = "no_std" ) ) ] +// crate::mod_interface! +// { // -// #[ cfg( not( feature = "no_std" ) ) ] -crate::mod_interface! +// // exposed use super; +// exposed use super::super::asset; +// +// // own use path_to_exe; +// +// } + +#[ 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 + { + }; + +} + +/// Shared with parent namespace of the module +#[ allow( unused_imports ) ] +pub mod orphan +{ + use super::*; + + #[ doc( inline ) ] + pub use exposed::*; - // exposed use super; - exposed use super::super::asset; + pub use super::super::asset; + +} + +/// Exposed namespace of the module. +#[ allow( unused_imports ) ] +pub mod exposed +{ + use super::*; + + #[ doc( inline ) ] + pub use prelude::*; + + #[ doc( inline ) ] + pub use + { + }; + +} + +/// Prelude to use essentials: `use my_module::prelude::*`. +#[ allow( unused_imports ) ] +pub mod prelude +{ + use super::*; - // own use path_to_exe; + #[ doc( inline ) ] + pub use + { + }; } diff --git a/module/core/test_tools/src/test/compiletime.rs b/module/core/test_tools/src/test/compiletime.rs index 77f9d362ac..9792d17e3d 100644 --- a/module/core/test_tools/src/test/compiletime.rs +++ b/module/core/test_tools/src/test/compiletime.rs @@ -3,22 +3,144 @@ //! Try building a program for negative testing. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { #[ doc( inline ) ] pub use ::trybuild::*; } +// // // +// #[ doc( inline ) ] +// #[ allow( unused_imports ) ] +// pub use own::*; +// +// #[ doc = r" Own namespace of the module." ] +// #[ allow( unused_imports ) ] +// pub mod own +// { +// use super::private; +// mod __all__ +// { +// pub use super::super::*; +// pub use super::super::private::*; +// } +// #[ doc( inline ) ] +// pub use super::orphan::*; +// #[ doc( inline ) ] +// #[ allow( unused_imports ) ] +// pub use private::{*}; +// } +// +// #[ doc = r" Orphan namespace of the module." ] +// #[ allow( unused_imports ) ] +// pub mod orphan +// { +// mod __all__ +// { +// pub use super::super::*; +// pub use super::super::private::*; +// } +// #[ doc( inline ) ] +// pub use super::exposed::*; +// } +// +// #[ doc = r" Exposed namespace of the module." ] +// #[ allow( unused_imports ) ] +// pub mod exposed +// { +// mod __all__ +// { +// pub use super::super::*; +// pub use super::super::private::*; +// } +// #[ doc( inline ) ] +// pub use super::prelude::*; +// #[ doc( inline ) ] +// #[ allow( unused_imports ) ] +// pub use super::super::compiletime; +// } +// +// #[ doc = r" Prelude to use essentials: `use my_module::prelude::*`." ] +// #[ allow( unused_imports ) ] +// pub mod prelude +// { +// mod __all__ +// { +// pub use super::super::*; +// pub use super::super::private::*; +// } +// } + +// crate::mod_interface! +// { +// // #![ debug ] +// // xxx : make it working +// // exposed use super; +// exposed use super::super::compiletime; +// own use +// { +// * +// }; +// } + +#[ 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 + { + private::*, + }; + +} + +/// Shared with parent namespace of the module +#[ allow( unused_imports ) ] +pub mod orphan +{ + use super::*; + + #[ doc( inline ) ] + pub use exposed::*; + + pub use super::super::compiletime; -crate::mod_interface! +} + +/// Exposed namespace of the module. +#[ allow( unused_imports ) ] +pub mod exposed { - // xxx : make it working - // exposed use super; - exposed use super::super::compiletime; - own use + use super::*; + + #[ doc( inline ) ] + pub use prelude::*; + + #[ doc( inline ) ] + pub use { - * }; + +} + +/// Prelude to use essentials: `use my_module::prelude::*`. +#[ allow( unused_imports ) ] +pub mod prelude +{ + use super::*; + + #[ doc( inline ) ] + pub use + { + }; + } diff --git a/module/core/test_tools/src/test/helper.rs b/module/core/test_tools/src/test/helper.rs index fe79e69784..16100d426a 100644 --- a/module/core/test_tools/src/test/helper.rs +++ b/module/core/test_tools/src/test/helper.rs @@ -5,7 +5,7 @@ // use super::*; -/// Internal namespace. +/// Define a private namespace for all its items. mod private { @@ -75,16 +75,78 @@ mod private pub use doc_file_test; } +// crate::mod_interface! +// { +// // xxx +// // #![ debug ] +// // exposed use super; +// exposed use super::super::helper; // +// prelude use +// { +// num, +// doc_file_test, +// }; +// } + +#[ 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 + { + private::*, + }; + +} -crate::mod_interface! +/// Shared with parent namespace of the module +#[ allow( unused_imports ) ] +pub mod orphan { - // exposed use super; - exposed use super::super::helper; + use super::*; - prelude use + #[ doc( inline ) ] + pub use exposed::*; + + pub use super::super::helper; + +} + +/// Exposed namespace of the module. +#[ allow( unused_imports ) ] +pub mod exposed +{ + use super::*; + + #[ doc( inline ) ] + pub use prelude::*; + + #[ doc( inline ) ] + pub use + { + private::num, + private::doc_file_test, + }; + +} + +/// Prelude to use essentials: `use my_module::prelude::*`. +#[ allow( unused_imports ) ] +pub mod prelude +{ + use super::*; + + #[ doc( inline ) ] + pub use { - num, - doc_file_test, }; + } diff --git a/module/core/test_tools/src/test/mod.rs b/module/core/test_tools/src/test/mod.rs index 60ec182ac4..b8ee109966 100644 --- a/module/core/test_tools/src/test/mod.rs +++ b/module/core/test_tools/src/test/mod.rs @@ -3,12 +3,114 @@ //! Tools for testing. //! -// #[ cfg( not( feature = "no_std" ) ) ] -crate::mod_interface! -{ - layer asset; - layer compiletime; - layer helper; - layer smoke_test; - layer version; +mod private {} + +// // #[ cfg( not( feature = "no_std" ) ) ] +// crate::mod_interface! +// { +// layer asset; +// layer compiletime; +// layer helper; +// layer smoke_test; +// layer version; +// } + +pub mod asset; +pub mod compiletime; +pub mod helper; +pub mod smoke_test; +pub mod version; +pub mod process; + +#[ cfg( feature = "enabled" ) ] +#[ doc( inline ) ] +#[ allow( unused_imports ) ] +pub use own::*; + +/// Own namespace of the module. +#[ cfg( feature = "enabled" ) ] +#[ allow( unused_imports ) ] +pub mod own +{ + use super::*; + + #[ doc( inline ) ] + pub use orphan::*; + + #[ doc( inline ) ] + pub use + { + asset::orphan::*, + compiletime::orphan::*, + helper::orphan::*, + smoke_test::orphan::*, + version::orphan::*, + process::orphan::*, + }; + +} + +/// Shared with parent namespace of the module +#[ cfg( feature = "enabled" ) ] +#[ allow( unused_imports ) ] +pub mod orphan +{ + use super::*; + + #[ doc( inline ) ] + pub use exposed::*; + +} + +/// Exposed namespace of the module. +#[ cfg( feature = "enabled" ) ] +#[ allow( unused_imports ) ] +pub mod exposed +{ + use super::*; + + #[ doc( inline ) ] + pub use prelude::*; + + #[ doc( inline ) ] + pub use + { + asset::exposed::*, + compiletime::exposed::*, + helper::exposed::*, + smoke_test::exposed::*, + version::exposed::*, + process::exposed::*, + }; + + #[ doc( inline ) ] + pub use crate::impls_index:: + { + impls, + index, + tests_impls, + tests_impls_optional, + tests_index, + }; + +} + +/// Prelude to use essentials: `use my_module::prelude::*`. +#[ cfg( feature = "enabled" ) ] +#[ allow( unused_imports ) ] +pub mod prelude +{ + use super::*; + + #[ doc( inline ) ] + pub use + { + asset::prelude::*, + compiletime::prelude::*, + helper::prelude::*, + smoke_test::prelude::*, + version::prelude::*, + process::prelude::*, + }; + } diff --git a/module/core/error_tools/src/typed.rs b/module/core/test_tools/src/test/process.rs similarity index 71% rename from module/core/error_tools/src/typed.rs rename to module/core/test_tools/src/test/process.rs index e4e341a586..bf59d4b314 100644 --- a/module/core/error_tools/src/typed.rs +++ b/module/core/test_tools/src/test/process.rs @@ -1,9 +1,15 @@ -/// Internal namespace. + +//! +//! Compact version of module::process_tools. What is needed from process tools +//! + +/// Define a private namespace for all its items. mod private { - } +pub mod environment; + #[ doc( inline ) ] #[ allow( unused_imports ) ] pub use own::*; @@ -13,8 +19,6 @@ pub use own::*; pub mod own { use super::*; - #[ doc( inline ) ] - pub use orphan::*; } /// Shared with parent namespace of the module @@ -22,19 +26,11 @@ pub mod own pub mod orphan { use super::*; - pub use super::super::typed; - pub use super::super::typed as for_lib; + pub use super::super::process as process_tools; #[ doc( inline ) ] pub use exposed::*; - #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use ::thiserror:: - { - Error, - }; - } /// Exposed namespace of the module. @@ -46,6 +42,11 @@ pub mod exposed #[ doc( inline ) ] pub use prelude::*; + #[ doc( inline ) ] + pub use private:: + { + }; + } /// Prelude to use essentials: `use my_module::prelude::*`. @@ -55,7 +56,8 @@ pub mod prelude use super::*; #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use thiserror; + pub use + { + }; -} \ No newline at end of file +} diff --git a/module/core/test_tools/src/test/process/environment.rs b/module/core/test_tools/src/test/process/environment.rs new file mode 100644 index 0000000000..1f3a146a9b --- /dev/null +++ b/module/core/test_tools/src/test/process/environment.rs @@ -0,0 +1,114 @@ + +//! +//! Environment of a process. +//! + +/// Define a private namespace for all its items. +mod private +{ + + #[ allow( unused_imports ) ] + use crate::*; + + /// Checks if the current execution environment is a Continuous Integration (CI) or Continuous Deployment (CD) pipeline. + /// + /// This function looks for environment variables that are commonly set by CI/CD systems to determine if it's running + /// within such an environment. It supports detection for a variety of popular CI/CD platforms including GitHub Actions, + /// GitLab CI, Travis CI, `CircleCI`, and Jenkins. + /// + /// # Returns + /// - `true` if an environment variable indicating a CI/CD environment is found. + /// - `false` otherwise. + /// + /// # Examples + /// + /// When running in a typical development environment (locally): + /// ```no_run + /// use test_tools::process_tools::environment; + /// assert_eq!( environment::is_cicd(), false ); + /// ``` + /// + /// When running in a CI/CD environment, one of the specified environment variables would be set, and: + /// ```no_run + /// // This example cannot be run as a test since it depends on the environment + /// // the code is executed in. However, in a CI environment, this would return true. + /// use test_tools::process_tools::environment; + /// assert_eq!( environment::is_cicd(), true ); + /// ``` + #[ cfg( feature = "process_environment_is_cicd" ) ] + #[ must_use ] + pub fn is_cicd() -> bool + { + use std::env; + let ci_vars = + [ + "CI", // Common in many CI systems + "GITHUB_ACTIONS", // GitHub Actions + "GITLAB_CI", // GitLab CI + "TRAVIS", // Travis CI + "CIRCLECI", // CircleCI + "JENKINS_URL", // Jenkins + ]; + + ci_vars.iter().any( | &var | env::var( var ).is_ok() ) + } + +} + +#[ 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 + { + private::is_cicd, + }; + +} + +/// Shared with parent 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::*; + + #[ doc( inline ) ] + pub use private:: + { + }; + +} + +/// Prelude to use essentials: `use my_module::prelude::*`. +#[ allow( unused_imports ) ] +pub mod prelude +{ + use super::*; + + #[ doc( inline ) ] + pub use + { + }; + +} diff --git a/module/core/test_tools/src/test/smoke_test.rs b/module/core/test_tools/src/test/smoke_test.rs index 34ebd2f55f..f0d9d7ad00 100644 --- a/module/core/test_tools/src/test/smoke_test.rs +++ b/module/core/test_tools/src/test/smoke_test.rs @@ -8,11 +8,12 @@ // xxx2 : use process_tools to build and run rust programs, introduce program_ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { + #[ allow( unused_imports ) ] use crate::*; - use dependency::process_tools::environment; + use process_tools::environment; // zzz : comment out // pub mod environment // { @@ -249,7 +250,6 @@ mod private } /// Run smoke test for both published and local version of the module. - pub fn smoke_tests_run() { smoke_test_for_local_run(); @@ -316,17 +316,87 @@ mod private } +// // +// crate::mod_interface! +// { +// +// // exposed use super; +// exposed use super::super::smoke_test; // -crate::mod_interface! +// exposed use SmokeModuleTest; +// exposed use smoke_test_run; +// exposed use smoke_tests_run; +// exposed use smoke_test_for_local_run; +// exposed use smoke_test_for_published_run; +// +// } + + +#[ 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 private:: + { + SmokeModuleTest, + smoke_test_run, + smoke_tests_run, + smoke_test_for_local_run, + smoke_test_for_published_run, + }; + +} + +/// Shared with parent namespace of the module +#[ allow( unused_imports ) ] +pub mod orphan +{ + use super::*; + + #[ doc( inline ) ] + pub use exposed::*; - // exposed use super; - exposed use super::super::smoke_test; + pub use super::super::smoke_test; - exposed use SmokeModuleTest; - exposed use smoke_test_run; - exposed use smoke_tests_run; - exposed use smoke_test_for_local_run; - exposed use smoke_test_for_published_run; +} + +/// Exposed namespace of the module. +#[ allow( unused_imports ) ] +pub mod exposed +{ + use super::*; + + #[ doc( inline ) ] + pub use prelude::*; + + #[ doc( inline ) ] + pub use private:: + { + SmokeModuleTest, + smoke_test_run, + smoke_tests_run, + smoke_test_for_local_run, + smoke_test_for_published_run, + }; + +} + +/// Prelude to use essentials: `use my_module::prelude::*`. +#[ allow( unused_imports ) ] +pub mod prelude +{ + use super::*; + + #[ doc( inline ) ] + pub use + { + }; } diff --git a/module/core/test_tools/src/test/version.rs b/module/core/test_tools/src/test/version.rs index 7737b5b456..f20821e54e 100644 --- a/module/core/test_tools/src/test/version.rs +++ b/module/core/test_tools/src/test/version.rs @@ -3,21 +3,80 @@ //! Version of Rust compiler //! -/// Internal namespace. +/// Define a private namespace for all its items. // #[ cfg( not( feature = "no_std" ) ) ] mod private { } +// // +// // #[ cfg( not( feature = "no_std" ) ) ] +// crate::mod_interface! +// { // -// #[ cfg( not( feature = "no_std" ) ) ] -crate::mod_interface! +// // exposed use super; +// exposed use super::super::version; +// +// prelude use ::rustversion::{ nightly, stable }; +// +// } + + +#[ 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 + { + private::*, + }; + +} + +/// Shared with parent namespace of the module +#[ allow( unused_imports ) ] +pub mod orphan { + use super::*; - // exposed use super; - exposed use super::super::version; + #[ doc( inline ) ] + pub use exposed::*; + + pub use super::super::version; + +} + +/// Exposed namespace of the module. +#[ allow( unused_imports ) ] +pub mod exposed +{ + use super::*; + + #[ doc( inline ) ] + pub use prelude::*; + + #[ doc( inline ) ] + pub use rustversion::{ nightly, stable }; + +} + +/// Prelude to use essentials: `use my_module::prelude::*`. +#[ allow( unused_imports ) ] +pub mod prelude +{ + use super::*; - prelude use ::rustversion::{ nightly, stable }; + #[ doc( inline ) ] + pub use + { + }; } diff --git a/module/core/test_tools/tests/inc/basic_test.rs b/module/core/test_tools/tests/inc/impls_index_test.rs similarity index 99% rename from module/core/test_tools/tests/inc/basic_test.rs rename to module/core/test_tools/tests/inc/impls_index_test.rs index 8e631611f4..9b1133fb91 100644 --- a/module/core/test_tools/tests/inc/basic_test.rs +++ b/module/core/test_tools/tests/inc/impls_index_test.rs @@ -15,7 +15,6 @@ use super::*; use ::test_tools as the_module; - #[ cfg( feature = "enabled" ) ] #[ cfg( not( feature = "no_std" ) ) ] the_module::tests_impls! diff --git a/module/core/test_tools/tests/inc/mem_test.rs b/module/core/test_tools/tests/inc/mem_test.rs new file mode 100644 index 0000000000..1cf4a2b724 --- /dev/null +++ b/module/core/test_tools/tests/inc/mem_test.rs @@ -0,0 +1,26 @@ +use super::*; + +// + +#[ allow( dead_code ) ] +#[ test ] +fn same_data() +{ + let buf = [ 0u8; 128 ]; + assert!( the_module::mem::same_data( &buf, &buf ) ); + + let x = [ 0u8; 1 ]; + let y = 0u8; + + assert!( the_module::mem::same_data( &x, &y ) ); + + assert!( !the_module::mem::same_data( &buf, &x ) ); + assert!( !the_module::mem::same_data( &buf, &y ) ); + + struct H1( &'static str ); + struct H2( &'static str ); + + assert!( the_module::mem::same_data( &H1( "hello" ), &H2( "hello" ) ) ); + assert!( !the_module::mem::same_data( &H1( "qwerty" ), &H2( "hello" ) ) ); + +} diff --git a/module/core/test_tools/tests/inc/mod.rs b/module/core/test_tools/tests/inc/mod.rs index bf3d2e3d78..fa8f21affb 100644 --- a/module/core/test_tools/tests/inc/mod.rs +++ b/module/core/test_tools/tests/inc/mod.rs @@ -1,8 +1,29 @@ -#[ allow( unused_imports ) ] use super::*; -mod basic_test; +mod impls_index_test; +mod mem_test; mod try_build_test; -// mod wtest_utility; -// qqq : include tests of all internal dependencies +/// Error tools. +#[ path = "../../../../core/error_tools/tests/inc/mod.rs" ] +pub mod error_tests; + +/// Collection tools. +#[ path = "../../../../core/collection_tools/tests/inc/mod.rs" ] +pub mod collection_tests; + +/// impl and index macros. +#[ path = "../../../../core/impls_index/tests/inc/mod.rs" ] +pub mod impls_index_tests; + +/// Memory tools. +#[ path = "../../../../core/mem_tools/tests/inc/mod.rs" ] +pub mod mem_tools_tests; + +/// Typing tools. +#[ path = "../../../../core/typing_tools/tests/inc/mod.rs" ] +pub mod typing_tools_tests; + +/// Diagnostics tools. +#[ path = "../../../../core/diagnostics_tools/tests/inc/mod.rs" ] +pub mod diagnostics_tools_tests; diff --git a/module/core/test_tools/tests/smoke_test.rs b/module/core/test_tools/tests/smoke_test.rs index d05a55b089..d31bbdb5e5 100644 --- a/module/core/test_tools/tests/smoke_test.rs +++ b/module/core/test_tools/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the crate. #[ cfg( feature = "enabled" ) ] #[ cfg( not( feature = "no_std" ) ) ] diff --git a/module/core/test_tools/tests/tests.rs b/module/core/test_tools/tests/tests.rs index 3cdbd75627..68d3dc1ed4 100644 --- a/module/core/test_tools/tests/tests.rs +++ b/module/core/test_tools/tests/tests.rs @@ -1,12 +1,17 @@ +//! All test. + +#![ allow( unused_imports ) ] + // #![ deny( rust_2018_idioms ) ] // #![ deny( missing_debug_implementations ) ] // #![ deny( missing_docs ) ] -#[ allow( unused_imports ) ] +include!( "../../../../module/step/meta/src/module/aggregating.rs" ); + use test_tools as the_module; -#[ allow( unused_imports ) ] -#[ cfg( feature = "enabled" ) ] -#[ cfg( not( feature = "no_std" ) ) ] -use test_tools::exposed::*; + +// #[ cfg( feature = "enabled" ) ] +// #[ cfg( not( feature = "no_std" ) ) ] +// use test_tools::exposed::*; mod inc; diff --git a/module/core/time_tools/License b/module/core/time_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/core/time_tools/License +++ b/module/core/time_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/time_tools/Readme.md b/module/core/time_tools/Readme.md index 903a8482e0..01bc1d87d8 100644 --- a/module/core/time_tools/Readme.md +++ b/module/core/time_tools/Readme.md @@ -2,7 +2,7 @@ # Module :: time_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_time_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_time_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/time_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/time_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Ftime_tools%2Fexamples%2Ftime_tools_trivial.rs,RUN_POSTFIX=--example%20time_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_time_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_time_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/time_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/time_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Ftime_tools%2Fexamples%2Ftime_tools_trivial.rs,RUN_POSTFIX=--example%20module%2Fcore%2Ftime_tools%2Fexamples%2Ftime_tools_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Collection of general purpose time tools. diff --git a/module/core/time_tools/tests/smoke_test.rs b/module/core/time_tools/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/core/time_tools/tests/smoke_test.rs +++ b/module/core/time_tools/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/core/typing_tools/Cargo.toml b/module/core/typing_tools/Cargo.toml index 5b128ba0e5..97878deab1 100644 --- a/module/core/typing_tools/Cargo.toml +++ b/module/core/typing_tools/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "typing_tools" -version = "0.8.0" +version = "0.10.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -25,8 +25,6 @@ workspace = true features = [ "full" ] all-features = false - - [features] default = [ diff --git a/module/core/typing_tools/License b/module/core/typing_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/core/typing_tools/License +++ b/module/core/typing_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/core/typing_tools/Readme.md index 33bd604df3..0424723d24 100644 --- a/module/core/typing_tools/Readme.md +++ b/module/core/typing_tools/Readme.md @@ -2,7 +2,7 @@ # Module :: typing_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_typing_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_typing_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/typing_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/typing_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Ftyping_tools%2Fexamples%2Ftyping_tools_trivial.rs,RUN_POSTFIX=--example%20typing_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_typing_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_typing_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/typing_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/typing_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Ftyping_tools%2Fexamples%2Ftyping_tools_trivial.rs,RUN_POSTFIX=--example%20module%2Fcore%2Ftyping_tools%2Fexamples%2Ftyping_tools_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Collection of general purpose tools for type checking. diff --git a/module/core/typing_tools/tests/inc/mod.rs b/module/core/typing_tools/tests/inc/mod.rs index f6849e47df..992c678289 100644 --- a/module/core/typing_tools/tests/inc/mod.rs +++ b/module/core/typing_tools/tests/inc/mod.rs @@ -1,8 +1,8 @@ -#[ allow( unused_imports ) ] use super::*; -#[ allow( unused_imports ) ] -use the_module::typing as the_module; +use test_tools::exposed::*; +// #[ allow( unused_imports ) ] +// use the_module::typing as the_module; #[ path = "../../../../core/implements/tests/inc/mod.rs" ] mod implements_test; diff --git a/module/core/typing_tools/tests/smoke_test.rs b/module/core/typing_tools/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/core/typing_tools/tests/smoke_test.rs +++ b/module/core/typing_tools/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/core/typing_tools/tests/tests.rs b/module/core/typing_tools/tests/tests.rs index 9f9c82cedc..090a22e25b 100644 --- a/module/core/typing_tools/tests/tests.rs +++ b/module/core/typing_tools/tests/tests.rs @@ -1,10 +1,9 @@ -// xxx -#![ cfg_attr( feature = "type_name_of_val", feature( type_name_of_val ) ) ] +//! All tests. + +// #![ cfg_attr( feature = "type_name_of_val", feature( type_name_of_val ) ) ] // // #![ cfg_attr( feature = "nightly", feature( type_name_of_val ) ) ] +#![ allow( unused_imports ) ] -#[ allow( unused_imports ) ] -use test_tools::exposed::*; -#[ allow( unused_imports ) ] use typing_tools as the_module; mod inc; diff --git a/module/core/variadic_from/Cargo.toml b/module/core/variadic_from/Cargo.toml index 616b747a6c..64d9bf0c36 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.22.0" +version = "0.30.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/variadic_from/License b/module/core/variadic_from/License index 6d5ef8559f..72c80c1308 100644 --- a/module/core/variadic_from/License +++ b/module/core/variadic_from/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/core/variadic_from/Readme.md index 1389bf6828..efaf398569 100644 --- a/module/core/variadic_from/Readme.md +++ b/module/core/variadic_from/Readme.md @@ -1,7 +1,7 @@ # Module :: variadic_from - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_variadic_from_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_variadic_from_push.yml) [![docs.rs](https://img.shields.io/docsrs/variadic_from?color=e3e8f0&logo=docs.rs)](https://docs.rs/variadic_from) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fvariadic_from%2Fexamples%2Fvariadic_from_trivial.rs,RUN_POSTFIX=--example%20variadic_from_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_variadic_from_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_variadic_from_push.yml) [![docs.rs](https://img.shields.io/docsrs/variadic_from?color=e3e8f0&logo=docs.rs)](https://docs.rs/variadic_from) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fvariadic_from%2Fexamples%2Fvariadic_from_trivial.rs,RUN_POSTFIX=--example%20module%2Fcore%2Fvariadic_from%2Fexamples%2Fvariadic_from_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) The variadic from is designed to provide a way to implement the From-like traits for structs with a variable number of fields, allowing them to be constructed from tuples of different lengths or from individual arguments. This functionality is particularly useful for creating flexible constructors that enable different methods of instantiation for a struct. By automating the implementation of traits crate reduces boilerplate code and enhances code readability and maintainability. diff --git a/module/core/variadic_from/src/variadic.rs b/module/core/variadic_from/src/variadic.rs index 715a135960..1297cb443c 100644 --- a/module/core/variadic_from/src/variadic.rs +++ b/module/core/variadic_from/src/variadic.rs @@ -2,7 +2,7 @@ //! Variadic constructor. Constructor with n arguments. Like Default, but with arguments. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { diff --git a/module/core/variadic_from/tests/smoke_test.rs b/module/core/variadic_from/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/core/variadic_from/tests/smoke_test.rs +++ b/module/core/variadic_from/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/core/wtools/Cargo.toml b/module/core/wtools/Cargo.toml index 8488ccd02b..c8d108f307 100644 --- a/module/core/wtools/Cargo.toml +++ b/module/core/wtools/Cargo.toml @@ -58,7 +58,7 @@ meta_default = [ "meta_mod_interface", # "meta_former", # "meta_options", - "meta_constructors", + # "meta_constructors", "meta_idents_concat", ] meta_full = [ @@ -68,7 +68,7 @@ meta_full = [ "meta_mod_interface", # "meta_former", # "meta_options", - "meta_constructors", + # "meta_constructors", "meta_idents_concat", ] # meta_use_std = [ "meta", "meta_tools/use_std" ] @@ -79,7 +79,7 @@ meta_for_each = [ "meta", "meta_tools/meta_for_each" ] meta_impls_index = [ "meta", "meta_tools/meta_impls_index" ] meta_mod_interface = [ "meta" ] # meta_mod_interface = [ "meta", "meta_tools/mod_interface" ] -meta_constructors = [ "meta", "meta_tools/meta_constructors" ] +# meta_constructors = [ "meta", "meta_tools/meta_constructors" ] meta_idents_concat = [ "meta", "meta_tools/meta_idents_concat" ] # meta_former = [ "meta", "meta_tools/former" ] # meta_options = [ "meta", "meta_tools/options" ] @@ -318,7 +318,6 @@ dt_default = [ # "dt_use_std", "data_type/default", "dt_either", - "dt_prelude", # "dt_type_constructor", # "dt_make", # "dt_vectorized_from", @@ -329,7 +328,6 @@ dt_full = [ # "dt_use_std", "data_type/full", "dt_either", - "dt_prelude", # "dt_type_constructor", # "dt_make", # "dt_vectorized_from", @@ -341,7 +339,6 @@ dt_full = [ dt_use_alloc = [ "dt", "data_type/use_alloc" ] dt_either = [ "dt", "data_type/dt_either" ] -dt_prelude = [ "dt", "data_type/dt_prelude" ] # dt_type_constructor = [ "dt", "data_type/dt_type_constructor" ] # dt_make = [ "dt", "data_type/dt_make" ] # dt_vectorized_from = [ "dt", "data_type/dt_vectorized_from" ] diff --git a/module/core/wtools/License b/module/core/wtools/License index e3e9e057cf..a23529f45b 100644 --- a/module/core/wtools/License +++ b/module/core/wtools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/core/wtools/Readme.md index 114861dea7..2f5552fc91 100644 --- a/module/core/wtools/Readme.md +++ b/module/core/wtools/Readme.md @@ -2,7 +2,7 @@ # Module :: wtools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_wtools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_wtools_push.yml) [![docs.rs](https://img.shields.io/docsrs/wtools?color=e3e8f0&logo=docs.rs)](https://docs.rs/wtools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fwtools%2Fexamples%2Fwtools_trivial.rs,RUN_POSTFIX=--example%20wtools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_wtools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_wtools_push.yml) [![docs.rs](https://img.shields.io/docsrs/wtools?color=e3e8f0&logo=docs.rs)](https://docs.rs/wtools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fwtools%2Fexamples%2Fwtools_trivial.rs,RUN_POSTFIX=--example%20module%2Fcore%2Fwtools%2Fexamples%2Fwtools_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Collection of general purpose tools for solving problems. Fundamentally extend the language without spoiling, so may be used solely or in conjunction with another module of such kind. diff --git a/module/core/wtools/tests/smoke_test.rs b/module/core/wtools/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/core/wtools/tests/smoke_test.rs +++ b/module/core/wtools/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/move/assistant/api/list.http b/module/move/assistant/api/list.http deleted file mode 100644 index 32a3263f5a..0000000000 --- a/module/move/assistant/api/list.http +++ /dev/null @@ -1,3 +0,0 @@ -get https://api.openai.com/v1/models -Authorization: Bearer {{openai_token}} -# Content-Type: application/json diff --git a/module/move/assistant/src/client.rs b/module/move/assistant/src/client.rs deleted file mode 100644 index 946be0cf2a..0000000000 --- a/module/move/assistant/src/client.rs +++ /dev/null @@ -1,96 +0,0 @@ -//! -//! Client of API. -//! - -/// Internal namespace. -mod private -{ - - pub use openai_api_rs::v1:: - { - api::Client, - assistant::AssistantObject, - }; - - use std:: - { - env, - error::Error, - }; - - use former::Former; - - /// Options for configuring the OpenAI API client. - #[ derive( Former, Debug ) ] - pub struct ClientOptions - { - /// The API key for authenticating with the OpenAI API. - pub api_key : Option< String >, - } - - /// Creates a new OpenAI API client using the API key from the environment variable `OPENAI_API_KEY`. - pub fn client() -> Result< Client, Box< dyn Error > > - { - let api_key = env::var( "OPENAI_API_KEY" )?; - Ok( Client::new( api_key ) ) - } - - -} - -#[ allow( unused_imports ) ] -pub use own::*; - -/// Own namespace of the module. -#[ allow( unused_imports ) ] -pub mod own -{ - use super::*; - - #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use orphan::*; - -} - -/// Orphan namespace of the module. -#[ allow( unused_imports ) ] -pub mod orphan -{ - use super::*; - #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use exposed::*; -} - -/// Exposed namespace of the module. -#[ allow( unused_imports ) ] -pub mod exposed -{ - use super::*; - - #[ doc( inline ) ] - pub use private:: - { - ClientOptions, - client, - AssistantObject, - }; - - // #[ doc( inline ) ] - // #[ allow( unused_imports ) ] - // pub use reflect_tools:: - // { - // Fields, - // _IteratorTrait, - // IteratorTrait, - // }; - -} - -/// Prelude to use essentials: `use my_module::prelude::*`. -#[ allow( unused_imports ) ] -pub mod prelude -{ - use super::*; -} diff --git a/module/move/assistant/src/main.rs b/module/move/assistant/src/main.rs deleted file mode 100644 index ad03e3549a..0000000000 --- a/module/move/assistant/src/main.rs +++ /dev/null @@ -1,28 +0,0 @@ -#![ cfg_attr( feature = "no_std", no_std ) ] -#![ doc( html_logo_url = "https://raw.githubusercontent.com/Wandalen/wTools/master/asset/img/logo_v3_trans_square.png" ) ] -#![ doc( html_favicon_url = "https://raw.githubusercontent.com/Wandalen/wTools/alpha/asset/img/logo_v3_trans_square_icon_small_v2.ico" ) ] -#![ doc( html_root_url = "https://docs.rs/assistant/latest/assistant/" ) ] -#![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] - -use std:: -{ - env, - error::Error, -}; - -use dotenv::dotenv; - -use assistant:: -{ - client, -}; - -#[ tokio::main ] -async fn main() -> Result< (), Box< dyn Error > > -{ - dotenv().ok(); - let client = client()?; - let assistants = client.list_assistant( None, None, None, None )?; - println!( "Assistants: {:?}", assistants.data ); - Ok( () ) -} diff --git a/module/move/crates_tools/Cargo.toml b/module/move/crates_tools/Cargo.toml index 3bc5d48dab..62244920ae 100644 --- a/module/move/crates_tools/Cargo.toml +++ b/module/move/crates_tools/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "crates_tools" -version = "0.12.0" +version = "0.15.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/move/crates_tools/License b/module/move/crates_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/move/crates_tools/License +++ b/module/move/crates_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/crates_tools/Readme.md b/module/move/crates_tools/Readme.md index dabc50fb6c..f7751cb4ab 100644 --- a/module/move/crates_tools/Readme.md +++ b/module/move/crates_tools/Readme.md @@ -1,8 +1,8 @@ -# Module :: crates_tools +# Module :: `crates_tools` - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_crates_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_crates_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/crates_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/crates_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fmove%2Fcrates_tools%2Fexamples%2Fcrates_tools_trivial.rs,RUN_POSTFIX=--example%20crates_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_crates_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_crates_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/crates_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/crates_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fmove%2Fcrates_tools%2Fexamples%2Fcrates_tools_trivial.rs,RUN_POSTFIX=--example%20module%2Fmove%2Fcrates_tools%2Fexamples%2Fcrates_tools_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Tools to analyse crate files. @@ -25,7 +25,6 @@ Some possible use cases are: ```rust use crates_tools::*; -fn main() { #[ cfg( feature = "enabled" ) ] { diff --git a/module/move/crates_tools/src/lib.rs b/module/move/crates_tools/src/lib.rs index 92dc8c0048..1d60bb2135 100644 --- a/module/move/crates_tools/src/lib.rs +++ b/module/move/crates_tools/src/lib.rs @@ -3,24 +3,26 @@ #![ doc( html_root_url = "https://docs.rs/crates_tools/latest/crates_tools/" ) ] #![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] -/// Internal namespace. +/// Define a private namespace for all its items. #[ cfg( feature = "enabled" ) ] mod private { use std::collections::HashMap; - use std::fmt::Formatter; + use core::fmt::Formatter; use std::io::Read; use std::path::{ Path, PathBuf }; - use std::time::Duration; - use ureq::{ Agent, AgentBuilder }; + use core::time::Duration; + use ureq::AgentBuilder; /// Represents a `.crate` archive, which is a collection of files and their contents. #[ derive( Default, Clone, PartialEq ) ] pub struct CrateArchive( HashMap< PathBuf, Vec< u8 > > ); - impl std::fmt::Debug for CrateArchive + impl core::fmt::Debug for CrateArchive { - fn fmt( &self, f : &mut Formatter< '_ > ) -> std::fmt::Result + #[ allow( clippy::implicit_return, clippy::min_ident_chars ) ] + #[ inline] + fn fmt( &self, f : &mut Formatter< '_ > ) -> core::fmt::Result { f.debug_struct( "CrateArchive" ).field( "files", &self.0.keys() ).finish() } @@ -29,24 +31,33 @@ mod private impl CrateArchive { /// Reads and decode a `.crate` archive from a given path. + /// # Errors + /// qqq: doc + #[ allow( clippy::question_mark_used, clippy::implicit_return ) ] + #[ inline ] pub fn read< P >( path : P ) -> std::io::Result< Self > where P : AsRef< Path >, { let mut file = std::fs::File::open( path )?; let mut buf = vec![]; + #[ allow( clippy::verbose_file_reads ) ] file.read_to_end( &mut buf )?; Self::decode( buf ) } #[ cfg( feature = "network" ) ] + #[ allow( clippy::question_mark_used, clippy::implicit_return, clippy::result_large_err ) ] /// Downloads and decodes a `.crate` archive from a given url. + /// # Errors + /// qqq: docs + #[ inline ] pub fn download< Url >( url : Url ) -> Result< Self, ureq::Error > where Url : AsRef< str >, { - let agent: Agent = AgentBuilder::new() + let agent = AgentBuilder::new() .timeout_read( Duration::from_secs( 5 ) ) .timeout_write( Duration::from_secs( 5 ) ) .build(); @@ -63,16 +74,24 @@ mod private /// Requires the full version of the package, in the format of `"x.y.z"` /// /// Returns error if the package with specified name and version - not exists. + /// # Errors + /// qqq: doc #[ cfg( feature = "network" ) ] + #[ allow( clippy::implicit_return, clippy::result_large_err ) ] + #[ inline ] pub fn download_crates_io< N, V >( name : N, version : V ) -> Result< Self, ureq::Error > where - N : std::fmt::Display, - V : std::fmt::Display, + N : core::fmt::Display, + V : core::fmt::Display, { Self::download( format!( "https://static.crates.io/crates/{name}/{name}-{version}.crate" ) ) } /// Decodes a bytes that represents a `.crate` file. + /// # Errors + /// qqq: doc + #[ allow( clippy::question_mark_used, unknown_lints, clippy::implicit_return ) ] + #[ inline ] pub fn decode< B >( bytes : B ) -> std::io::Result< Self > where B : AsRef<[ u8 ]>, @@ -81,43 +100,44 @@ mod private use flate2::bufread::GzDecoder; use tar::Archive; - let bytes = bytes.as_ref(); - if bytes.is_empty() + let bytes_slice = bytes.as_ref(); + if bytes_slice.is_empty() { return Ok( Self::default() ) } - let gz = GzDecoder::new( bytes ); + let gz = GzDecoder::new( bytes_slice ); let mut archive = Archive::new( gz ); let mut output = HashMap::new(); for file in archive.entries()? { - let mut file = file?; + let mut archive_file = file?; let mut contents = vec![]; - file.read_to_end( &mut contents )?; + archive_file.read_to_end( &mut contents )?; - output.insert( file.path()?.to_path_buf(), contents ); + output.insert( archive_file.path()?.to_path_buf(), contents ); } Ok( Self( output ) ) } - } - impl CrateArchive - { /// Returns a list of files from the `.crate` file. + #[ allow( clippy::implicit_return ) ] + #[ inline ] pub fn list( &self ) -> Vec< &Path > { self.0.keys().map( PathBuf::as_path ).collect() } /// Returns content of file by specified path from the `.crate` file in bytes representation. + #[ allow( clippy::implicit_return ) ] + #[ inline ] pub fn content_bytes< P >( &self, path : P ) -> Option< &[ u8 ] > - where - P : AsRef< Path >, + where + P : AsRef< Path >, { self.0.get( path.as_ref() ).map( Vec::as_ref ) } @@ -126,7 +146,7 @@ mod private #[ cfg( feature = "enabled" ) ] #[ doc( inline ) ] -#[ allow( unused_imports ) ] +#[ allow( unused_imports, clippy::pub_use ) ] pub use own::*; /// Own namespace of the module. @@ -134,9 +154,9 @@ pub use own::*; #[ allow( unused_imports ) ] pub mod own { - use super::*; + use super::orphan; #[ doc( inline ) ] - #[ allow( unused_imports ) ] + #[ allow( unused_imports, clippy::pub_use ) ] pub use orphan::*; } @@ -145,9 +165,9 @@ pub mod own #[ allow( unused_imports ) ] pub mod orphan { - use super::*; + use super::exposed; #[ doc( inline ) ] - #[ allow( unused_imports ) ] + #[ allow( unused_imports, clippy::pub_use ) ] pub use exposed::*; } @@ -156,9 +176,9 @@ pub mod orphan #[ allow( unused_imports ) ] pub mod exposed { - use super::*; + use super::prelude; #[ doc( inline ) ] - #[ allow( unused_imports ) ] + #[ allow( unused_imports, clippy::pub_use ) ] pub use prelude::*; } @@ -167,8 +187,8 @@ pub mod exposed #[ allow( unused_imports ) ] pub mod prelude { - use super::*; + use super::private; #[ doc( inline ) ] - #[ allow( unused_imports ) ] + #[ allow( unused_imports, clippy::pub_use ) ] pub use private::CrateArchive; } diff --git a/module/move/deterministic_rand/Cargo.toml b/module/move/deterministic_rand/Cargo.toml index 1a469f1249..ae667e3e41 100644 --- a/module/move/deterministic_rand/Cargo.toml +++ b/module/move/deterministic_rand/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deterministic_rand" -version = "0.5.0" +version = "0.6.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/move/deterministic_rand/License b/module/move/deterministic_rand/License index 6d5ef8559f..72c80c1308 100644 --- a/module/move/deterministic_rand/License +++ b/module/move/deterministic_rand/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/move/deterministic_rand/Readme.md index 4d52d1cc38..54b6c809ed 100644 --- a/module/move/deterministic_rand/Readme.md +++ b/module/move/deterministic_rand/Readme.md @@ -2,7 +2,7 @@ - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_deterministic_rand_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_deterministic_rand_push.yml) [![docs.rs](https://img.shields.io/docsrs/deterministic_rand?color=e3e8f0&logo=docs.rs)](https://docs.rs/deterministic_rand) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fmove%2Fdeterministic_rand%2Fexamples%2Fdeterministic_rand_trivial.rs,RUN_POSTFIX=--example%20deterministic_rand_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_deterministic_rand_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_deterministic_rand_push.yml) [![docs.rs](https://img.shields.io/docsrs/deterministic_rand?color=e3e8f0&logo=docs.rs)](https://docs.rs/deterministic_rand) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fmove%2Fdeterministic_rand%2Fexamples%2Fdeterministic_rand_trivial.rs,RUN_POSTFIX=--example%20module%2Fmove%2Fdeterministic_rand%2Fexamples%2Fdeterministic_rand_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Hierarchical random number generators for concurrent simulations with switchable determinism. diff --git a/module/move/deterministic_rand/src/hrng_deterministic.rs b/module/move/deterministic_rand/src/hrng_deterministic.rs index ceb64b06c0..e489d8522e 100644 --- a/module/move/deterministic_rand/src/hrng_deterministic.rs +++ b/module/move/deterministic_rand/src/hrng_deterministic.rs @@ -6,7 +6,7 @@ //! Both have the same interface and are interchengable by switching on/off a feature `determinsim`. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { diff --git a/module/move/deterministic_rand/src/hrng_non_deterministic.rs b/module/move/deterministic_rand/src/hrng_non_deterministic.rs index 1ab19d55d2..57db16656b 100644 --- a/module/move/deterministic_rand/src/hrng_non_deterministic.rs +++ b/module/move/deterministic_rand/src/hrng_non_deterministic.rs @@ -6,7 +6,7 @@ //! Both have the same interface and are interchengable by switching on/off a feature `determinsim`. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { diff --git a/module/move/deterministic_rand/src/iter.rs b/module/move/deterministic_rand/src/iter.rs index caab96a148..cdfb83e100 100644 --- a/module/move/deterministic_rand/src/iter.rs +++ b/module/move/deterministic_rand/src/iter.rs @@ -3,7 +3,7 @@ //! Extensions of iterator for determinism. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { diff --git a/module/move/deterministic_rand/src/lib.rs b/module/move/deterministic_rand/src/lib.rs index b41e465453..dccd3d6c55 100644 --- a/module/move/deterministic_rand/src/lib.rs +++ b/module/move/deterministic_rand/src/lib.rs @@ -18,6 +18,8 @@ pub use hrng_deterministic as hrng; #[ cfg( any( not( feature = "determinism" ), feature = "no_std" ) ) ] pub use hrng_non_deterministic as hrng; +mod private {} + mod_interface! { diff --git a/module/move/deterministic_rand/src/seed.rs b/module/move/deterministic_rand/src/seed.rs index c7f1e078a9..fc68cc4cdf 100644 --- a/module/move/deterministic_rand/src/seed.rs +++ b/module/move/deterministic_rand/src/seed.rs @@ -3,7 +3,7 @@ //! Master seed. //! -/// Internal namespace. +/// Define a private namespace for all its items. mod private { #[ cfg( feature = "no_std" ) ] 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/Cargo.toml b/module/move/graphs_tools/Cargo.toml index f0eeb97831..0b5425d3d3 100644 --- a/module/move/graphs_tools/Cargo.toml +++ b/module/move/graphs_tools/Cargo.toml @@ -1,10 +1,9 @@ [package] name = "graphs_tools" -version = "0.2.0" +version = "0.3.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", - "Dmytro Kryvoruchko ", ] license = "MIT" readme = "Readme.md" @@ -24,26 +23,34 @@ workspace = true features = [ "full" ] all-features = false - [features] default = [ - "enabled" + "enabled", + "debug", ] full = [ - "enabled", + "default", +] +enabled = [ + "meta_tools/enabled", + "iter_tools/enabled", + # "data_type/enabled", + # "strs_tools/enabled", + "collection_tools/enabled", + "former/enabled", ] -no_std = [] -use_alloc = [ "no_std" ] -enabled = [ "meta_tools/enabled", "iter_tools/enabled", "data_type/enabled", "strs_tools/enabled" ] +debug = [] [dependencies] -indexmap = "~1.8" +# indexmap = "~1.8" meta_tools = { workspace = true, features = [ "default" ] } iter_tools = { workspace = true, features = [ "default" ] } -data_type = { workspace = true, features = [ "default" ] } -strs_tools = { workspace = true, features = [ "default" ] } +# data_type = { workspace = true, features = [ "default" ] } +collection_tools = { workspace = true, features = [ "default" ] } +# strs_tools = { workspace = true, features = [ "default" ] } derive_tools = { workspace = true, features = [ "default" ] } # type_constructor ={ workspace = true, features = [ "default" ] } +former = { workspace = true, features = [ "default" ] } [dev-dependencies] test_tools = { workspace = true } diff --git a/module/move/graphs_tools/License b/module/move/graphs_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/move/graphs_tools/License +++ b/module/move/graphs_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/move/graphs_tools/Readme.md index d65e2f6913..7d87413ba4 100644 --- a/module/move/graphs_tools/Readme.md +++ b/module/move/graphs_tools/Readme.md @@ -2,15 +2,14 @@ # Module :: graphs_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_graphs_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_graphs_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/graphs_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/graphs_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fmove%2Fgraphs_tools%2Fexamples%2Fgraphs_tools_trivial.rs,RUN_POSTFIX=--example%20graphs_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_graphs_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_graphs_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/graphs_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/graphs_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fmove%2Fgraphs_tools%2Fexamples%2Fgraphs_tools_trivial.rs,RUN_POSTFIX=--example%20module%2Fmove%2Fgraphs_tools%2Fexamples%2Fgraphs_tools_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) -Graphs tools. +**NOT ready for production** + - ```rust #[ cfg( all( feature = "cell_factory", feature = "use_std" ) ) ] { @@ -37,3 +36,4 @@ cd wTools cd examples/graphs_tools_trivial cargo run ``` +--> diff --git a/module/move/graphs_tools/src/abs.rs b/module/move/graphs_tools/src/abs.rs new file mode 100644 index 0000000000..27e52613fb --- /dev/null +++ b/module/move/graphs_tools/src/abs.rs @@ -0,0 +1,85 @@ + +/// Define a private namespace for all its items. +mod private +{ + + pub use iter_tools::{ _IterTrait, IterTrait, BoxedIter }; + + use std:: + { + hash::Hash, + fmt, + }; + + /// + /// Interface to identify an instance of somthing, for exampel a node. + /// + + pub trait IdentityInterface + where + Self : + 'static + + Copy + + Hash + + fmt::Debug + + PartialEq + + Eq + , + { + } + + impl< T > IdentityInterface for T + where + T : + 'static + + Copy + + Hash + + fmt::Debug + + PartialEq + + Eq + , + { + } + + /// Uniquely identify a node. + pub trait NodeId : IdentityInterface + { + } + + /// Node itsef. + pub trait Node + { + } + + /// Represent directed graph. Can be zero-sized structure if nodes own all the information. + pub trait GraphDirected< 'a > + { + /// Uniquely identify a node. + type NodeId : NodeId; + /// Node itself. + type Node : Node + 'a; + + /// Get a reference on a node by its id. + fn node_ref( &'a self, node_id : Self::NodeId ) -> &'a Self::Node; + /// Get id by its node reference. + fn node_id( &self, node_id : &'a Self::Node ) -> Self::NodeId; + + /// Iterate over out nodes of + fn node_out_nodes( &'a self, node_id : Self::NodeId ) -> BoxedIter< 'a, Self::NodeId >; + + } + +} + +crate::mod_interface! +{ + own use + { + // _IterTrait, + IdentityInterface, + NodeId, + Node, + GraphDirected, + + }; +} diff --git a/module/move/graphs_tools/src/canonical.rs b/module/move/graphs_tools/src/canonical.rs new file mode 100644 index 0000000000..d17ad4b26c --- /dev/null +++ b/module/move/graphs_tools/src/canonical.rs @@ -0,0 +1,11 @@ + +/// Define a private namespace for all its items. +mod private +{ + +} + +crate::mod_interface! +{ + +} diff --git a/module/move/graphs_tools/src/debug.rs b/module/move/graphs_tools/src/debug.rs new file mode 100644 index 0000000000..d17ad4b26c --- /dev/null +++ b/module/move/graphs_tools/src/debug.rs @@ -0,0 +1,11 @@ + +/// Define a private namespace for all its items. +mod private +{ + +} + +crate::mod_interface! +{ + +} diff --git a/module/move/graphs_tools/src/lib.rs b/module/move/graphs_tools/src/lib.rs index e171ce3821..b55f5baca8 100644 --- a/module/move/graphs_tools/src/lib.rs +++ b/module/move/graphs_tools/src/lib.rs @@ -1,16 +1,9 @@ -#![ cfg_attr( feature = "no_std", no_std ) ] +// #![ cfg_attr( feature = "no_std", no_std ) ] #![ doc( html_logo_url = "https://raw.githubusercontent.com/Wandalen/wTools/alpha/asset/img/graph_logo_v1_trans.png" ) ] #![ doc( html_favicon_url = "https://raw.githubusercontent.com/Wandalen/wTools/alpha/asset/img/graph_logo_v1_trans.ico" ) ] #![ doc( html_root_url = "https://docs.rs/graphs_tools/latest/graphs_tools/" ) ] -// #![ deny( rust_2018_idioms ) ] -// #![ deny( missing_debug_implementations ) ] -// #![ deny( missing_docs ) ] #![ deny( unused_imports ) ] -// #![ feature( type_name_of_val ) ] -// #![ feature( type_alias_impl_trait ) ] -// #![ feature( trace_macros ) ] - //! //! Implementation of automata. //! @@ -19,24 +12,41 @@ #![ allow( unused_imports ) ] use iter_tools::iter; -use data_type::dt; -use meta_tools::meta; -use strs_tools::string; - +// use data_type::dt; +// use meta_tools::meta; +// use strs_tools::string; use meta_tools::mod_interface; +use former::Former; + +/// Define a private namespace for all its items. +mod private +{ +} + mod_interface! { + /// Abstract layer. - #[ cfg( not( feature = "no_std" ) ) ] layer abs; + + /// Search algorithms. + layer search; + /// Canonical representation. - #[ cfg( not( feature = "no_std" ) ) ] layer canonical; - /// Algorithms. - #[ cfg( not( feature = "no_std" ) ) ] - layer algo; - own use ::meta_tools::prelude::*; + /// For diagnostics only. + #[ cfg( feature = "debug" ) ] + layer debug; + + // /// Algorithms. + // #[ cfg( not( feature = "no_std" ) ) ] + // layer algo; + + /// Print tree. + layer tree_print; + + // own use ::meta_tools::prelude::*; } // zzz : implement checks diff --git a/module/move/graphs_tools/src/search.rs b/module/move/graphs_tools/src/search.rs new file mode 100644 index 0000000000..48b2b855be --- /dev/null +++ b/module/move/graphs_tools/src/search.rs @@ -0,0 +1,173 @@ +mod private +{ + use crate::*; + + /// Former of Options for searching. + pub fn options< 'a, Method, Graph, PreVisit, PostVisit >() -> OptionsFormer< 'a, Method, Graph, PreVisit, PostVisit > + where + Graph : crate::abs::GraphDirected< 'a > + ?Sized, + Method : super::Method, + PreVisit : OnVisit< 'a, Graph::Node >, + PostVisit : OnVisit< 'a, Graph::Node >, + { + Options::former() + } + + /// Options for configuring a graph search. + #[ derive( Debug, Default, Former ) ] + pub struct Options< 'a, Method, Graph, PreVisit = NopVisit, PostVisit = NopVisit > + where + Graph : crate::abs::GraphDirected< 'a > + ?Sized, + Method : super::Method, + PreVisit : OnVisit< 'a, Graph::Node >, + PostVisit : OnVisit< 'a, Graph::Node >, + { + /// Starting node ID for the search. + pub start_id : Graph::NodeId, + + /// Function to call on each pre-order visit of node. + pub pre_visit : PreVisit, + /// Function to call on each post-order visit of node. + pub post_visit : PostVisit, + + /// Method of searhcing. + pub method : Method, + /// Additional options specific to the search method. + pub _extra : Method::ExtraOptions, + /// Phantom data to associate types and lifetimes. + pub _phantom : std::marker::PhantomData< ( &'a (), ) >, + } + + impl< 'a, Method, Graph, PreVisit, PostVisit > Options< 'a, Method, Graph, PreVisit, PostVisit > + where + Graph : ForGraphDirected< 'a > + ?Sized, + Method : super::Method, + PreVisit : OnVisit< 'a, Graph::Node >, + PostVisit : OnVisit< 'a, Graph::Node >, + { + /// Search traversing each node in an order specified by method. + pub fn search( self, graph : &'a Graph ) + { + graph.search( self ) + } + } + + // xxx : adjust Former to eliminate need in this + impl< 'a, Method, Graph, PreVisit, PostVisit > OptionsFormer< 'a, Method, Graph, PreVisit, PostVisit > + where + Graph : ForGraphDirected< 'a > + ?Sized, + Method : super::Method, + PreVisit : OnVisit< 'a, Graph::Node >, + PostVisit : OnVisit< 'a, Graph::Node >, + { + + pub fn pre_visit_set( mut self, pre_visit : PreVisit ) -> Self + { + self.storage.pre_visit = Some( pre_visit ); + self + } + + pub fn post_visit_set( mut self, post_visit : PostVisit ) -> Self + { + self.storage.post_visit = Some( post_visit ); + self + } + + pub fn method_set( mut self, method : Method ) -> Self + { + self.storage.method = Some( method ); + self + } + + } + + /// Trait for performing searches on directed graphs. + pub trait ForGraphDirected< 'a > : crate::abs::GraphDirected< 'a > + { + /// Perform a search using specified options and method. + fn search< Method, PreVisit, PostVisit > + ( + &'a self, + o : Options< 'a, Method, Self, PreVisit, PostVisit >, + ) + where + Method : super::Method, + PreVisit : OnVisit< 'a, Self::Node >, + PostVisit : OnVisit< 'a, Self::Node >, + { + Method::_search( self, o ) + } + } + + impl< 'a, T > ForGraphDirected< 'a > for T + where + T : crate::abs::GraphDirected< 'a >, + { + } + + /// Trait for defining specific search strategies like DFS or BFS. + pub trait Method : Default + { + /// Additional options for the search method. + type ExtraOptions : Default; + + /// Execute the search on a graph. + fn _search< 'a, Graph, PreVisit, PostVisit > + ( + graph : &'a Graph, + o : Options< 'a, Self, Graph, PreVisit, PostVisit >, + ) + where + PreVisit : OnVisit< 'a, Graph::Node >, + PostVisit : OnVisit< 'a, Graph::Node >, + Graph : ForGraphDirected< 'a > + ?Sized, + Self : Sized; + } + + /// A function to call on visit, either pre-order or post-order. + pub trait OnVisit< 'a, Node > + { + /// Call itself. + fn call( &mut self, node : &'a Node ); + } + + /// No-op visit + #[ derive( Debug, Default ) ] + pub struct NopVisit; + impl< 'a, Node > OnVisit< 'a, Node > for NopVisit + { + fn call( &mut self, _node : &'a Node ) + { + } + } + + impl< 'a, Node, F > OnVisit< 'a, Node > for F + where + Node : 'a, + F : FnMut( &'a Node ), + { + fn call( &mut self, node : &'a Node ) + { + self( node ); + } + } + +} + +crate::mod_interface! +{ + layer + { + dfs, + bfs, + }; + own use + { + options, + Method, + Options, + ForGraphDirected, + OnVisit, + NopVisit + }; +} diff --git a/module/move/graphs_tools/src/search/bfs.rs b/module/move/graphs_tools/src/search/bfs.rs new file mode 100644 index 0000000000..c963b53f06 --- /dev/null +++ b/module/move/graphs_tools/src/search/bfs.rs @@ -0,0 +1,54 @@ +//! Breadth first search method. + +mod private +{ + use crate::*; + use search::{ Method, ForGraphDirected, Options, OnVisit }; + + /// Breadth-first search strategy. + #[ derive( Debug, Default ) ] + pub struct Bfs; + + impl Method for Bfs + { + type ExtraOptions = (); + + /// Perform breadth-first search on a graph. + fn _search< 'a, Graph, PreVisit, PostVisit > + ( + graph : &'a Graph, + mut o : Options< 'a, Self, Graph, PreVisit, PostVisit >, + ) + where + PreVisit : OnVisit< 'a, Graph::Node >, + PostVisit : OnVisit< 'a, Graph::Node >, + Graph : ForGraphDirected< 'a > + ?Sized, + { + let mut visited = collection_tools::HashSet::new(); + let mut queue = collection_tools::VecDeque::new(); + queue.push_back( o.start_id ); + + while let Some( node_id ) = queue.pop_front() + { + let node = graph.node_ref( node_id ); + if visited.insert( node_id ) + { + o.pre_visit.call( node ); + for child_id in graph.node_out_nodes( node_id ) + { + queue.push_back( child_id ); + } + } + } + } + } + +} + +crate::mod_interface! +{ + orphan use + { + Bfs, + }; +} diff --git a/module/move/graphs_tools/src/search/dfs.rs b/module/move/graphs_tools/src/search/dfs.rs new file mode 100644 index 0000000000..3443b5581f --- /dev/null +++ b/module/move/graphs_tools/src/search/dfs.rs @@ -0,0 +1,74 @@ +//! Depth first search method. + +mod private +{ + use crate::*; + use search::{ Method, ForGraphDirected, Options, OnVisit }; + + /// Depth-first search method. + #[ derive( Debug, Default ) ] + pub struct Dfs; + + impl Method for Dfs + { + type ExtraOptions = (); + +// node::0 +// ├─ node::1 +// │ ├─ node::4 +// │ ├─ node::5 +// ├─ node::2 +// ├─ node::3 +// │ ├─ node::6 +// │ ├─ node::7 + + /// Perform depth-first search on a graph. + fn _search< 'a, Graph, PreVisit, PostVisit > + ( + graph : &'a Graph, + mut o : Options< 'a, Self, Graph, PreVisit, PostVisit >, + ) + where + PreVisit : OnVisit< 'a, Graph::Node >, + PostVisit : OnVisit< 'a, Graph::Node >, + Graph : ForGraphDirected< 'a > + ?Sized, + { + let mut visited = collection_tools::HashSet::new(); + let mut stack = collection_tools::Vec::new(); + stack.push( ( o.start_id, true ) ); + + // while let Some( node_id ) = stack.pop() + while let Some( ( node_id, is_preorder ) ) = stack.pop() + { + let node = graph.node_ref( node_id ); + + if !is_preorder + { + o.post_visit.call( node ); + continue; + } + + if visited.insert( node_id ) + { + stack.push( ( node_id, false ) ); + o.pre_visit.call( node ); + for child_id in graph.node_out_nodes( node_id ).rev() + { + // o.post_visit.call( node ); + stack.push( ( child_id, true ) ); + } + } + } + } + + } + +} + +crate::mod_interface! +{ + orphan use + { + Dfs, + }; +} diff --git a/module/move/graphs_tools/src/tree_print.rs b/module/move/graphs_tools/src/tree_print.rs new file mode 100644 index 0000000000..e8ded60186 --- /dev/null +++ b/module/move/graphs_tools/src/tree_print.rs @@ -0,0 +1,219 @@ + +/// Define a private namespace for all its items. +mod private +{ + + use crate::*; + pub use iter_tools::{ _IterTrait, IterTrait, BoxedIter }; + + use std:: + { + hash::Hash, + fmt, + }; + +// /// Represent directed graph. Can be zero-sized structure if nodes own all the information. +// pub trait GraphDirected< 'a > +// { +// /// Uniquely identify a node. +// type NodeId : NodeId; +// /// Node itself. +// type Node : Node + 'a; +// +// /// Get a reference on a node by its id. +// fn node_ref( &'a self, node_id : Self::NodeId ) -> &'a Self::Node; +// /// Get id by its node reference. +// fn node_id( &self, node_id : &'a Self::Node ) -> Self::NodeId; +// +// /// Iterate over out nodes of +// fn node_out_nodes( &'a self, node_id : Self::NodeId ) -> BoxedIter< 'a, Self::NodeId >; +// +// } + + /// Print directed graph as a tree. + pub trait GraphDirectedPrintAsTree< 'g > + where + Self : abs::GraphDirected< 'g >, + { + + /// Write a graph into foromat stream with all nodes traversed by DFS. + fn write_as_dfs_tree< 'w >( &'g self, write : &'w mut ( dyn core::fmt::Write + 'w ), node_id : Self::NodeId ) -> fmt::Result + { + #![ allow( non_upper_case_globals ) ] + use iter_tools::Itertools; + const up_down : &str = "│ "; + const up_down_right : &str = "├─ "; + // const _left_right : &str = "─"; + // const _down_right : &str = "┌─"; + + let mut visited = collection_tools::HashSet::new(); + let mut stack = collection_tools::Vec::new(); + + let prefix = | level : isize | + { + let left = if level > 0 + { + std::iter::repeat( up_down ).take( ( level - 1 ) as usize ).join( " " ) + } + else + { + String::new() + }; + let right = if level > 0 + { + up_down_right + } + else + { + &String::new() + }; + return format!( "{}{}", left, right ); + }; + + let push = | stack : &mut collection_tools::Vec< ( Self::NodeId, isize, bool ) >, node_id, level, is_preorder | + { + // println!( "push {:?} level:{} is_preorder:{}", node_id, level, if is_preorder { 1 } else { 0 } ); + stack.push( ( node_id, level, is_preorder ) ); + }; + + push( &mut stack, node_id, 0, true ); + + while let Some( ( node_id, level, _preorder ) ) = stack.pop() + { + // if !is_preorder + // { + // write.write_fmt( format_args!( "{}{:?}\n", prefix( level ), node_id ) )?; + // continue; + // } + + if visited.insert( node_id ) + { + // push( &mut stack, node_id, level, false ); + write.write_fmt( format_args!( "{}{:?}\n", prefix( level ), node_id ) )?; + + for child_id in self.node_out_nodes( node_id ).rev() + { + push( &mut stack, child_id, level + 1, true ); + } + } + } + + return Ok( () ) + } + + /// Represent a graph as a string with all nodes traversed by DFS. + fn string_with_dfs_tree< 'w >( &'g self, node : Self::NodeId ) -> String + { + // let node = self.node_ref( node ); + let mut result = String::new(); + self.write_as_dfs_tree( &mut result, node ).unwrap(); + result + } + + /// Write a graph into foromat stream with all nodes traversed by BFS. + fn write_as_bfs_tree< 'w >( &'g self, write : &'w mut ( dyn core::fmt::Write + 'w ), node_id : Self::NodeId ) -> fmt::Result + { + #![ allow( non_upper_case_globals ) ] + use iter_tools::Itertools; + const up_down : &str = "│ "; + const up_down_right : &str = "├─ "; + // const _left_right : &str = "─"; + // const _down_right : &str = "┌─"; + + let mut level : isize = -1; + let mut visited = collection_tools::HashSet::new(); + let mut stack = collection_tools::Vec::new(); + let mut next = collection_tools::Vec::new(); + + let prefix = | level : isize | + { + let left = if level > 0 + { + std::iter::repeat( up_down ).take( ( level - 1 ) as usize ).join( " " ) + } + else + { + String::new() + }; + let right = if level > 0 + { + up_down_right + } + else + { + &String::new() + }; + return format!( "{}{}", left, right ); + }; + + let push = | next : &mut collection_tools::Vec< Self::NodeId >, node_id | + { + // println!( "push {:?}", node_id ); + next.insert( 0, node_id ); + }; + + push( &mut next, node_id ); + + while next.len() > 0 + { + + core::mem::swap( &mut stack, &mut next ); + next.clear(); + level += 1; + + while let Some( node_id ) = stack.pop() + { + + if visited.insert( node_id ) + { + write.write_fmt( format_args!( "{}{:?}\n", prefix( level ), node_id ) )?; + for child_id in self.node_out_nodes( node_id ) + { + push( &mut next, child_id ); + } + } + + } + + } + return Ok( () ) + } + + /// Represent a graph as a string with all nodes traversed by BFS. + fn string_with_bfs_tree< 'w >( &'g self, node : Self::NodeId ) -> String + { + // let node = self.node_ref( node ); + let mut result = String::new(); + self.write_as_bfs_tree( &mut result, node ).unwrap(); + result + } + + } + + impl< 'g, T > GraphDirectedPrintAsTree< 'g > for T + where + Self : abs::GraphDirected< 'g >, + { + } + + // impl fmt::Debug for Context< '_ > + // { + // fn fmt( &self, c : &mut fmt::Formatter< '_ > ) -> fmt::Result + // { + // c + // .debug_struct( "Context" ) + // .field( "buf", &"dyn fmt::Write" ) + // .field( "printer", &self.printer ) + // .finish() + // } + // } + +} + +crate::mod_interface! +{ + own use + { + GraphDirectedPrintAsTree, + }; +} diff --git a/module/move/assistant/tests/inc/experiment.rs b/module/move/graphs_tools/tests/inc/basic_test.rs similarity index 100% rename from module/move/assistant/tests/inc/experiment.rs rename to module/move/graphs_tools/tests/inc/basic_test.rs diff --git a/module/move/graphs_tools/tests/inc/graph.rs b/module/move/graphs_tools/tests/inc/graph.rs new file mode 100644 index 0000000000..50a3c2b023 --- /dev/null +++ b/module/move/graphs_tools/tests/inc/graph.rs @@ -0,0 +1,3 @@ +use super::*; + +pub mod map_of_nodes; diff --git a/module/move/graphs_tools/tests/inc/graph/map_of_nodes.rs b/module/move/graphs_tools/tests/inc/graph/map_of_nodes.rs new file mode 100644 index 0000000000..eaff7ef477 --- /dev/null +++ b/module/move/graphs_tools/tests/inc/graph/map_of_nodes.rs @@ -0,0 +1,183 @@ +use super::*; + +use derive_tools::From; +use the_module::abs; +use iter_tools::{ _IterTrait, IterTrait, BoxedIter }; +use std::fmt; + +#[ derive( Debug ) ] +pub struct Node +{ + pub id : NodeId, + pub children : Vec< NodeId >, +} + +impl the_module::abs::Node for Node {} + +#[ allow( dead_code ) ] +impl Node +{ + pub fn new< IntoId : Into< NodeId > >( id : IntoId ) -> Node + { + Node + { + id : id.into(), + children : Vec::new(), + } + } + + pub fn child_add( &mut self, child : &Node ) -> &mut Self + { + self.children.push( child.id ); + self + } + + pub fn children_add< 'a, I >( &mut self, nodes : I ) -> &mut Self + where + I : IntoIterator< Item = &'a Node >, + { + for node in nodes + { + self.children.push( node.id ); + } + self + } + +} + +#[ derive( Default ) ] +pub struct Graph +{ + nodes : HashMap< NodeId, Node >, +} + +#[ allow( dead_code ) ] +impl Graph +{ + + pub fn node_add( &mut self, node : Node ) + { + self.nodes.insert( node.id, node ); + } + + pub fn nodes_add< 'a, I >( &mut self, nodes : I ) -> &mut Self + where + I : IntoIterator< Item = Node >, + { + for node in nodes + { + self.nodes.insert( node.id, node ); + } + self + } + +} + +impl< 'a > abs::GraphDirected< 'a > for Graph +{ + + type NodeId = NodeId; + type Node = Node; + + fn node_ref( &'a self, node_id : NodeId ) -> &'a Node + { + self.nodes.get( &node_id ).expect( "If id exist then node shoudl also exist" ) + } + + fn node_id( &self, node : &Node ) -> NodeId + { + node.id + } + + fn node_out_nodes( &'a self, node_id : NodeId ) -> BoxedIter< 'a, Self::NodeId > + { + if let Some( node ) = self.nodes.get( &node_id ) + { + Box::new( node.children.iter().cloned() ) + } + else + { + Box::new( std::iter::empty() ) + } + } +} + +#[ derive( Copy, Clone, Hash, PartialEq, Eq, From ) ] +pub struct NodeId( usize ); + +impl fmt::Debug for NodeId +{ + fn fmt( &self, c : &mut fmt::Formatter< '_ > ) -> fmt::Result + { + c + .write_fmt( format_args!( "node::{:?}", self.0 ) ) + } +} + +impl the_module::abs::NodeId for NodeId {} + +// Constructors + +#[ allow( dead_code ) ] +impl Graph +{ + + pub fn duplet() -> Self + { + + // Create nodes + let mut node0 = Node::new( 0 ); + let node1 = Node::new( 1 ); + let node2 = Node::new( 2 ); + + // Set up the graph structure + node0.children_add([ &node1, &node2 ]); + + let mut graph = Self::default(); + graph.nodes_add([ node0, node1, node2 ]); + + graph + } + + pub fn duplet_assymetric() -> Self + { + + // Create nodes + let mut node0 = Node::new( 0 ); + let node1 = Node::new( 1 ); + let mut node2 = Node::new( 2 ); + let node3 = Node::new( 3 ); + + node0.children_add([ &node1, &node2 ]); + node2.children_add([ &node3 ]); + + let mut graph = Self::default(); + graph.nodes_add([ node0, node1, node2, node3 ]); + + graph + } + + pub fn triplet_with_double_legs() -> Self + { + + // Create nodes + let mut node0 = Node::new( 0 ); + let mut node1 = Node::new( 1 ); + let node2 = Node::new( 2 ); + let mut node3 = Node::new( 3 ); + let node4 = Node::new( 4 ); + let node5 = Node::new( 5 ); + let node6 = Node::new( 6 ); + let node7 = Node::new( 7 ); + + node0.children_add([ &node1, &node2, &node3 ]); + node1.children_add([ &node4, &node5 ]); + node3.children_add([ &node6, &node7 ]); + + let mut graph = Self::default(); + graph.nodes_add([ node0, node1, node2, node3, node4, node5, node6, node7 ]); + + graph + } + +} diff --git a/module/move/graphs_tools/tests/inc/mod.rs b/module/move/graphs_tools/tests/inc/mod.rs index 56d3aaf445..17a45e6d11 100644 --- a/module/move/graphs_tools/tests/inc/mod.rs +++ b/module/move/graphs_tools/tests/inc/mod.rs @@ -1,15 +1,10 @@ #![ allow( unused_imports ) ] use super::*; -use std::collections::HashSet; -// use wtools::prelude::*; -#[ cfg( not( feature = "no_std" ) ) ] -mod canonical_node_test; -#[ cfg( not( feature = "no_std" ) ) ] -// mod cell_factory_test; -// #[ cfg( not( feature = "no_std" ) ) ] -mod factory_test; -#[ cfg( not( feature = "no_std" ) ) ] -mod identity_test; -mod factory_impls; +pub mod graph; + +mod basic_test; +mod nodes_test; +mod search_test; +mod tree_print_test; \ No newline at end of file diff --git a/module/move/graphs_tools/tests/inc/nodes_test.rs b/module/move/graphs_tools/tests/inc/nodes_test.rs new file mode 100644 index 0000000000..530d84e27c --- /dev/null +++ b/module/move/graphs_tools/tests/inc/nodes_test.rs @@ -0,0 +1,119 @@ +// use super::*; +// +// use derive_tools::From; +// +// #[ derive( Debug ) ] +// struct Node< 'a > +// { +// id : NodeId, +// children : Vec< &'a Node< 'a > >, +// } +// +// impl< 'a > Node< 'a > +// { +// fn new< IntoId : Into< NodeId > >( id : IntoId ) -> Node< 'a > +// { +// Node +// { +// id : id.into(), +// children : Vec::new(), +// } +// } +// +// fn child_add( &mut self, child : &'a Node< 'a > ) -> &mut Self +// { +// self.children.push( child ); +// self +// } +// } +// +// struct Graph< 'a > +// { +// nodes : HashMap< NodeId, &'a Node< 'a > >, +// } +// +// impl< 'a > Graph< 'a > +// { +// fn new() -> Graph< 'a > +// { +// Graph +// { +// nodes : HashMap::new(), +// } +// } +// +// fn add_node( &mut self, node : &'a Node< 'a > ) +// { +// self.nodes.insert( node.id, node ); +// } +// +// fn node_ref( &self, node_id : NodeId ) -> Option< &'a Node< 'a > > +// { +// self.nodes.get( &node_id ).copied() +// } +// +// fn node_id( node : &'a Node< 'a > ) -> NodeId +// { +// node.id +// } +// +// fn node_out_nodes( &self, node_id : NodeId ) -> Box< dyn Iterator< Item = NodeId > + 'a > +// { +// if let Some( node ) = self.nodes.get( &node_id ) +// { +// Box::new( node.children.iter().map( | child | child.id ) ) +// } +// else +// { +// Box::new( std::iter::empty() ) +// } +// } +// } +// +// #[ derive( Debug, Copy, Clone, Hash, PartialEq, Eq, From ) ] +// struct NodeId( usize ); +// +// impl the_module::abs::NodeId for NodeId {} +// +// #[ test ] +// fn basic() +// { +// +// // test +// +// let mut node1 = Node::new( NodeId( 1 ) ); +// let node2 = Node::new( NodeId( 2 ) ); +// let node3 = Node::new( NodeId( 3 ) ); +// let node4 = Node::new( NodeId( 4 ) ); +// +// node1 +// .child_add( &node2 ) +// .child_add( &node3 ) +// .child_add( &node4 ); +// +// let mut graph = Graph::new(); +// graph.add_node( &node1 ); +// graph.add_node( &node2 ); +// graph.add_node( &node3 ); +// graph.add_node( &node4 ); +// +// // Assert that the root node is correctly retrieved +// assert_eq!( graph.node_ref( NodeId( 1 ) ).unwrap().id, NodeId( 1 ) ); +// +// // Assert that the root node has the correct children +// let out_nodes : Vec< NodeId > = graph.node_out_nodes( NodeId( 1 ) ).collect(); +// assert_eq!( out_nodes, vec![ NodeId( 2 ), NodeId( 3 ), NodeId( 4 ) ] ); +// +// // Print statements for debugging +// println!( "{:?}", graph.node_ref( NodeId( 1 ) ) ); +// println!( "{:?}", out_nodes ); +// +// // Assert that the root node structure is as expected +// assert_eq!( node1.id, NodeId( 1 ) ); +// assert_eq!( node1.children.len(), 3 ); +// assert_eq!( node1.children[ 0 ].id, NodeId( 2 ) ); +// assert_eq!( node1.children[ 1 ].id, NodeId( 3 ) ); +// assert_eq!( node1.children[ 2 ].id, NodeId( 4 ) ); +// +// println!( "{:?}", node1 ); +// } diff --git a/module/move/graphs_tools/tests/inc/search_test.rs b/module/move/graphs_tools/tests/inc/search_test.rs new file mode 100644 index 0000000000..c956e9305b --- /dev/null +++ b/module/move/graphs_tools/tests/inc/search_test.rs @@ -0,0 +1,4 @@ +use super::*; + +mod dfs_test; +mod bfs_test; diff --git a/module/move/graphs_tools/tests/inc/search_test/bfs_test.rs b/module/move/graphs_tools/tests/inc/search_test/bfs_test.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/module/move/graphs_tools/tests/inc/search_test/bfs_test.rs @@ -0,0 +1 @@ + diff --git a/module/move/graphs_tools/tests/inc/search_test/dfs_test.rs b/module/move/graphs_tools/tests/inc/search_test/dfs_test.rs new file mode 100644 index 0000000000..f3175b9d64 --- /dev/null +++ b/module/move/graphs_tools/tests/inc/search_test/dfs_test.rs @@ -0,0 +1,106 @@ +use super::*; + +// #[ path = "../graph.rs" ] +// mod graph; + +use graph::map_of_nodes:: +{ + Node, NodeId, Graph, +}; + +// = + +#[ test ] +fn test_dfs_manual() +{ + // use the_module::search; + // use the_module::abs; + use the_module::search::{ ForGraphDirected, NopVisit }; + let graph = Graph::triplet_with_double_legs(); + + // Prepare a vector to collect visited nodes + let mut pre_visited_nodes = Vec::new(); + let pre_visit = | node : &Node | + { + pre_visited_nodes.push( node.id ); + println!( "pre visiting {:?}", node.id ); + }; + + let mut post_visited_nodes = Vec::new(); + let post_visit = | node : &Node | + { + post_visited_nodes.push( node.id ); + println!( "post visiting {:?}", node.id ); + }; + + // Create search options + let search_options = the_module::search::Options + { + start_id : 0.into(), + pre_visit, + post_visit, + method : the_module::search::Dfs, + _extra : (), + _phantom : Default::default(), + }; + + // Perform DFS + graph.search( search_options ); + + // Assert the order of visited nodes + assert_eq!( pre_visited_nodes, into_vec![ 0, 1, 4, 5, 2, 3, 6, 7 ] ); + assert_eq!( post_visited_nodes, into_vec![ 4, 5, 1, 2, 6, 7, 3, 0 ] ); + +} + +// = + +#[ test ] +fn test_dfs() +{ + // use the_module::search; + // use the_module::abs; + use the_module::search::{ ForGraphDirected, NopVisit }; + let graph = Graph::triplet_with_double_legs(); + + // Prepare a vector to collect visited nodes + let mut pre_visited_nodes = Vec::new(); + let pre_visit = | node : &Node | + { + pre_visited_nodes.push( node.id ); + println!( "pre visiting {:?}", node.id ); + }; + + let mut post_visited_nodes = Vec::new(); + let post_visit = | node : &Node | + { + post_visited_nodes.push( node.id ); + println!( "post visiting {:?}", node.id ); + }; + + // Create search options + the_module::search::options() + .start_id( 0 ) + .pre_visit_set( pre_visit ) + .post_visit_set( post_visit ) + .method_set( the_module::search::Dfs ) + .form() + .search( &graph ) + ; + + // Assert the order of visited nodes + assert_eq!( pre_visited_nodes, into_vec![ 0, 1, 4, 5, 2, 3, 6, 7 ] ); + assert_eq!( post_visited_nodes, into_vec![ 4, 5, 1, 2, 6, 7, 3, 0 ] ); + + // node::0 + // ├─ node::1 + // │ ├─ node::4 + // │ ├─ node::5 + // ├─ node::2 + // ├─ node::3 + // │ ├─ node::6 + // │ ├─ node::7 + +} + +// xxx \ No newline at end of file diff --git a/module/move/graphs_tools/tests/inc/tree_print_test.rs b/module/move/graphs_tools/tests/inc/tree_print_test.rs new file mode 100644 index 0000000000..44f664060f --- /dev/null +++ b/module/move/graphs_tools/tests/inc/tree_print_test.rs @@ -0,0 +1,74 @@ +use super::*; + +use graph::map_of_nodes:: +{ + Node, NodeId, Graph, +}; + +// = + +#[ test ] +fn write_as_dfs_tree() +{ + use the_module::tree_print::GraphDirectedPrintAsTree; + let graph = Graph::duplet_assymetric(); + + let mut got = String::new(); + let r = graph.write_as_dfs_tree( &mut got, 0.into() ); + let exp = r#"node::0 +├─ node::1 +├─ node::2 +│ ├─ node::3 +"#; + println!( "{}", got ); + assert_eq!( got, exp ); + assert!( r.is_ok() ); + +} + +// + +#[ test ] +fn string_with_dfs_tree() +{ + use the_module::tree_print::GraphDirectedPrintAsTree; + let graph = Graph::triplet_with_double_legs(); + + let got = graph.string_with_dfs_tree( 0.into() ); + println!( "{}", got ); + let exp = r#"node::0 +├─ node::1 +│ ├─ node::4 +│ ├─ node::5 +├─ node::2 +├─ node::3 +│ ├─ node::6 +│ ├─ node::7 +"#; + assert_eq!( got, exp ); + +} + +// + +#[ test ] +fn string_with_bfs_tree() +{ + use the_module::tree_print::GraphDirectedPrintAsTree; + let graph = Graph::triplet_with_double_legs(); + + let got = graph.string_with_bfs_tree( 0.into() ); + println!( "{}", got ); + let exp = r#"node::0 +├─ node::1 +├─ node::2 +├─ node::3 +│ ├─ node::4 +│ ├─ node::5 +│ ├─ node::6 +│ ├─ node::7 +"#; + println!( "{}", got ); + assert_eq!( got, exp ); + +} diff --git a/module/move/graphs_tools/tests/smoke_test.rs b/module/move/graphs_tools/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/move/graphs_tools/tests/smoke_test.rs +++ b/module/move/graphs_tools/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/move/graphs_tools_deprecated/Cargo.toml b/module/move/graphs_tools_deprecated/Cargo.toml new file mode 100644 index 0000000000..d875799b47 --- /dev/null +++ b/module/move/graphs_tools_deprecated/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "graphs_tools_deprecated" +version = "0.3.0" +edition = "2021" +authors = [ + "Kostiantyn Wandalen ", + "Dmytro Kryvoruchko ", +] +license = "MIT" +readme = "Readme.md" +documentation = "https://docs.rs/graphs_tools" +repository = "https://github.com/Wandalen/wTools/tree/master/module/core/graphs_tools" +homepage = "https://github.com/Wandalen/wTools/tree/master/module/core/graphs_tools" +description = """ +Graphs tools. +""" +categories = [ "algorithms", "development-tools" ] +keywords = [ "fundamental", "general-purpose" ] + +[lints] +workspace = true + +[package.metadata.docs.rs] +features = [ "full" ] +all-features = false + + +[features] +default = [ + "enabled" +] +full = [ + "enabled", +] +no_std = [] +use_alloc = [ "no_std" ] +enabled = [ "meta_tools/enabled", "iter_tools/enabled", "data_type/enabled", "strs_tools/enabled" ] + +[dependencies] +indexmap = "~1.8" +meta_tools = { workspace = true, features = [ "default" ] } +iter_tools = { workspace = true, features = [ "default" ] } +data_type = { workspace = true, features = [ "default" ] } +strs_tools = { workspace = true, features = [ "default" ] } +derive_tools = { workspace = true, features = [ "default" ] } +# type_constructor ={ workspace = true, features = [ "default" ] } + +[dev-dependencies] +test_tools = { workspace = true } diff --git a/module/move/graphs_tools_deprecated/License b/module/move/graphs_tools_deprecated/License new file mode 100644 index 0000000000..72c80c1308 --- /dev/null +++ b/module/move/graphs_tools_deprecated/License @@ -0,0 +1,22 @@ +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 +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/graphs_tools_deprecated/Readme.md b/module/move/graphs_tools_deprecated/Readme.md new file mode 100644 index 0000000000..88273b115b --- /dev/null +++ b/module/move/graphs_tools_deprecated/Readme.md @@ -0,0 +1,39 @@ + + +# Module :: graphs_tools + + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_graphs_tools_deprecated_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_graphs_tools_deprecated_push.yml) [![docs.rs](https://img.shields.io/docsrs/graphs_tools_deprecated?color=e3e8f0&logo=docs.rs)](https://docs.rs/graphs_tools_deprecated) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fmove%2Fgraphs_tools_deprecated%2Fexamples%2Fgraphs_tools_trivial.rs,RUN_POSTFIX=--example%20module%2Fmove%2Fgraphs_tools_deprecated%2Fexamples%2Fgraphs_tools_trivial.rs/https://github.com/Wandalen/wTools) [![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/graphs_tools_deprecated/examples/graphs_tools_trivial.rs b/module/move/graphs_tools_deprecated/examples/graphs_tools_trivial.rs new file mode 100644 index 0000000000..b985090463 --- /dev/null +++ b/module/move/graphs_tools_deprecated/examples/graphs_tools_trivial.rs @@ -0,0 +1,11 @@ +//! qqq : write proper description +fn main() +{ + // xxx : fix me + // use graphs_tools::prelude::*; + // let node : graphs_tools::canonical::Node = from!( 13 ); + // assert_eq!( node.id(), 13.into() ); + // println!( "{:?}", node ); + /* print : node::13 */ +} + diff --git a/module/move/graphs_tools/src/abs/edge.rs b/module/move/graphs_tools_deprecated/src/abs/edge.rs similarity index 94% rename from module/move/graphs_tools/src/abs/edge.rs rename to module/move/graphs_tools_deprecated/src/abs/edge.rs index 550a350efb..214f8f10d9 100644 --- a/module/move/graphs_tools/src/abs/edge.rs +++ b/module/move/graphs_tools_deprecated/src/abs/edge.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::prelude::*; diff --git a/module/move/graphs_tools/src/abs/factory.rs b/module/move/graphs_tools_deprecated/src/abs/factory.rs similarity index 99% rename from module/move/graphs_tools/src/abs/factory.rs rename to module/move/graphs_tools_deprecated/src/abs/factory.rs index 0f6d19e324..ad6bc76c8b 100644 --- a/module/move/graphs_tools/src/abs/factory.rs +++ b/module/move/graphs_tools_deprecated/src/abs/factory.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::prelude::*; diff --git a/module/move/graphs_tools/src/abs/id_generator.rs b/module/move/graphs_tools_deprecated/src/abs/id_generator.rs similarity index 94% rename from module/move/graphs_tools/src/abs/id_generator.rs rename to module/move/graphs_tools_deprecated/src/abs/id_generator.rs index 943315c041..2090439804 100644 --- a/module/move/graphs_tools/src/abs/id_generator.rs +++ b/module/move/graphs_tools_deprecated/src/abs/id_generator.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { // use crate::prelude::*; diff --git a/module/move/graphs_tools/src/abs/identity.rs b/module/move/graphs_tools_deprecated/src/abs/identity.rs similarity index 97% rename from module/move/graphs_tools/src/abs/identity.rs rename to module/move/graphs_tools_deprecated/src/abs/identity.rs index 412b759d73..ba548c94dc 100644 --- a/module/move/graphs_tools/src/abs/identity.rs +++ b/module/move/graphs_tools_deprecated/src/abs/identity.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { // use crate::prelude::*; @@ -35,6 +35,7 @@ mod private , { } + // // /// // /// Interface to identify an instance of somthing with ability to increase it to generate a new one. diff --git a/module/move/graphs_tools/src/abs/mod.rs b/module/move/graphs_tools_deprecated/src/abs/mod.rs similarity index 100% rename from module/move/graphs_tools/src/abs/mod.rs rename to module/move/graphs_tools_deprecated/src/abs/mod.rs diff --git a/module/move/graphs_tools/src/abs/node.rs b/module/move/graphs_tools_deprecated/src/abs/node.rs similarity index 95% rename from module/move/graphs_tools/src/abs/node.rs rename to module/move/graphs_tools_deprecated/src/abs/node.rs index b227581718..703bd0893d 100644 --- a/module/move/graphs_tools/src/abs/node.rs +++ b/module/move/graphs_tools_deprecated/src/abs/node.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::prelude::*; diff --git a/module/move/graphs_tools/src/algo/dfs.rs b/module/move/graphs_tools_deprecated/src/algo/dfs.rs similarity index 88% rename from module/move/graphs_tools/src/algo/dfs.rs rename to module/move/graphs_tools_deprecated/src/algo/dfs.rs index 0a75884e2c..13e7c81e84 100644 --- a/module/move/graphs_tools/src/algo/dfs.rs +++ b/module/move/graphs_tools_deprecated/src/algo/dfs.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::prelude::*; diff --git a/module/move/graphs_tools/src/algo/mod.rs b/module/move/graphs_tools_deprecated/src/algo/mod.rs similarity index 100% rename from module/move/graphs_tools/src/algo/mod.rs rename to module/move/graphs_tools_deprecated/src/algo/mod.rs diff --git a/module/move/graphs_tools/src/canonical/edge.rs b/module/move/graphs_tools_deprecated/src/canonical/edge.rs similarity index 96% rename from module/move/graphs_tools/src/canonical/edge.rs rename to module/move/graphs_tools_deprecated/src/canonical/edge.rs index 3bf782aaee..4d02b207d4 100644 --- a/module/move/graphs_tools/src/canonical/edge.rs +++ b/module/move/graphs_tools_deprecated/src/canonical/edge.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::prelude::*; diff --git a/module/move/graphs_tools/src/canonical/factory_generative.rs b/module/move/graphs_tools_deprecated/src/canonical/factory_generative.rs similarity index 98% rename from module/move/graphs_tools/src/canonical/factory_generative.rs rename to module/move/graphs_tools_deprecated/src/canonical/factory_generative.rs index ba735895c4..1e2e838081 100644 --- a/module/move/graphs_tools/src/canonical/factory_generative.rs +++ b/module/move/graphs_tools_deprecated/src/canonical/factory_generative.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::prelude::*; diff --git a/module/move/graphs_tools/src/canonical/factory_impl.rs b/module/move/graphs_tools_deprecated/src/canonical/factory_impl.rs similarity index 100% rename from module/move/graphs_tools/src/canonical/factory_impl.rs rename to module/move/graphs_tools_deprecated/src/canonical/factory_impl.rs diff --git a/module/move/graphs_tools/src/canonical/factory_readable.rs b/module/move/graphs_tools_deprecated/src/canonical/factory_readable.rs similarity index 98% rename from module/move/graphs_tools/src/canonical/factory_readable.rs rename to module/move/graphs_tools_deprecated/src/canonical/factory_readable.rs index 9ec9bf6012..c82868fbc1 100644 --- a/module/move/graphs_tools/src/canonical/factory_readable.rs +++ b/module/move/graphs_tools_deprecated/src/canonical/factory_readable.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::prelude::*; @@ -71,7 +71,6 @@ mod private where NodeId : IdentityInterface, EdgeId : IdentityInterface, - { index! { @@ -88,7 +87,6 @@ mod private where EdgeId : IdentityInterface, NodeId : IdentityInterface, - { index! { diff --git a/module/move/graphs_tools/src/canonical/identity.rs b/module/move/graphs_tools_deprecated/src/canonical/identity.rs similarity index 98% rename from module/move/graphs_tools/src/canonical/identity.rs rename to module/move/graphs_tools_deprecated/src/canonical/identity.rs index 90b53e8879..83f29837a9 100644 --- a/module/move/graphs_tools/src/canonical/identity.rs +++ b/module/move/graphs_tools_deprecated/src/canonical/identity.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::prelude::*; @@ -54,7 +54,6 @@ mod private // // zzz : implement IdentityGenerableInterface for other identities. make it working - // zzz : use type constructors // types! // { diff --git a/module/move/graphs_tools/src/canonical/mod.rs b/module/move/graphs_tools_deprecated/src/canonical/mod.rs similarity index 100% rename from module/move/graphs_tools/src/canonical/mod.rs rename to module/move/graphs_tools_deprecated/src/canonical/mod.rs diff --git a/module/move/graphs_tools/src/canonical/node.rs b/module/move/graphs_tools_deprecated/src/canonical/node.rs similarity index 98% rename from module/move/graphs_tools/src/canonical/node.rs rename to module/move/graphs_tools_deprecated/src/canonical/node.rs index 94d7f7d313..dbc11c2e85 100644 --- a/module/move/graphs_tools/src/canonical/node.rs +++ b/module/move/graphs_tools_deprecated/src/canonical/node.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::prelude::*; @@ -109,7 +109,6 @@ mod private where NodeId : IdentityInterface, EdgeId : IdentityInterface, - { } @@ -160,7 +159,6 @@ mod private where NodeId : IdentityInterface, EdgeId : IdentityInterface, - { fn eq( &self, other : &Self ) -> bool { @@ -173,7 +171,6 @@ mod private where NodeId : IdentityInterface, EdgeId : IdentityInterface, - {} } diff --git a/module/move/graphs_tools_deprecated/src/lib.rs b/module/move/graphs_tools_deprecated/src/lib.rs new file mode 100644 index 0000000000..e171ce3821 --- /dev/null +++ b/module/move/graphs_tools_deprecated/src/lib.rs @@ -0,0 +1,56 @@ +#![ cfg_attr( feature = "no_std", no_std ) ] +#![ doc( html_logo_url = "https://raw.githubusercontent.com/Wandalen/wTools/alpha/asset/img/graph_logo_v1_trans.png" ) ] +#![ doc( html_favicon_url = "https://raw.githubusercontent.com/Wandalen/wTools/alpha/asset/img/graph_logo_v1_trans.ico" ) ] +#![ doc( html_root_url = "https://docs.rs/graphs_tools/latest/graphs_tools/" ) ] +// #![ deny( rust_2018_idioms ) ] +// #![ deny( missing_debug_implementations ) ] +// #![ deny( missing_docs ) ] +#![ deny( unused_imports ) ] + +// #![ feature( type_name_of_val ) ] +// #![ feature( type_alias_impl_trait ) ] +// #![ feature( trace_macros ) ] + +//! +//! Implementation of automata. +//! + +#![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] + +#![ allow( unused_imports ) ] +use iter_tools::iter; +use data_type::dt; +use meta_tools::meta; +use strs_tools::string; + +use meta_tools::mod_interface; +mod_interface! +{ + /// Abstract layer. + #[ cfg( not( feature = "no_std" ) ) ] + layer abs; + /// Canonical representation. + #[ cfg( not( feature = "no_std" ) ) ] + layer canonical; + /// Algorithms. + #[ cfg( not( feature = "no_std" ) ) ] + layer algo; + + own use ::meta_tools::prelude::*; +} + +// zzz : implement checks +// +// - graph is connected +// - graph is complete +// - graph is isomorphic with another graph +// - graph get regularity degree +// - graph is bipartite +// - graph decomposition on cycles +// - graph decomposition on connected components +// +// - node get open neighbourhood? +// - node get closed neighbourhood? +// - node get degree ( nodes ) +// - node get size ( edges ) +// diff --git a/module/move/graphs_tools_deprecated/tests/graphs_tools_tests.rs b/module/move/graphs_tools_deprecated/tests/graphs_tools_tests.rs new file mode 100644 index 0000000000..74cedc3fe6 --- /dev/null +++ b/module/move/graphs_tools_deprecated/tests/graphs_tools_tests.rs @@ -0,0 +1,10 @@ + +// #![ feature( type_name_of_val ) ] +// #![ feature( type_alias_impl_trait ) ] + +#[ allow( unused_imports ) ] +use graphs_tools as the_module; +#[ allow( unused_imports ) ] +use test_tools::exposed::*; + +mod inc; diff --git a/module/move/graphs_tools/tests/inc/canonical_node_test.rs b/module/move/graphs_tools_deprecated/tests/inc/canonical_node_test.rs similarity index 100% rename from module/move/graphs_tools/tests/inc/canonical_node_test.rs rename to module/move/graphs_tools_deprecated/tests/inc/canonical_node_test.rs diff --git a/module/move/graphs_tools/tests/inc/cell_factory_test.rs b/module/move/graphs_tools_deprecated/tests/inc/cell_factory_test.rs similarity index 100% rename from module/move/graphs_tools/tests/inc/cell_factory_test.rs rename to module/move/graphs_tools_deprecated/tests/inc/cell_factory_test.rs diff --git a/module/move/graphs_tools/tests/inc/factory_impls.rs b/module/move/graphs_tools_deprecated/tests/inc/factory_impls.rs similarity index 100% rename from module/move/graphs_tools/tests/inc/factory_impls.rs rename to module/move/graphs_tools_deprecated/tests/inc/factory_impls.rs diff --git a/module/move/graphs_tools/tests/inc/factory_test.rs b/module/move/graphs_tools_deprecated/tests/inc/factory_test.rs similarity index 100% rename from module/move/graphs_tools/tests/inc/factory_test.rs rename to module/move/graphs_tools_deprecated/tests/inc/factory_test.rs diff --git a/module/move/graphs_tools/tests/inc/identity_test.rs b/module/move/graphs_tools_deprecated/tests/inc/identity_test.rs similarity index 100% rename from module/move/graphs_tools/tests/inc/identity_test.rs rename to module/move/graphs_tools_deprecated/tests/inc/identity_test.rs diff --git a/module/move/graphs_tools_deprecated/tests/inc/mod.rs b/module/move/graphs_tools_deprecated/tests/inc/mod.rs new file mode 100644 index 0000000000..56d3aaf445 --- /dev/null +++ b/module/move/graphs_tools_deprecated/tests/inc/mod.rs @@ -0,0 +1,15 @@ +#![ allow( unused_imports ) ] + +use super::*; +use std::collections::HashSet; +// use wtools::prelude::*; + +#[ cfg( not( feature = "no_std" ) ) ] +mod canonical_node_test; +#[ cfg( not( feature = "no_std" ) ) ] +// mod cell_factory_test; +// #[ cfg( not( feature = "no_std" ) ) ] +mod factory_test; +#[ cfg( not( feature = "no_std" ) ) ] +mod identity_test; +mod factory_impls; diff --git a/module/move/graphs_tools_deprecated/tests/smoke_test.rs b/module/move/graphs_tools_deprecated/tests/smoke_test.rs new file mode 100644 index 0000000000..c9b1b4daae --- /dev/null +++ b/module/move/graphs_tools_deprecated/tests/smoke_test.rs @@ -0,0 +1,13 @@ +//! Smoke testing of the package. + +#[ 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/gspread/.secret/readme.md b/module/move/gspread/.secret/readme.md new file mode 100644 index 0000000000..1fa27de082 --- /dev/null +++ b/module/move/gspread/.secret/readme.md @@ -0,0 +1,75 @@ +# Getting API Keys for OAuth Authentication + +Follow these steps to create and configure your OAuth credentials for using Google APIs. + +## 1. Configure Consent Screen + +1. Go to the [Google API Console](https://console.developers.google.com/). +2. From the projects list, select an existing project or create a new one. +3. Go to **OAuth consent screen** +4. Choose **Extrenal** User Type +5. Fill **App name**, **User support email** and **Developer contact information**. Click **continue** +6. Click on **ADD OR REMOVE SCOPES** +7. Add **.../auth/userinfo.email** and **.../auth/userinfo.profile** spoces. +8. Finish configuration + +## 2. Enable Google Sheets API + +1. Go to the [Google API Console](https://console.developers.google.com/). +2. In the left side menu, select **Enabled APIs & Services**. +3. Click on **ENABLE APIS AND SERVICES** +4. Search for **Google Sheets API** +5. Click on **Enable** + +## 2. Create API Credentials + +1. Go to the [Google API Console](https://console.developers.google.com/). +2. From the projects list, select an existing project or create a new one. +3. In the left side menu, select **APIs & Services**. +4. On the left menu, click **Credentials**. + +### 1-1. Service Account +1. Click **Create Credentials** and select **Service account**. +2. Enter a name of your app. Then put on the **Done** button. +3. Click on app email in section **Service Account**. After put on the **Keys**. +4. Create new keys in JSON type. + +### 1-2. OAuth client ID +5. Click **Create Credentials** and select **OAuth client ID**. +6. In the **Application type** section, select **Desktop app**. +7. Provide an appropriate name for your client ID (e.g., "Gspread OAuth Client"). +8. Click **Create**. + +After you will have all required tokens to use application + +## 3. Store Your Credentials + +For **Service Account**, **Client ID** and **Client Secret** are enough. +For **Service account** use all tokens. + +Save the credentials in a `.env` within a `.secret` directory. The file should look like this: + +```bash +CLIENT_ID=YOUR_CLIENT_ID +CLIENT_SECRET=YOUR_SECRET_KEY +# other tokens +# .... +``` + +## 4. Why do we need it? + +After executing each command, you need to grant the GSPREAD program access to the Google API. You will receive a link that begin with 'Please direct your browser to https://....' that will redirect you to your browser, where you must authorize the access. You will need to select the appropriate Google account that has the credentials for the application. The tokens are set up to do this process. + +## 5. Troubleshooting + +### 1-1. OAuth client ID +If you encounter a page displaying an error instead of the Google account selection screen, it is likely that you need to add **AUTH_URI** or **TOKEN_URI** to the .env file. In this case, all four secrets are required. To retrieve them, download the API key you created in JSON format. Open the file and copy the necessary keys into the .env file. After making these changes, your .env file should look like this: + +```bash +CLIENT_ID=YOUR_CLIENT_ID +CLIENT_SECRET=YOUR_SECRET_KEY +AUTH_URI=YOUR_AUTH_URI +TOKEN_URI=YOUR_TOKEN_URI +``` + +If you still get some issues, follow [Google OAuth Documentation](https://developers.google.com/identity/protocols/oauth2/). \ No newline at end of file diff --git a/module/move/gspread/Cargo.toml b/module/move/gspread/Cargo.toml new file mode 100644 index 0000000000..6a3a66ad93 --- /dev/null +++ b/module/move/gspread/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "gspread" +version = "0.1.0" +edition = "2021" +authors = [ + "Vsevolod Bakutov " +] +license = "MIT" +description = """ + Google Sheets Cli API +""" +categories = [ "algorithms", "development-tools" ] +keywords = [ "fundamental", "general-purpose" ] +default-run = "main" + +[[bin]] +name = "main" +path = "src/bin/main.rs" + +[features] +with_online = [] +default = [ "enabled" ] +full = [ "enabled" ] +enabled = [ + "former/enabled", + "format_tools/enabled", + "reflect_tools/enabled", +] + +[dependencies] +mod_interface = { workspace = true, features = ["full"] } +former = { workspace = true, features = ["full"] } +format_tools = { workspace = true, features = ["full"] } +reflect_tools = { workspace = true, features = [ "full" ] } +clap = { version = "4.5.20", features = ["derive"] } +tokio = { version = "1", features = ["full"] } +pth = "0.21.0" +dotenv = "0.15" +serde = { version = "1.0.213", features = ["derive"] } +serde_with = "3.11.0" +error_tools = "0.19.0" +derive_tools = { version = "0.32.0", features = ["full"] } +serde_json = "1.0.132" +regex = "1.11.1" +reqwest = { version = "0.11", features = ["json"] } +yup-oauth2 = "11.0.0" +rand = "0.8" +once_cell = "1.20.3" + +[dev-dependencies] +test_tools = { workspace = true } +httpmock = "0.7.0-rc.1" diff --git a/module/move/gspread/readme.md b/module/move/gspread/readme.md new file mode 100644 index 0000000000..991a5abe04 --- /dev/null +++ b/module/move/gspread/readme.md @@ -0,0 +1,7 @@ +## Module :: gspread + +[![experimental](https://img.shields.io/badge/status-experimental-orange)](https://github.com/emersion/stability-badges#experimental) +[![ask](https://img.shields.io/badge/discord-join%20chat-7289DA)](https://discord.gg/RzQGqF5z) + + +**NOT ready for production** \ No newline at end of file diff --git a/module/move/gspread/src/actions.rs b/module/move/gspread/src/actions.rs new file mode 100644 index 0000000000..f5b9e35c11 --- /dev/null +++ b/module/move/gspread/src/actions.rs @@ -0,0 +1,25 @@ +//! +//! CLI actions of the tool. +//! + +mod private {} + +crate::mod_interface! +{ + layer utils; + layer gspread; + layer gspread_header_get; + layer gspread_rows_get; + layer gspread_cell_get; + layer gspread_cell_set; + layer gspread_row_get; + layer gspread_row_get_custom; + layer gspread_row_update; + layer gspread_row_append; + layer gspread_row_update_custom; + layer gspread_column_get; + layer gspread_clear; + layer gspread_clear_custom; + layer gspread_copy; +} + diff --git a/module/move/gspread/src/actions/gspread.rs b/module/move/gspread/src/actions/gspread.rs new file mode 100644 index 0000000000..f5c5b8ef8f --- /dev/null +++ b/module/move/gspread/src/actions/gspread.rs @@ -0,0 +1,1109 @@ +//! +//! Google Sheets API actions. +//! +//! This module also contains the definition of Google Sheets Error. +//! + +mod private +{ + use regex::Regex; + use serde_json::json; + use once_cell::sync::Lazy; + use std::collections::HashMap; + + use crate::gcore::client::InsertDataOption; + use crate::*; + use gcore::Secret; + use gcore::error:: + { + Error, + Result + }; + use gcore::client:: + { + Client, + Dimension, + ValueRange, + ValueInputOption, + ValueRenderOption, + UpdateValuesResponse, + // ValuesAppendResponse, + BatchUpdateValuesRequest, + BatchUpdateValuesResponse, + BatchClearValuesRequest, + BatchClearValuesResponse, + SheetProperties, + ValuesClearResponse + }; + + static REGEX_ROW_INDEX : Lazy< Regex > = Lazy::new( || { + Regex::new( r"^([A-Za-z]+)(\d+)$" ).unwrap() + }); + + /// # get_key_matches + /// + /// Collect value matches in a column. + /// + /// ## Params: + /// - `column`: A reference to Vec< serde_json::Value >, column. + /// - `key`: A reference to a serde_json::Value, value to find. + /// + /// Return `Vec< usize >` + fn get_key_matches + ( + column : &Vec< serde_json::Value >, + key : &serde_json::Value + ) -> Vec< usize > + { + column + .iter() + .enumerate() + .filter( | &( _, val ) | { *val == *key } ) + .map( | ( i, _ ) | i ) + .collect() + } + + /// Return row key depending on selected action. + fn get_row_keys + ( + key_matches : Vec< usize >, + action : OnFind + ) -> Vec< usize > + { + match action + { + OnFind::AllMatchedRow => key_matches, + OnFind::FirstMatchedRow => vec![ *key_matches.first().unwrap() ], + OnFind::LastMatchedRow => vec![ *key_matches.last().unwrap() ] + } + } + + /// Converts number to column label. + fn number_to_column_label( mut num : usize ) -> String + { + let mut chars = Vec::new(); + while num > 0 + { + let remainder = ( num - 1 ) % 26; + let c = ( b'A' + remainder as u8 ) as char; + chars.push( c ); + num = ( num - 1 ) / 26; + } + chars.reverse(); + chars.into_iter().collect() + } + /// Converts label to number. + fn column_label_to_number( col : &str ) -> usize + { + let mut result = 0; + for c in col.chars() + { + let digit = c as usize - 'A' as usize + 1; + result = result * 26 + digit + } + result + } + + /// # `update_row` + /// + /// Updates a specific row in a Google Sheet with the provided values. + /// + /// ## Parameters: + /// - `client`: + /// A reference to the [`Client`] client configured for the Google Sheets API. + /// - `spreadsheet_id`: + /// A `&str` representing the unique identifier of the spreadsheet. + /// - `sheet_name`: + /// A `&str` specifying the name of the sheet. + /// - `row_key`: + /// A `serde_json::Value` representing the row's key (e.g., the row index). + /// - `row_key_val`: + /// A `HashMap< String, serde_json::Value >` where: + /// - Key: The column name (e.g., "A", "B"). + /// - Value: The new value to set in the corresponding cell. + /// + /// ## Returns: + /// - Result< [`BatchUpdateValuesResponse`] > + /// + /// ## Errors: + /// - `Error::ApiError`: + /// Occurs if the Google Sheets API returns an error, e.g., due to invalid input or insufficient permissions. + pub async fn update_row< S : Secret > + ( + client : &Client< '_, S >, + spreadsheet_id : &str, + sheet_name : &str, + row_key : serde_json::Value, + row_key_val : HashMap< String, serde_json::Value > + ) -> Result< BatchUpdateValuesResponse > + { + let mut value_ranges = Vec::with_capacity( row_key_val.len() ); + + for ( col_name, value ) in row_key_val + { + value_ranges.push + ( + ValueRange + { + major_dimension : Some( Dimension::Row ), + values : Some( vec![ vec![ value ] ] ), + range : Some( format!( "{}!{}{}", sheet_name, col_name, row_key ) ), + } + ) + } + + let request = BatchUpdateValuesRequest + { + data : value_ranges, + value_input_option : ValueInputOption::UserEntered, + include_values_in_response : Some( true ), + response_value_render_option : Some( ValueRenderOption::FormattedValue ), + response_date_time_render_option : Default::default() + }; + + match client + .spreadsheet() + .values_batch_update( spreadsheet_id, request ) + .doit() + .await + { + Ok( response ) => Ok( response ), + Err( error ) => Err( error ) + } + } + + /// # get_column + /// + /// Retrive a specific column from a Google Sheet. + /// + /// ## Parameters: + /// - `client`: + /// A reference to the [`Client`] client configured for the Google Sheets API. + /// - `spreadsheet_id`: + /// A `&str` representing the unique identifier of the spreadsheet. + /// - `sheet_name`: + /// A `&str` specifying the name of the sheet. + /// - `column_id`: + /// `&str` specifying the sheet's column id (e. g. A, B, C, ..., ZZZ) + /// + /// ## Returns: + /// - Result< Vec< serde_json::Value > > + /// + /// ## Errors: + /// - `Error::ApiError`: + /// Occurs if the Google Sheets API returns an error, e.g., due to invalid input or insufficient permissions. + pub async fn get_column< S : Secret > + ( + client : &Client< '_, S >, + spreadsheet_id : &str, + sheet_name : &str, + column_id : &str + ) -> Result< Vec< serde_json::Value > > + { + let range = format!( "{}!{}:{}", sheet_name, column_id, column_id ); + + match client + .spreadsheet() + .values_get( spreadsheet_id, &range ) + .major_dimension( Dimension::Column ) + .value_render_option( ValueRenderOption::UnformattedValue ) + .doit() + .await + { + Ok( response ) => + { + match response.values + { + Some( values ) => + { + let column = values + .into_iter() + .next() + .unwrap_or_default(); + + Ok( column ) + } + None => Ok( Vec::new() ) + } + }, + Err( error ) => Err( Error::ApiError( error.to_string() ) ) + } + } + + /// # `update_rows_by_custom_row_key` + /// + /// Updates a specific row or rows in a Google Sheet with the provided values. + /// + /// ## Parameters: + /// - `client`: + /// A reference to the [`Client`] client configured for the Google Sheets API. + /// - `spreadsheet_id`: + /// A `&str` representing the unique identifier of the spreadsheet. + /// - `sheet_name`: + /// A `&str` specifying the name of the sheet. + /// - `key_by`: + /// A `( &str, serde_json::Value )` a pair of column key and its value. + /// - `row_key_val`: + /// A `HashMap< String, serde_json::Value >` where: + /// - Key: The column name (e.g., "A", "B"). + /// - Value: The new value to set in the corresponding cell. + /// - `update_range_at_all_match_cells` + /// A `bool` If true, updates the rows with all match cells. Otherwise updates row with the first match cell. + /// - `raise_error_on_fail` + /// Returns an error if there were not found any matches. + /// + /// ## Returns: + /// - Result< [`BatchUpdateValuesResponse`] > + /// + /// ## Errors: + /// - `Error::ApiError`: + /// Occurs if the Google Sheets API returns an error, e.g., due to invalid input or insufficient permissions. + pub async fn update_rows_by_custom_row_key< S : Secret > + ( + client : &Client< '_, S >, + spreadsheet_id : &str, + sheet_name : &str, + key_by : ( &str, serde_json::Value ), + row_key_val : HashMap< String, serde_json::Value >, + on_find : OnFind, + on_fail : OnFail + ) -> Result< BatchUpdateValuesResponse > + { + // Getting provided column. + let range = format!( "{}!{}:{}", sheet_name, key_by.0, key_by.0 ); + + // Get column + let value_range = client + .spreadsheet() + .values_get( spreadsheet_id, &range ) + .major_dimension( Dimension::Column ) + .value_render_option( ValueRenderOption::UnformattedValue ) + .doit() + .await + .map_err( | err | Error::ApiError( err.to_string() ) )?; + + let values = match value_range.values + { + Some( values ) => values, + None => + { + match on_fail + { + OnFail::Nothing => return Ok( BatchUpdateValuesResponse::default() ), + OnFail::AppendRow => + { + let _ = append_row( client, spreadsheet_id, sheet_name, &row_key_val ).await?; + let response = BatchUpdateValuesResponse + { + spreadsheet_id : Some( spreadsheet_id.to_string() ), + total_updated_rows : Some( 1 ), + total_updated_sheets : Some( 1 ), + total_updated_cells : Some( row_key_val.len() as u32 ), + total_updated_columns : Some( row_key_val.len() as u32 ), + responses : None + }; + + return Ok( response ); + } + OnFail::Error => return Err( Error::ApiError( "Not such value in the sheet.".to_string() ) ) + } + } + }; + + // Counting mathces. + let row_keys : Vec< usize > = values[0] + .iter() + .enumerate() + .filter( | &( _, val ) | { *val == key_by.1 } ) + .map( | ( i, _ ) | i ) + .collect(); + + if row_keys.is_empty() + { + match on_fail + { + OnFail::Nothing => return Ok( BatchUpdateValuesResponse::default() ), + OnFail::AppendRow => + { + let _ = append_row( client, spreadsheet_id, sheet_name, &row_key_val ).await?; + let response = BatchUpdateValuesResponse + { + spreadsheet_id : Some( spreadsheet_id.to_string() ), + total_updated_rows : Some( 1 ), + total_updated_sheets : Some( 1 ), + total_updated_cells : Some( row_key_val.len() as u32 ), + total_updated_columns : Some( row_key_val.len() as u32 ), + responses : None + }; + + return Ok( response ); + } + OnFail::Error => return Err( Error::ApiError( "Not such value in the sheet.".to_string() ) ) + } + } + + // Preparing value ranges. + let mut value_ranges = Vec::with_capacity( row_key_val.len() ); + let range = match on_find + { + OnFind::AllMatchedRow => row_keys, + OnFind::FirstMatchedRow => vec![ *row_keys.first().unwrap() ], + OnFind::LastMatchedRow => vec![ *row_keys.last().unwrap() ] + }; + + for row_key in range + { + for ( col_name, value ) in &row_key_val + { + value_ranges.push + ( + ValueRange + { + major_dimension : Some( Dimension::Row ), + values : Some( vec![ vec![ value.clone() ] ] ), + range : Some( format!( "{}!{}{}", sheet_name, col_name, row_key + 1 ) ), + } + ); + } + } + + // Making HTTP request. + let request = BatchUpdateValuesRequest + { + data : value_ranges, + value_input_option : ValueInputOption::UserEntered, + include_values_in_response : Some( true ), + response_value_render_option : Some( ValueRenderOption::FormattedValue ), + response_date_time_render_option : Default::default() + }; + + match client + .spreadsheet() + .values_batch_update( spreadsheet_id, request ) + .doit() + .await + { + Ok( response ) => Ok( response ), + Err( error ) => Err( error ) + } + + } + + /// # `append_row` + /// + /// Append a new row at the end of the sheet. + /// + /// ## Parameters: + /// - `client`: + /// A reference to the [`Client`] client configured for the Google Sheets API. + /// - `spreadsheet_id`: + /// A `&str` representing the unique identifier of the spreadsheet. + /// - `sheet_name`: + /// A `&str` specifying the name of the sheet whose header is to be retrieved. + /// - `row_key_val`: + /// A `HashMap< String, serde_json::Value >` where: + /// - Key: The column name (e.g., "A", "B"). + /// - Value: The new value to set in the corresponding cell. + /// + /// ## Returns: + /// - `Result< ValuesAppendResponse >` + /// + /// ## Errors: + /// - `Error::ApiError`: + /// Occurs if the Google Sheets API returns an error, such as an invalid spreadsheet ID + /// or insufficient permissions. + pub async fn append_row< S : Secret > + ( + client : &Client< '_, S >, + spreadsheet_id : &str, + sheet_name : &str, + row_key_val : &HashMap< String, serde_json::Value > + ) -> Result< BatchUpdateValuesResponse > + { + // Sort column indexes, from A -> ZZZ + let mut columns : Vec< ( String, usize, serde_json::Value ) > = row_key_val + .iter() + .map( | ( k, v ) | ( k.clone(), column_label_to_number( k ), v.clone() ) ) + .collect(); + + columns.sort_by_key( | ( _, col_idx, _ ) | *col_idx ); + + let min_idx = 1; + let max_idx = columns.last().unwrap().1; + + let empty_row_size = max_idx - min_idx + 1; + let empty_row = vec![ json!( "" ); empty_row_size ]; + + let range = format!( "{}!A1", sheet_name ); + let empty_value_range = ValueRange + { + major_dimension : Some( Dimension::Row ), + values : Some( vec![ empty_row ] ), + range : None + }; + + let append_response = client + .spreadsheet() + .append( spreadsheet_id, &range, empty_value_range ) + .insert_data_option( InsertDataOption::InsertRows ) + .doit() + .await; + + let row_index = match append_response + { + Ok( ref response ) => parse_row_index + ( + &response + .updates + .clone() + .unwrap() + .updated_range + .unwrap() + )?, + Err( error ) => return Err( Error::ApiError( error.to_string() ) ) + }; + + let total_colspan = max_idx - min_idx + 1; + let max_subrequests = 100; + let chunk_size = ( total_colspan + max_subrequests - 1 ) / max_subrequests; + + let mut batch_ranges = Vec::new(); + + let mut start_col = min_idx; + let mut idx_cols = 0; + let col_count = columns.len(); + + while start_col <= max_idx + { + let end_col = ( start_col + chunk_size - 1 ).min( max_idx ); + let subrange_len = end_col - start_col + 1; + + let mut row_values = vec![ json!( "" ); subrange_len ]; + while idx_cols < col_count + { + let col_idx = columns[ idx_cols ].1; + if col_idx < start_col + { + idx_cols += 1; + continue; + } + if col_idx > end_col + { + break; + } + + let offset = col_idx - start_col; + row_values[ offset ] = columns[ idx_cols ].2.clone(); + idx_cols += 1; + } + + let start_col_label = number_to_column_label( start_col ); + let end_col_label = number_to_column_label( end_col ); + + let range_str = if start_col == end_col { + format!( "{}!{}{}", sheet_name, start_col_label, row_index ) + } else { + format! + ( + "{}!{}{}:{}{}", + sheet_name, start_col_label, row_index, end_col_label, row_index + ) + }; + + let value_range = ValueRange + { + major_dimension : Some( Dimension::Row ), + values : Some( vec![ row_values ] ), + range : Some( range_str ), + }; + batch_ranges.push( value_range ); + + // Next chunck; + start_col = end_col + 1; + } + + let request = BatchUpdateValuesRequest + { + data : batch_ranges, + value_input_option : ValueInputOption::UserEntered, + include_values_in_response : Some( true ), + response_value_render_option : Some( ValueRenderOption::FormattedValue ), + response_date_time_render_option : Default::default(), + }; + + match client + .spreadsheet() + .values_batch_update( spreadsheet_id, request ) + .doit() + .await + { + Ok( response ) => Ok( response ), + Err( error ) => { + println!( "{error}" ); + Err( Error::ApiError( error.to_string() ) ) + } + } + } + + fn parse_row_index( range_str : &str ) -> Result< u32 > + { + let parts : Vec< &str > = range_str.split( '!' ).collect(); + + let second_part = parts[ 1 ]; + + let sub_parts : Vec< &str > = second_part.split( ':' ).collect(); + + let left_part = sub_parts[ 0 ]; + + if let Some( caps ) = REGEX_ROW_INDEX.captures( left_part ) + { + let row_str = &caps[ 2 ]; + let row_index = row_str + .parse::< u32 >() + .map_err( | err | Error::ParseError( err.to_string() ) )?; + + Ok( row_index ) + } + else + { + Err( Error::ParseError( format!( "Could not parse column+row from '{left_part}'" ) ) ) + } + } + + /// # `get_row_by_custom_row_key` + /// + /// Retrieves rows from the specified sheet that match a given "custom row key" value. + /// [batchGet docs](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchGet). + /// + /// ## Parameters: + /// - `client`: + /// A reference to the [`Client`] configured for the Google Sheets API. + /// - `spreadsheet_id`: + /// A `&str` representing the unique identifier of the spreadsheet. + /// - `sheet_name`: + /// A `&str` specifying the name of the sheet from which rows are to be retrieved. + /// - `key_by`: + /// A tuple `( column_id, value )` where: + /// - `column_letter`: The column identifier (e.g., `"A"`, `"B"`). + /// - `value`: A `serde_json::Value` to match in the given column. + /// - `on_find`: + /// An enum [`OnFind`] defining how to handle multiple matches + /// (e.g., return the first match, last match, or all matches). + /// + /// ## Returns: + /// - `Result< Vec< Vec< serde_json::Value > > >` + /// On success, returns a list of rows, where each row is a `Vec< serde_json::Value >`. + /// + /// ## Errors: + /// - `Error::ApiError`: + /// Occurs if the Google Sheets API returns an error, + /// such as an invalid spreadsheet ID, insufficient permissions, + /// or any issues during the request/response cycle. + pub async fn get_row_by_custom_row_key< S : Secret > + ( + client : &Client< '_, S >, + spreadsheet_id : &str, + sheet_name : &str, + key_by : ( &str, serde_json::Value ), + on_find : OnFind, + ) -> Result< Vec< Vec< serde_json::Value > > > + { + match get_column + ( + client, + spreadsheet_id, + sheet_name, + key_by.0 + ) + .await + { + Ok( column ) => + { + if column.is_empty() + { + return Ok( Vec::new() ); + } + else + { + let key_matches = get_key_matches( &column, &key_by.1 ); + let row_keys = get_row_keys( key_matches, on_find ); + + let mut ranges = Vec::with_capacity( row_keys.len() ); + for row_key in row_keys + { + let range = format!( "{}!A{}:ZZZ{}", sheet_name, row_key + 1, row_key + 1 ); + ranges.push( range ); + } + + match client + .spreadsheet() + .values_get_batch( spreadsheet_id ) + .ranges( ranges ) + .doit() + .await + { + Ok( response ) => + { + let values : Vec< Vec< serde_json::Value > > = response + .value_ranges + .unwrap_or_default() + .into_iter() + .flat_map( | range | range.values.unwrap_or_default() ) + .collect(); + + Ok( values ) + } + Err( error ) => Err( Error::ApiError( error.to_string() ) ) + } + } + }, + + Err( error ) => Err( Error::ApiError( error.to_string() ) ) + } + + } + + + + /// # `get_header` + /// + /// Retrieves the header row of a specific sheet. + /// + /// ## Parameters: + /// - `client`: + /// A reference to the [`Client`] client configured for the Google Sheets API. + /// - `spreadsheet_id`: + /// A `&str` representing the unique identifier of the spreadsheet. + /// - `sheet_name`: + /// A `&str` specifying the name of the sheet whose header is to be retrieved. + /// + /// ## Returns: + /// - `Result< Vec< Vec< serde_json::Value > > >` + /// + /// ## Errors: + /// - `Error::ApiError`: + /// Occurs if the Google Sheets API returns an error, such as an invalid spreadsheet ID + /// or insufficient permissions. + pub async fn get_header< S : Secret > + ( + + client : &Client< '_, S >, + spreadsheet_id : &str, + sheet_name : &str, + ) -> Result< Vec< serde_json::Value > > + { + let range = format!( "{}!A1:ZZZ1", sheet_name ); + + match client + .spreadsheet() + .values_get( spreadsheet_id, &range ) + .doit() + .await + { + Ok( response ) => + { + match response.values + { + Some( values ) => Ok( values[0].clone() ), + None => Ok( Vec::new() ) + } + } + Err( error ) => Err( error ) + } + + } + + /// # get_row + /// + /// Retreive a specific row by its key for a Google Sheet. + /// + /// ## Parameters: + /// - `client`: + /// A reference to the `Client` client configured for the Google Sheets API. + /// - `spreadsheet_id`: + /// A `&str` representing the unique identifier of the spreadsheet. + /// - `sheet_name`: + /// A `&str` specifying the name of the sheet whose rows are to be retrieved. + /// - `row_key`: + /// A `serde_json::Value` represents row's key. Key starts from 1. + pub async fn get_row< S : Secret > + ( + client : &Client< '_, S >, + spreadsheet_id : &str, + sheet_name : &str, + row_key : serde_json::Value + ) -> Result< Vec< serde_json::Value > > + { + let range = format!( "{}!A{}:ZZZ{}", sheet_name, row_key, row_key ); + + match client + .spreadsheet() + .values_get( spreadsheet_id, &range ) + .value_render_option( ValueRenderOption::UnformattedValue ) + .doit() + .await + { + Ok( response ) => + { + match response.values + { + Some( values ) => + { + let row = values + .into_iter() + .next() + .unwrap_or_default(); + + Ok( row ) + }, + None => Ok( Vec::new() ) + } + } + Err( error ) => Err( error ) + } + } + + /// # `get_rows` + /// + /// Retrieves all rows (excluding the header) from a specific sheet. + /// + /// ## Parameters: + /// - `client`: + /// A reference to the `Client` client configured for the Google Sheets API. + /// - `spreadsheet_id`: + /// A `&str` representing the unique identifier of the spreadsheet. + /// - `sheet_name`: + /// A `&str` specifying the name of the sheet whose rows are to be retrieved. + /// + /// ## Returns: + /// - `Result< Vec< Vec< serde_json::Value > > >` + /// + /// ## Errors: + /// - `Error::ApiError`: + /// Occurs if the Google Sheets API returns an error, such as an invalid spreadsheet ID + /// or insufficient permissions. + pub async fn get_rows< S : Secret > + ( + client : &Client< '_, S >, + spreadsheet_id : &str, + sheet_name : &str, + ) -> Result< Vec< Vec< serde_json::Value > > > + { + let range = format!( "{}!A2:ZZZ", sheet_name ); + + match client + .spreadsheet() + .values_get( spreadsheet_id, &range ) + .value_render_option( ValueRenderOption::UnformattedValue ) + .doit() + .await + { + Ok( response ) => + { + match response.values + { + Some( values ) => Ok( values ), + None => Ok( Vec::new() ) + } + } + Err( error ) => Err( error ) + } + + } + + /// # `get_cell` + /// + /// Retrieves the value of a specific cell from a Google Sheet. + /// + /// ## Parameters: + /// - `client`: + /// A reference to the [`Client`] client configured for the Google Sheets API. + /// - `spreadsheet_id`: + /// A `&str` representing the unique identifier of the spreadsheet. + /// - `sheet_name`: + /// A `&str` specifying the name of the sheet where the cell is located. + /// - `cell_id`: + /// A `&str` representing the cell ID in the format `A1`, where `A` is the column and `1` is the row. + /// + /// ## Returns: + /// - `Result< serde_json::Value >`: + /// + /// ## Errors: + /// - `Error::ApiError`: + /// Occurs if the Google Sheets API returns an error, such as an invalid spreadsheet ID + /// or insufficient permissions. + pub async fn get_cell< S : Secret > + ( + client : &Client< '_, S >, + spreadsheet_id : &str, + sheet_name : &str, + cell_id : &str + ) -> Result< serde_json::Value > + { + let range = format!( "{}!{}", sheet_name, cell_id ); + + match client + .spreadsheet() + .values_get( spreadsheet_id, &range ) + .doit() + .await + { + Ok( response ) => + { + match response.values + { + Some( values ) => Ok( values[0][0].clone() ), + None => Ok( json!( "" ) ) + } + } + Err( error ) => Err( error ) + } + } + + /// # `set_cell` + /// + /// Updates the value of a specific cell in a Google Sheet. + /// + /// ## Parameters: + /// - `client`: + /// A reference to the `Client` client configured for the Google Sheets API. + /// - `spreadsheet_id`: + /// A `&str` representing the unique identifier of the spreadsheet. + /// - `sheet_name`: + /// A `&str` specifying the name of the sheet where the cell is located. + /// - `cell_id`: + /// A `&str` representing the cell ID in the format `A1`, where `A` is the column and `1` is the row. + /// - `value`: + /// A `serde_json::Value` containing the new value to update in the cell. + /// + /// ## Returns: + /// - Result< [`UpdateValuesResponse`] > + /// + /// ## Errors: + /// - `Error::ApiError`: + /// Occurs if the Google Sheets API returns an error, such as invalid input or insufficient permissions. + pub async fn set_cell< S : Secret > + ( + client : &Client< '_, S >, + spreadsheet_id : &str, + sheet_name : &str, + cell_id : &str, + value : serde_json::Value + ) -> Result< UpdateValuesResponse > + { + let range = format!( "{}!{}", sheet_name, cell_id ); + + let value_range = ValueRange + { + values : Some( vec![ vec![ value ] ] ), + ..ValueRange::default() + }; + + match client + .spreadsheet() + .values_update( value_range, spreadsheet_id, &range ) + .doit() + .await + { + Ok( response ) => Ok( response ), + Err( error ) => Err( error ) + } + } + + /// # clear + /// + /// Clears a provided sheet. + /// + /// ## Parameters: + /// - `client`: + /// A reference to the `Client` client configured for the Google Sheets API. + /// - `spreadsheet_id`: + /// A `&str` representing the unique identifier of the spreadsheet. + /// - `sheet_name`: + /// A `&str` specifying the name of the sheet where the cell is located. + /// + /// ## Returns: + /// - Result< [`ValuesClearResponse`] > + /// + /// ## Errors: + /// - `Error::ApiError`: + /// Occurs if the Google Sheets API returns an error, such as invalid input or insufficient permissions. + pub async fn clear< S : Secret > + ( + client : &Client< '_, S >, + spreadsheet_id : &str, + sheet_name : &str + ) -> Result< ValuesClearResponse > + { + let range = format!( "{sheet_name}!A:ZZZ" ); + match client + .spreadsheet() + .clear( spreadsheet_id, &range ) + .doit() + .await + { + Ok( response ) => Ok( response ), + Err( error ) => Err( error ) + } + } + + /// # clear_by_custom_row_key + /// + /// Clears matched rows by doing action provided by `on_find`. + /// + /// ## Parameters: + /// - `client`: + /// A reference to the `Client` client configured for the Google Sheets API. + /// - `spreadsheet_id`: + /// A `&str` representing the unique identifier of the spreadsheet. + /// - `sheet_name`: + /// A `&str` specifying the name of the sheet where the cell is located. + /// - `key_by`: + /// A tuple representing a column id and value to find in that column. + /// - `on_find`: + /// Action to do on finded matches. + /// + /// ## Returns: + /// - Result< [`BatchClearValuesResponse`] > + /// + /// ## Errors: + /// - `Error::ApiError`: + /// Occurs if the Google Sheets API returns an error, such as invalid input or insufficient permissions. + pub async fn clear_by_custom_row_key< S : Secret > + ( + client : &Client< '_, S >, + spreadsheet_id : &str, + sheet_name : &str, + key_by : ( &str, serde_json::Value ), + on_find : OnFind, + ) -> Result< BatchClearValuesResponse > + { + match get_column + ( + client, + spreadsheet_id, + sheet_name, + key_by.0 + ) + .await + { + Ok( column ) => + { + if column.is_empty() + { + return Ok( BatchClearValuesResponse::default() ); + } + + let key_matches = get_key_matches( &column, &key_by.1 ); + let row_keys = get_row_keys( key_matches, on_find ); + + let mut ranges = Vec::with_capacity( row_keys.len() ); + for row_key in row_keys + { + let range = format!( "{}!A{}:ZZZ{}", sheet_name, row_key + 1, row_key + 1 ); + ranges.push( range ); + } + + let request = BatchClearValuesRequest + { + ranges : ranges + }; + + match client + .spreadsheet() + .clear_batch( spreadsheet_id, request ) + .doit() + .await + { + Ok( response ) => Ok( response ), + Err( error ) => Err( error ) + } + }, + Err( error ) => Err( error ) + } + } + + /// # copy_to + /// + /// Copies a spreadsheet's sheet to the other spreadsheet. + /// + /// ## Prameters: + /// - `client` + /// A referebce to a [`Client`] object. + /// - `spreadsheet_id` + /// A reference to string slice which represents a spreadsheet id. + /// - `sheet_id` + /// A reference to a string slice which represents a source sheet's id. + /// - `dest` + /// A reference to a string slice which represents a destination spreadsheet's id. + /// + /// ## Returns: + /// - `Result< `[SheetProperties]` >` + /// + /// ## Errors: + /// - [`Error::ApiError`] + /// - [`Error::ParseError`] + pub async fn copy_to< S : Secret > + ( + client : &Client< '_, S >, + spreadsheet_id : &str, + sheet_id : &str, + dest : &str + ) -> Result< SheetProperties > + { + match client + .sheet() + .copy_to( spreadsheet_id, sheet_id, dest ) + .doit() + .await + { + Ok( response ) => Ok( response ), + Err( error ) => Err( error ) + } + } + + /// Action to do if one or more rows were found. + pub enum OnFind + { + /// Update first matched row. + FirstMatchedRow, + /// Update last matched row. + LastMatchedRow, + /// Update all matched rows. + AllMatchedRow, + } + + /// Action to do if row was not find. + pub enum OnFail + { + /// Returns error. + Error, + /// Does nothing. + Nothing, + /// Append provided row at the and of sheet. + AppendRow, + } + +} + +crate::mod_interface! +{ + own use + { + OnFind, + OnFail, + set_cell, + get_cell, + get_row, + get_rows, + update_row, + get_header, + append_row, + update_rows_by_custom_row_key, + get_row_by_custom_row_key, + get_column, + clear, + clear_by_custom_row_key, + copy_to + }; +} \ No newline at end of file diff --git a/module/move/gspread/src/actions/gspread_cell_get.rs b/module/move/gspread/src/actions/gspread_cell_get.rs new file mode 100644 index 0000000000..b6b1db44d3 --- /dev/null +++ b/module/move/gspread/src/actions/gspread_cell_get.rs @@ -0,0 +1,36 @@ +//! +//! Action for the command "cell get". +//! +//! Retrieves the value of a selected cell from the specified Google Sheet. +//! + +mod private +{ + + + use crate::*; + use actions::gspread::get_cell; + use gcore::Secret; + use gcore::error::Result; + use gcore::client::Client; + + pub async fn action< S : Secret > + ( + client : &Client< '_, S >, + spreadsheet_id : &str, + sheet_name : &str, + cell_id : &str, + ) -> Result< serde_json::Value > + { + match get_cell( client, spreadsheet_id, sheet_name, cell_id ).await + { + Ok( value ) => Ok( value ), + Err( error ) => Err( error ) + } + } +} + +crate::mod_interface! +{ + own use action; +} \ No newline at end of file diff --git a/module/move/gspread/src/actions/gspread_cell_set.rs b/module/move/gspread/src/actions/gspread_cell_set.rs new file mode 100644 index 0000000000..9aee1546af --- /dev/null +++ b/module/move/gspread/src/actions/gspread_cell_set.rs @@ -0,0 +1,48 @@ +//! +//! Action for the command "cell set". +//! +//! Updates the value of a selected cell in the specified Google Sheet. +//! + + +mod private +{ + use crate::*; + use serde_json::json; + use actions::gspread::set_cell; + use gcore::Secret; + use gcore::client::Client; + use gcore::error:: + { + Error, + Result + }; + + pub async fn action< S : Secret > + ( + client : &Client< '_, S >, + spreadsheet_id : &str, + sheet_name : &str, + cell_id : &str, + value : &str + ) -> Result< u32 > + { + match set_cell( client, spreadsheet_id, sheet_name, cell_id, json!( value ) ).await + { + Ok( response ) => + { + match response.updated_cells + { + Some( amount ) => Ok( amount ), + None => Err( Error::CellError( "Some problem with cell updating".to_string() ) ) + } + }, + Err( error ) => Err( error ) + } + } +} + +crate::mod_interface! +{ + own use action; +} \ No newline at end of file diff --git a/module/move/gspread/src/actions/gspread_clear.rs b/module/move/gspread/src/actions/gspread_clear.rs new file mode 100644 index 0000000000..a363298ba5 --- /dev/null +++ b/module/move/gspread/src/actions/gspread_clear.rs @@ -0,0 +1,34 @@ +//! +//! Action for clear command. +//! + +mod private +{ + use crate::*; + use gcore::Secret; + use gcore::error::Result; + use gcore::client::Client; + use actions::gspread::clear; + + pub async fn action< S : Secret > + ( + client : &Client< '_, S >, + spreadsheet_id : &str, + sheet_name : &str + ) -> Result< String > + { + match clear( client, spreadsheet_id, sheet_name ).await + { + Ok( response ) => Ok( response.cleared_range.unwrap_or_default() ), + Err( error ) => Err( error ) + } + } +} + +crate::mod_interface! +{ + own use + { + action + }; +} \ No newline at end of file diff --git a/module/move/gspread/src/actions/gspread_clear_custom.rs b/module/move/gspread/src/actions/gspread_clear_custom.rs new file mode 100644 index 0000000000..062e3918b6 --- /dev/null +++ b/module/move/gspread/src/actions/gspread_clear_custom.rs @@ -0,0 +1,55 @@ +//! +//! Action for clear custom command. +//! + +mod private +{ + use crate::*; + use gcore::Secret; + use gcore:: + { + client::Client, + error::Result + }; + use actions::gspread::clear_by_custom_row_key; + use actions::utils:: + { + parse_key_by, + parse_on_find + }; + + pub async fn action< S : Secret > + ( + client : &Client< '_, S >, + spreadsheet_id : &str, + sheet_name : &str, + key_by : &str, + on_find : &str + ) -> Result< Vec< String > > + { + let key_by = parse_key_by( key_by )?; + let on_find = parse_on_find( on_find )?; + + match clear_by_custom_row_key + ( + client, + spreadsheet_id, + sheet_name, + key_by, + on_find + ) + .await + { + Ok( response ) => Ok( response.cleared_ranges.unwrap_or_default() ), + Err( error ) => Err( error ) + } + } +} + +crate::mod_interface! +{ + own use + { + action + }; +} \ No newline at end of file diff --git a/module/move/gspread/src/actions/gspread_column_get.rs b/module/move/gspread/src/actions/gspread_column_get.rs new file mode 100644 index 0000000000..05580e3ff0 --- /dev/null +++ b/module/move/gspread/src/actions/gspread_column_get.rs @@ -0,0 +1,42 @@ +//! +//! Action for column get command. +//! + +mod private +{ + use crate::*; + use gcore::Secret; + use gcore::error::Result; + use gcore::client::Client; + use actions::gspread::get_column; + + pub async fn action< S : Secret > + ( + client : &Client< '_, S >, + spreadsheet_id : &str, + sheet_name : &str, + column_id : &str + ) -> Result< Vec< serde_json::Value > > + { + match get_column + ( + client, + spreadsheet_id, + sheet_name, + column_id + ) + .await + { + Ok( column ) => Ok( column ), + Err( error ) => Err( error ) + } + } +} + +crate::mod_interface! +{ + own use + { + action + }; +} \ No newline at end of file diff --git a/module/move/gspread/src/actions/gspread_copy.rs b/module/move/gspread/src/actions/gspread_copy.rs new file mode 100644 index 0000000000..7d0cf2413d --- /dev/null +++ b/module/move/gspread/src/actions/gspread_copy.rs @@ -0,0 +1,49 @@ +//! +//! Copy command action +//! + +mod private +{ + use crate::*; + use actions::gspread::copy_to; + use gcore:: + { + Secret, + client::Client, + error::Result + }; + + pub async fn action< S : Secret > + ( + client : &Client< '_, S >, + spreadsheet_id : &str, + sheet_id : &str, + dest : &str + ) -> Result< String > + { + match copy_to + ( + client, + spreadsheet_id, + sheet_id, + dest + ) + .await + { + Ok( response ) => + { + let title = response.title.unwrap_or_default(); + Ok( title ) + }, + Err( error ) => Err( error ) + } + } +} + +crate::mod_interface! +{ + own use + { + action + }; +} \ No newline at end of file diff --git a/module/move/gspread/src/actions/gspread_header_get.rs b/module/move/gspread/src/actions/gspread_header_get.rs new file mode 100644 index 0000000000..30278397de --- /dev/null +++ b/module/move/gspread/src/actions/gspread_header_get.rs @@ -0,0 +1,35 @@ +//! +//! Action for the command "header". +//! +//! Retrieves the header (first row) from the specified Google Sheet. +//! + + +mod private +{ + use crate::*; + use actions::gspread::get_header; + use gcore::Secret; + use gcore::client::Client; + use gcore::error::Result; + + pub async fn action< S : Secret > + ( + client : &Client< '_, S >, + spreadsheet_id : &str, + sheet_name : &str + ) -> Result< Vec< serde_json::Value > > + { + match get_header( client, spreadsheet_id, sheet_name ).await + { + Ok( result ) => Ok( result ), + Err( error ) => Err( error ) + } + } + +} + +crate::mod_interface! +{ + own use action; +} \ No newline at end of file diff --git a/module/move/gspread/src/actions/gspread_row_append.rs b/module/move/gspread/src/actions/gspread_row_append.rs new file mode 100644 index 0000000000..820582bcfe --- /dev/null +++ b/module/move/gspread/src/actions/gspread_row_append.rs @@ -0,0 +1,63 @@ + + +mod private +{ + use std::collections::HashMap; + use crate::*; + use actions::gspread::append_row; + use gcore::Secret; + use gcore::client::Client; + use gcore::error:: + { + Error, + Result + }; + + /// # parse_json + /// + /// Parse privded string to HashMap< String, serde_json::Value > + /// + /// ## Errors: + /// + /// Can occur if provided string is not valid. + fn parse_json + ( + json_str : &str + ) -> Result< HashMap< String, serde_json::Value > > + { + let parsed_json : HashMap< String, serde_json::Value > = serde_json::from_str( json_str ) + .map_err( | error | Error::InvalidJSON( format!( "Failed to parse JSON: {}", error ) ) )?; + + Ok( parsed_json ) + } + + pub async fn action< S : Secret > + ( + client : &Client< '_, S >, + spreadsheet_id : &str, + sheet_name : &str, + json_str : &str + ) -> Result< u32 > + { + match parse_json( json_str ) + { + Ok( row_key_val ) => + { + match append_row( client, spreadsheet_id, sheet_name, &row_key_val ).await + { + Ok( response ) => Ok( response.total_updated_cells.unwrap() ), + Err( error ) => Err( error ) + } + } + Err( error ) => Err( error ) + } + } +} + +crate::mod_interface! +{ + own use + { + action, + }; +} \ No newline at end of file diff --git a/module/move/gspread/src/actions/gspread_row_get.rs b/module/move/gspread/src/actions/gspread_row_get.rs new file mode 100644 index 0000000000..166326c8f3 --- /dev/null +++ b/module/move/gspread/src/actions/gspread_row_get.rs @@ -0,0 +1,42 @@ +//! +//! Action which calls `get_row` function. +//! + +mod private +{ + use crate::*; + use actions::gspread::get_row; + use gcore::Secret; + use gcore::error::Result; + use gcore::client::Client; + + pub async fn action< S : Secret > + ( + client : &Client< '_, S >, + spreadsheet_id : &str, + sheet_name : &str, + row_key : serde_json::Value + ) -> Result< Vec< serde_json::Value > > + { + match get_row + ( + client, + spreadsheet_id, + sheet_name, + row_key + ) + .await + { + Ok( row ) => Ok( row ), + Err( error ) => Err( error ) + } + } +} + +crate::mod_interface! +{ + own use + { + action + }; +} \ No newline at end of file diff --git a/module/move/gspread/src/actions/gspread_row_get_custom.rs b/module/move/gspread/src/actions/gspread_row_get_custom.rs new file mode 100644 index 0000000000..7ad6815b67 --- /dev/null +++ b/module/move/gspread/src/actions/gspread_row_get_custom.rs @@ -0,0 +1,53 @@ + + +mod private +{ + use crate::*; + use actions::gspread::get_row_by_custom_row_key; + use actions::utils:: + { + parse_key_by, + parse_on_find + }; + use gcore:: + { + Secret, + client::Client, + error::Result + }; + + pub async fn action< S : Secret > + ( + client : &Client< '_, S >, + spreadsheet_id : &str, + sheet_name : &str, + key_by : &str, + on_find : &str + ) -> Result< Vec< Vec< serde_json::Value > > > + { + let key_by = parse_key_by( key_by )?; + let on_find = parse_on_find( on_find )?; + + match get_row_by_custom_row_key + ( + client, + spreadsheet_id, + sheet_name, + key_by, + on_find + ) + .await + { + Ok( rows ) => Ok( rows ), + Err( error ) => Err( error ) + } + } +} + +crate::mod_interface! +{ + own use + { + action + }; +} \ No newline at end of file diff --git a/module/move/gspread/src/actions/gspread_row_update.rs b/module/move/gspread/src/actions/gspread_row_update.rs new file mode 100644 index 0000000000..043189a968 --- /dev/null +++ b/module/move/gspread/src/actions/gspread_row_update.rs @@ -0,0 +1,156 @@ +//! +//! Set command -> set specified values in specified columns in specified row +//! + +mod private +{ + use std::collections::HashMap; + + use crate::*; + use ser::Deserialize; + use actions::gspread::update_row; + use gcore::Secret; + use gcore::client::Client; + use gcore::error:: + { + Error, + Result + }; + + /// # ParsedJson + /// + /// A structure to store the row's primary key and new values for cell updates. + /// + /// ## Fields: + /// - `row_key`: + /// The primary key of the row. + /// - `row_key_val`: + /// A map of column names to new values. + #[ derive( Deserialize, Debug ) ] + struct ParsedJson + { + row_key : serde_json::Value, + row_key_val : HashMap< String, serde_json::Value >, + } + + /// # `parse_json` + /// + /// Parses the `--json` flag to extract the row key and values to update. + /// + /// ## Parameters: + /// - `json_str`: + /// The JSON string passed via the `--json` flag. + /// - `select_row_by_key`: + /// The key to use for identifying the row (e.g., `"id"`). + /// + /// ## Returns: + /// - `Result< ParsedJson >` + fn parse_json + ( + json_str : &str, + select_row_by_key : &str, + ) -> Result< ParsedJson > + { + let mut parsed_json : HashMap< String, serde_json::Value > = serde_json::from_str( json_str ) + .map_err( | error | Error::InvalidJSON( format!( "Failed to parse JSON: {}", error ) ) )?; + + let row_key = if let Some( row_key ) = parsed_json.remove( select_row_by_key ) + { + row_key + } + else + { + return Err( Error::InvalidJSON( format!( "Key '{}' not found in JSON", select_row_by_key ) ) ); + }; + + for ( col_name, _ ) in &parsed_json + { + if !col_name.chars().all( | c | c.is_alphabetic() && c.is_uppercase() ) + { + return Err + ( + Error::InvalidJSON + ( + format!( "Invalid column name: {}. Allowed only uppercase alphabetic letters (A-Z)", col_name ) + ) + ); + } + }; + + Ok + ( + ParsedJson + { + row_key : row_key, + row_key_val : parsed_json, + } + ) + } + + /// # `check_select_row_by_key` + /// + /// Validates if the provided row key is allowed. + /// + /// ## Parameters: + /// - `key`: + /// The row's primary key. + /// + /// ## Returns: + /// - `Result< () >` + fn check_select_row_by_key + ( + key : &str + ) -> Result< () > + { + let keys = vec![ "id" ]; + if keys.contains( &key ) + { + Ok( () ) + } + else + { + Err + ( + Error::ParseError( format!( "Invalid select_row_by_key: '{}'. Allowed keys: {:?}", key, keys ) ) + ) + } + } + + pub async fn action< S : Secret > + ( + client : &Client< '_, S >, + select_row_by_key : &str, + json_str : &str, + spreadsheet_id : &str, + table_name : &str + ) -> Result< u32 > + { + check_select_row_by_key( select_row_by_key )?; + + match parse_json( json_str, select_row_by_key ) + { + Ok( parsed_json ) => + { + match update_row( client, spreadsheet_id, table_name, parsed_json.row_key, parsed_json.row_key_val ).await + { + Ok( response ) => + { + match response.total_updated_cells + { + Some( val ) => Ok( val ), + None => Ok( 0 ), + } + }, + Err( error ) => Err( error ) + } + } + Err( error ) => Err( error ), + } + } + +} + +crate::mod_interface! +{ + own use action; +} \ No newline at end of file diff --git a/module/move/gspread/src/actions/gspread_row_update_custom.rs b/module/move/gspread/src/actions/gspread_row_update_custom.rs new file mode 100644 index 0000000000..fbd72ee878 --- /dev/null +++ b/module/move/gspread/src/actions/gspread_row_update_custom.rs @@ -0,0 +1,82 @@ + + + +mod private +{ + use crate::*; + use gcore::Secret; + use gcore::error::Result; + use gcore::client::Client; + use actions::gspread::update_rows_by_custom_row_key; + use actions::utils:: + { + parse_json, + parse_key_by, + parse_on_fail, + parse_on_find + }; + + + pub async fn action< S : Secret > + ( + client : &Client< '_, S >, + spreadsheet_id : &str, + sheet_name : &str, + key_by : &str, + json_str : &str, + on_find : &str, + on_fail : &str + ) -> Result< u32 > + { + let key_by = match parse_key_by( key_by ) + { + Ok( val ) => val, + Err( error ) => return Err( error ), + }; + + let on_find = parse_on_find( on_find )?; + let on_fail = parse_on_fail( on_fail )?; + + match parse_json( json_str ) + { + Ok( parsed_json ) => + { + match update_rows_by_custom_row_key + ( + client, + spreadsheet_id, + sheet_name, + key_by, + parsed_json, + on_find, + on_fail + ).await + { + Ok( response ) => Ok + ( + match response.responses + { + Some( _ ) => match response.total_updated_cells + { + Some( amount ) => amount, + None => 0 + }, + None => 0, + } + ), + Err( error ) => Err( error ) + } + }, + + Err( error ) => Err( error ) + } + } +} + +crate::mod_interface! +{ + own use + { + action + }; +} \ No newline at end of file diff --git a/module/move/gspread/src/actions/gspread_rows_get.rs b/module/move/gspread/src/actions/gspread_rows_get.rs new file mode 100644 index 0000000000..7c4d31db60 --- /dev/null +++ b/module/move/gspread/src/actions/gspread_rows_get.rs @@ -0,0 +1,34 @@ +//! +//! Action for the command "rows". +//! +//! Retrieves all rows from the specified Google Sheet, excluding the header. +//! + + +mod private +{ + use crate::*; + use actions::gspread::get_rows; + use gcore::Secret; + use gcore::error::Result; + use gcore::client::Client; + + pub async fn action< S : Secret > + ( + client : &Client< '_, S >, + spreadsheet_id : &str, + sheet_name : &str + ) -> Result< Vec< Vec < serde_json::Value > > > + { + match get_rows( client, spreadsheet_id, sheet_name ).await + { + Ok( rows ) => Ok( rows ), + Err( error ) => Err( error ) + } + } +} + +crate::mod_interface! +{ + own use action; +} diff --git a/module/move/gspread/src/actions/utils.rs b/module/move/gspread/src/actions/utils.rs new file mode 100644 index 0000000000..79d1222c51 --- /dev/null +++ b/module/move/gspread/src/actions/utils.rs @@ -0,0 +1,169 @@ + +mod private +{ + use regex::Regex; + use std::collections::HashMap; + + use crate::*; + use gcore::error:: + { + Error, Result + }; + use actions::gspread:: + { + OnFail, + OnFind + }; + + /// # parse_key_by + /// + /// Parse a provided string to ( &str, serde_json::Value ) + /// + /// ## Errors + /// + /// Can occur if passed string is not valid. + pub fn parse_key_by( s : &str ) -> Result< ( &str, serde_json::Value ) > + { + let result : ( &str, serde_json::Value ) = serde_json::from_str( s ) + .map_err( | err | Error::ParseError( format!( "Failed to parse key_by. {}", err ) ) )?; + + Ok( result ) + } + + /// # parse_on_find + /// + /// Parse provided string to OnFind's variant. + /// + /// ## Errors + /// + /// Can occur if variant is not allowed. + pub fn parse_on_find( on_find : &str ) -> Result< OnFind > + { + check_variant( on_find, vec![ "first", "last", "all" ] )?; + match on_find + { + "first" => Ok( OnFind::FirstMatchedRow ), + "last" => Ok( OnFind::LastMatchedRow ), + "all" => Ok( OnFind::AllMatchedRow ), + &_ => Err( Error::ParseError( format!( "OnFind prase error." ) ) ) + } + } + + /// # parse_on_fail + /// + /// Parse provided string to OnFail's variant. + /// + /// ## Errors + /// + /// Can occur if variant is not allowed. + pub fn parse_on_fail( on_fail : &str ) -> Result< OnFail > + { + check_variant( on_fail, vec![ "none", "error", "append" ] )?; + match on_fail + { + "none" => Ok( OnFail::Nothing ), + "error" => Ok( OnFail::Error ), + "append" => Ok( OnFail::AppendRow ), + &_ => Err( Error::ParseError( format!( "OnFail parse error." ) ) ) + } + } + + /// # check_variant + /// + /// Checks if passed variant is correct. + /// + /// ## Returns: + /// - `Result< () >` + /// + /// ## Errors: + /// + /// Can occur if passed varaint is not alllowed. + pub fn check_variant + ( + variant : &str, + allowed : Vec< &str > + ) -> Result< () > + { + if allowed.contains( &variant ) + { + Ok( () ) + } + else + { + Err + ( + Error::ParseError( format!( "Not suchvariant: {}. Allowed: {:?}", variant, allowed ) ) + ) + } + } + + /// # parse_json + /// + /// Parse passed json to HashMap< String, serde_json::Value > + /// + /// ## Returns + /// - `Result< HashMap< String, serde_json::Value > >` + /// + /// ## Errors + /// + /// Can occur if the passed json is not valid. + pub fn parse_json + ( + json_str : &str + ) -> Result< HashMap< String, serde_json::Value > > + { + let parsed_json : HashMap< String, serde_json::Value > = serde_json::from_str( json_str ) + .map_err( | error | Error::InvalidJSON( format!( "Failed to parse JSON: {}", error ) ) )?; + + Ok( parsed_json ) + } + + /// # `get_spreadsheet_id_from_url` + /// + /// Retrieves the spreadsheet ID from the provided Google Sheets URL. + /// + /// ## Parameters: + /// - `url`: + /// A `&str` containing the full URL of the Google spreadsheet. + /// + /// ## Returns: + /// - `Result< &str >` + /// + /// ## Errors: + /// - `Error::InvalidUrl`: + /// Occurs when the URL does not match the expected format. + /// Suggests copying the entire URL directly from the browser. + pub fn get_spreadsheet_id_from_url + ( + url : &str + ) -> Result< &str > + { + + let re = Regex::new( r"d/([^/]+)/edit" ).unwrap(); + if let Some( captures ) = re.captures( url ) + { + if let Some( id ) = captures.get( 1 ) + { + return Ok( id.as_str() ); + } + } + + Err + ( + Error::InvalidUrl( "Wrong url format.\nFix: copy sheet's the whole url from your browser. Usage: --url ''".to_string() ) + ) + } +} + +crate::mod_interface! +{ + own use + { + parse_json, + parse_key_by, + parse_on_find, + parse_on_fail, + check_variant, + get_spreadsheet_id_from_url + }; +} \ No newline at end of file diff --git a/module/move/gspread/src/bin/main.rs b/module/move/gspread/src/bin/main.rs new file mode 100644 index 0000000000..eed834026b --- /dev/null +++ b/module/move/gspread/src/bin/main.rs @@ -0,0 +1,45 @@ +use std::error::Error; +use clap::Parser; +use dotenv::dotenv; + +use gspread::*; +use gcore::ApplicationSecret; +use gcore::client:: +{ + Auth, + Client +}; + +use commands:: +{ + self, + Cli, + CliCommand +}; + + +#[ tokio::main ] +async fn main() -> Result< (), Box< dyn Error > > +{ + dotenv().ok(); + + let secret = ApplicationSecret::read(); + + let auth = Auth::new( &secret ); + + let client = Client::former() + .auth( auth ) + .form(); + + let cli = Cli::parse(); + + match cli.command + { + CliCommand::GSpread( cmd ) => + { + commands::gspread::command( &client, cmd ).await; + } + } + + Ok( () ) +} diff --git a/module/move/gspread/src/bin/test.rs b/module/move/gspread/src/bin/test.rs new file mode 100644 index 0000000000..6858da47d9 --- /dev/null +++ b/module/move/gspread/src/bin/test.rs @@ -0,0 +1,102 @@ +use std::error::Error; +use dotenv::dotenv; +use gspread::*; +use gcore::ApplicationSecret; +use gcore::client:: +{ + Auth, + Client +}; + +use std::collections::HashMap; +use serde_json::json; +use rand::Rng; +use rand::rngs::OsRng; + + +#[ tokio::main ] +async fn main() -> Result< (), Box< dyn Error > > +{ + dotenv().ok(); + + let secret = ApplicationSecret::read(); + + let auth = Auth::new( &secret ); + + let client = Client::former() + .auth( auth ) + .form(); + + let spreadsheet_ids = vec![ + "172krpHTo_BI8Bwm9-9aGc5Bt9tm6P3nbiwkveVbO81k", + ]; + let tables = vec!["t1"]; + let mut row_key_val = generate_truly_random_key_val(5000, 100); + + for &spreadsheet_id in &spreadsheet_ids { + for i in 0..5 { + for &sheet_name in &tables { + row_key_val.insert("A".to_string(), json!(i)); + _ = gspread::actions::gspread::append_row(&client, spreadsheet_id, sheet_name, &row_key_val).await; + } + } + } + + Ok( () ) +} + + +fn generate_truly_random_key_val(n: usize, str_len: usize) -> HashMap { + let all_cols = generate_all_columns(); + let total = all_cols.len(); + + let mut rng = OsRng; + let mut indices: Vec = (0..total).collect(); + + for i in 0..total { + let j = i + (rng.gen_range(0..(total - i))); + indices.swap(i, j); + } + + let chosen_indices = &indices[0..n.min(total)]; + + let mut result = HashMap::new(); + for &idx in chosen_indices { + let col = &all_cols[idx]; + let val = random_string(&mut rng, str_len); + result.insert(col.clone(), json!(val)); + } + result +} + +fn random_string(rng: &mut OsRng, length: usize) -> String { + let charset = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\ + abcdefghijklmnopqrstuvwxyz\ + 0123456789"; + (0..length) + .map(|_| { + let idx = rng.gen_range(0..charset.len()); + charset[idx] as char + }) + .collect() +} + +fn generate_all_columns() -> Vec { + let mut columns = Vec::new(); + for c1 in b'A'..=b'Z' { + columns.push((c1 as char).to_string()); + } + for c1 in b'A'..=b'Z' { + for c2 in b'A'..=b'Z' { + columns.push(format!("{}{}", c1 as char, c2 as char)); + } + } + for c1 in b'A'..=b'Z' { + for c2 in b'A'..=b'Z' { + for c3 in b'A'..=b'Z' { + columns.push(format!("{}{}{}", c1 as char, c2 as char, c3 as char)); + } + } + } + columns +} \ No newline at end of file diff --git a/module/move/gspread/src/commands.rs b/module/move/gspread/src/commands.rs new file mode 100644 index 0000000000..2ae8f78f3d --- /dev/null +++ b/module/move/gspread/src/commands.rs @@ -0,0 +1,70 @@ +//! +//! Commands +//! + + +mod private +{ + use clap:: + { + Parser, + Subcommand + }; + use crate::*; + use commands::gspread; + + /// # Cli + /// + /// The main structure representing the CLI interface of the tool. + /// + /// This struct is the entry point for parsing and handling command-line arguments using the `clap` crate. + /// + /// ## Fields: + /// - `command`: + /// A `CliCommand` enum that specifies the root command and its subcommands. + #[ derive ( Debug, Parser ) ] + pub struct Cli + { + /// Root of the CLI commands. + #[ command ( subcommand ) ] + pub command : CliCommand, + } + + /// # CliCommand + /// + /// An enumeration of all root-level CLI commands. + /// + /// Each variant represents a category of commands or a specific functionality the tool provides. + /// + /// ## Variants: + /// - `GSpread`: + /// Handles commands related to Google Sheets (`gspread`). + /// Delegates to the `gspread::Command` for further subcommands and logic. + #[ derive ( Debug, Subcommand ) ] + pub enum CliCommand + { + #[ command ( subcommand, long_about = "\n\nGoogle Sheets commands.", name = "gspread" ) ] + GSpread( gspread::Command ), + } + +} + +crate::mod_interface! +{ + layer gspread; + layer gspread_header; + layer gspread_rows; + layer gspread_cell; + layer gspread_row; + layer gspread_column; + layer gspread_clear; + layer gspread_clear_custom; + layer gspread_copy; + + own use + { + Cli, + CliCommand, + }; +} + diff --git a/module/move/gspread/src/commands/gspread.rs b/module/move/gspread/src/commands/gspread.rs new file mode 100644 index 0000000000..653dfaf0e4 --- /dev/null +++ b/module/move/gspread/src/commands/gspread.rs @@ -0,0 +1,454 @@ +//! +//! Collection of Google Sheets API commands. +//! + + +mod private +{ + + use clap:: + { + Subcommand, + Parser + }; + use gcore::client::Client; + + use crate::*; + use gcore::Secret; + use commands:: + { + gspread_header, + gspread_row, + gspread_rows, + gspread_cell, + gspread_column, + gspread_clear, + gspread_clear_custom, + gspread_copy + }; + + /// # CommonArgs + /// + /// Structure containing common command-line arguments for `gspread` commands. + /// + /// ## Fields: + /// - `url`: + /// The full URL of the Google Sheet. + /// Example: `'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}'` + /// - `tab`: + /// The name of the specific sheet to target. + /// Example: `Sheet1` + #[ derive( Debug, Parser ) ] + pub struct CommonArgs + { + #[ arg( long, help = "Full URL of Google Sheet.\n\ + It has to be inside of '' to avoid parse errors.\n\ + Example: 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}'" ) ] + pub url : String, + + #[ arg( long, help = "Sheet name.\nExample: Sheet1" ) ] + pub tab : String + } + + /// # Command + /// + /// Enum representing all available `gspread` commands. + /// + /// ## Variants: + /// - `Header`: Retrieves the header (first row) of a specific sheet. + /// - `Rows`: Retrieves all rows (excluding the header) from a specific sheet. + /// - `Cell`: Retrieves or updates a single cell in a sheet. + /// - `Cells`: Updates multiple cells in a specific row. + /// - `Row`: Updates or appends rows. + /// - `Column`: Retrives a column. + /// - `Clear`: Clears a sheet. + /// - `ClearCustom`: Clears a range specified bu row key and on-find arguments. + /// - `Copy`: Copies a spreadsheet's sheet to other spreadsheet. + /// + /// ## Examples: + /// - Retrieve the header: + /// ```bash + /// gspread header --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' --tab Sheet1 + /// ``` + /// - Retrieve all rows: + /// ```bash + /// gspread rows --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' --tab Sheet1 + /// ``` + /// - Retrieve a single cell: + /// ```bash + /// gspread cell get --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' --tab Sheet1 --cell A1 + /// ``` + /// - Update a single cell: + /// ```bash + /// gspread cell set --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' --tab Sheet1 --cell A1 --val NewVal + /// ``` + /// - Update multiple cells in a single row: + /// ```bash + /// gspread cells set + /// --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' --tab Sheet1 --select-row-by-key "id" --json '{"id": "2", "A": "1", "B": "2"}' + /// ``` + /// - Update rows: + /// ```bash + /// gspread row update-custom + /// --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' --tab tab8 --json '{"A": "1", "B": "2"}' --key-by '["A", 800]' --on-fail append --on-find all + /// ``` + /// - Append a new row: + /// ```bash + /// gspread row append + /// --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' --tab tab8 --json '{ "D": 800, "F": 400, "H": 200 }' + /// ``` + /// - Retrive a column: + /// ```bash + /// gspread column get + /// --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' --tab tab8 --column-id 'A' + /// ``` + /// - Clear sheet: + /// ```bash + /// gspread clear + /// --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' --tab tab8 + /// ``` + /// - Clear a range specified by row key: + /// ```bash + /// gspread clear-custom + /// --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' --tab tab1 --key-by '["A", 4]' --on-find all + /// ``` + /// - Copy a sheet from a specified spreadsheet to the other one. + /// ```bash + /// gspread copy + /// --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' --sheet-id 1484163460 + /// --dest 'https://docs.google.com/spreadsheets/d/{dest_spreadsheet_id}/edit?gid={dest_sheet_id}#gid={dest_sheet_id}' + /// ``` + #[ derive( Debug, Subcommand ) ] + pub enum Command + { + #[ command( name = "header", about = "Retrieves the header (first row).", long_about = r#" +--------------------------------------------------------------------------------------------------------------- + HEADER +--------------------------------------------------------------------------------------------------------------- + ● Description: + ↓ ↓ ↓ ↓ ↓ ↓ + + Retrieves the header (first row) of a specific sheet in the same view as in Google Sheet. + +--------------------------------------------------------------------------------------------------------------- + ● Command example: + ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ + + cargo run gspread header \ + --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' \ + --tab tab1 + +--------------------------------------------------------------------------------------------------------------- + ● Output: Prints a retrieved header in a table view: + ↓ ↓ ↓ ↓ + + Header: + │ 0 │ 1 │ 2 │ <---- Just column enumeration. + ───────────────────────── + │ Name │ Surname │ Age │ <---- Header. +--------------------------------------------------------------------------------------------------------------- + ● Errors: + ↓ ↓ ↓ ↓ + + ◦ Error::ApiError: + ---------------------------------------------------------------- + Occurs if the Google Sheets API returns an error, + such as an invalid spreadsheet ID, insufficient permissions + or invalid sheet name. + ---------------------------------------------------------------- + + ◦ Error::InvalidURL: + ---------------------------------------------------------------------- + Occurs when you passed url with invalid format of your spreasdsheet. + ---------------------------------------------------------------------- + +--------------------------------------------------------------------------------------------------------------- + "# ) ] + Header( CommonArgs ), + + #[ command( name = "rows", about = "Retrieves all rows but not header.", long_about = r#" +--------------------------------------------------------------------------------------------------------------- + ROWS +--------------------------------------------------------------------------------------------------------------- + ● Description: + ↓ ↓ ↓ ↓ ↓ ↓ + + Retrieves all rows of a specific sheet but not header in the same view as in Google Sheet. + +--------------------------------------------------------------------------------------------------------------- + ● Command example: + ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ + + cargo run gspread rows \ + --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' \ + --tab tab1 + +--------------------------------------------------------------------------------------------------------------- + ● Output: Prints retrieved rows in a table view: + ↓ ↓ ↓ ↓ + + Rows: + │ 0 │ 1 │ 2 │ <---- Just column enumeration. + ───────────────────────── + │ name1 │ surname1 │ 20 │ <---- The first row after header. + │ name2 │ surname2 │ 85 │ + | ... | ... | .. | + +--------------------------------------------------------------------------------------------------------------- + ● Errors: + ↓ ↓ ↓ ↓ + + ◦ Error::ApiError: + ---------------------------------------------------------------- + Occurs if the Google Sheets API returns an error, + such as an invalid spreadsheet ID, insufficient permissions + or invalid sheet name. + ---------------------------------------------------------------- + + ◦ Error::InvalidURL: + ---------------------------------------------------------------------- + Occurs when you passed url with invalid format of your spreasdsheet. + ---------------------------------------------------------------------- + +--------------------------------------------------------------------------------------------------------------- + "# ) ] + Rows( CommonArgs ), + + #[ command ( subcommand, name = "cell", about = "Retrieves or updates a single cell." ) ] + Cell( gspread_cell::Commands ), + + #[ command( subcommand, name = "row", about = "Updates, appends or retrieves a row." ) ] + Row( gspread_row::Commands ), + + #[ command( subcommand, name = "column", about = "Retrieves a column." ) ] + Column( gspread_column::Commands ), + + #[ command( name = "clear", about = "Completely clears the sheet.", long_about = r#" +--------------------------------------------------------------------------------------------------------------- + CLEAR +--------------------------------------------------------------------------------------------------------------- + ● Description: + ↓ ↓ ↓ ↓ ↓ ↓ + + Completely clears the sheet. + +--------------------------------------------------------------------------------------------------------------- + ● Command example: + ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ + + cargo run gspread clear \ + --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' \ + --tab tab1 + +--------------------------------------------------------------------------------------------------------------- + ● Output: Prints a message with cleared range: + ↓ ↓ ↓ ↓ + + Range 'tab1'!A1:Z1000 was successfully cleared + +--------------------------------------------------------------------------------------------------------------- + ● Errors: + ↓ ↓ ↓ ↓ + + ◦ Error::ApiError: + ---------------------------------------------------------------- + Occurs if the Google Sheets API returns an error, + such as an invalid spreadsheet ID, insufficient permissions + or invalid sheet name. + ---------------------------------------------------------------- + + ◦ Error::InvalidURL: + ---------------------------------------------------------------------- + Occurs when you passed url with invalid format of your spreasdsheet. + ---------------------------------------------------------------------- + +--------------------------------------------------------------------------------------------------------------- + "# ) ] + Clear( CommonArgs ), + + #[ command( name = "clear-custom", about = "Clears range sprecified by `key-by` and `on-find` action.", long_about = r#" +--------------------------------------------------------------------------------------------------------------- + CLEAR-CUSTOM +--------------------------------------------------------------------------------------------------------------- + ● Description: + ↓ ↓ ↓ ↓ ↓ ↓ + + Clears range specified by `key-by` and `on-find` action. + + `key-by` is a tuple of column id and value to find in that column. + For example, --key-by ["A", 2] means "We are looking for value `2` in the column with id `A`". + + `on-find` is the action to perform upon finding that value. There are 3 variants: + 1. Clear only the first matched row. + 2. Clear only the last matched row. + 3. Clear all matched rows. + + For example, consider the following table: + |-----------| + | A | B | C | + |-----------| + | 1 | . | . | + | 1 | . | . | + | 2 | . | . | + | 3 | . | . | + | 1 | . | . | + |-----------| + + If we run: `cargo run clear-custom ... --key-by ["A", 1] --on-find (action)` + the program will find all rows which contain the value `1` in column `A` + and will clear them according to the specified `on-find` action. + + If there are no matches, nothing happens. + +--------------------------------------------------------------------------------------------------------------- + ● Command example: + ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ + + cargo run gspread clear-custom \ + --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' \ + --tab tab1 \ + --key-by '["A", 4]' \ + --on-find all + +--------------------------------------------------------------------------------------------------------------- + ● Output: Prints a message with cleared ranges: + ↓ ↓ ↓ ↓ + + Updated ranges: ["'tab1'!A2:Z2"] + +--------------------------------------------------------------------------------------------------------------- + ● Errors: + ↓ ↓ ↓ ↓ + + ◦ Error::ApiError: + ---------------------------------------------------------------- + Occurs if the Google Sheets API returns an error, + such as an invalid spreadsheet ID, insufficient permissions + or invalid sheet name. + ---------------------------------------------------------------- + + ◦ Error::ParseError: + --------------------------------------------------------- + Occurs when serde_json can not parse an argument + --------------------------------------------------------- + + ◦ Error::InvalidURL: + ---------------------------------------------------------------------- + Occurs when you passed url with invalid format of your spreasdsheet. + ---------------------------------------------------------------------- + +--------------------------------------------------------------------------------------------------------------- + "# ) ] + ClearCustom( gspread_clear_custom::Args ), + + #[ command( name = "copy", about = "Copies a spreadsheet's sheet to the another spreadsheet.", long_about = r#" +--------------------------------------------------------------------------------------------------------------- + COPY +--------------------------------------------------------------------------------------------------------------- + ● Description: + ↓ ↓ ↓ ↓ ↓ ↓ + + Copies a spreadsheet's sheet specified by `--url` and `--sheet-id` arguments + to another spreadsheet defined by the `--dest` argument. + +--------------------------------------------------------------------------------------------------------------- + ● Command example: + ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ + + cargo run gspread copy \ + --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' \ + --sheet-id 1484163460 \ + --dest 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' + +--------------------------------------------------------------------------------------------------------------- + ● Output: Prints a message like this: + ↓ ↓ ↓ ↓ + + A sheet was successfully copied to a new one with title 'tab1 (copy)' + +--------------------------------------------------------------------------------------------------------------- + ● Errors: + ↓ ↓ ↓ ↓ + + ◦ Error::ApiError: + ---------------------------------------------------------------- + Occurs if the Google Sheets API returns an error, + such as an invalid spreadsheet ID, insufficient permissions + or invalid sheet name. + ---------------------------------------------------------------- + + ◦ Error::InvalidURL: + ---------------------------------------------------------------------- + Occurs when you passed url with invalid format of your spreasdsheet. + ---------------------------------------------------------------------- + +--------------------------------------------------------------------------------------------------------------- + "# )] + Copy( gspread_copy::Args ) + + } + + /// # `command` + /// + /// Executes the appropriate `gspread` command. + pub async fn command< S : Secret > + ( + client : &Client< '_, S >, + command : Command, + ) + { + match command + { + Command::Header( header_command ) => + { + gspread_header::command( client, header_command ).await; + }, + + Command::Rows( rows_command ) => + { + gspread_rows::command( client, rows_command ).await; + }, + + Command::Cell( cell_command ) => + { + gspread_cell::command( client, cell_command ).await; + }, + + Command::Row( row_command ) => + { + gspread_row::command( client, row_command ).await; + }, + + Command::Column( column_command ) => + { + gspread_column::command( client, column_command ).await; + }, + + Command::Clear( clear_command ) => + { + gspread_clear::command( client, clear_command ).await; + }, + + Command::ClearCustom( args ) => + { + gspread_clear_custom::command( client, args ).await; + }, + + Command::Copy( args ) => + { + gspread_copy::command( client, args ).await; + } + } + } + +} + +crate::mod_interface! +{ + own use + { + CommonArgs, + Command, + command, + }; +} \ No newline at end of file diff --git a/module/move/gspread/src/commands/gspread_cell.rs b/module/move/gspread/src/commands/gspread_cell.rs new file mode 100644 index 0000000000..24e2815b3d --- /dev/null +++ b/module/move/gspread/src/commands/gspread_cell.rs @@ -0,0 +1,290 @@ +//! +//! Collection of subcommands fo command "cell" +//! + +mod private +{ + + use clap::Subcommand; + use crate::*; + + use gcore::client::Client; + use gcore::Secret; + use actions; + use actions::utils::get_spreadsheet_id_from_url; + + /// # Commands + /// + /// Subcommands for the `CELL` command, used to interact with individual cells in a Google Sheet. + /// + /// ## Variants: + /// + /// ### `Get` + /// + /// Retrieves the value of a specific cell. + /// + /// **Arguments:** + /// - `url`: + /// The full URL of the Google Sheet. + /// Example: `'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}'`. + /// + /// - `tab`: + /// The name of the specific sheet to target. + /// Example: `Sheet1`. + /// + /// - `cell`: + /// The ID of the cell in the format `A1`, where `A` is the column and `1` is the row. + /// Example: `A4`. + /// + /// **Example:** + /// ```bash + /// gspread cell get \ + /// --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' \ + /// --tab tab1 \ + /// --cell A1 + /// ``` + /// + /// ### `Set` + /// + /// Updates the value of a specific cell. + /// + /// **Arguments:** + /// - `url`: + /// The full URL of the Google Sheet. + /// Example: `'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}'`. + /// + /// - `tab`: + /// The name of the specific sheet to target. + /// Example: `Sheet1`. + /// + /// - `cell`: + /// The ID of the cell in the format `A1`, where `A` is the column and `1` is the row. + /// Example: `A4`. + /// + /// - `val`: + /// The value to set in the specified cell. + /// Example: `hello`. + /// + /// **Example:** + /// ```bash + /// gspread cell set \ + /// --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' \ + /// --tab tab1 \ + /// --cell A1 \ + /// --val 13 + /// ``` + #[ derive( Debug, Subcommand ) ] + #[ command( long_about = "\n\nSubcommands for the `CELL` command, used to interact with individual cells in a Google Sheet." ) ] + pub enum Commands + { + #[ command( name = "get", about = "Retrieves a single cell.", long_about = r#" +--------------------------------------------------------------------------------------------------------------- + CELL GET +--------------------------------------------------------------------------------------------------------------- + ● Description: + ↓ ↓ ↓ ↓ ↓ ↓ + + Retrieves a single cell specified by the `--cell` argument in A1 notation. + +--------------------------------------------------------------------------------------------------------------- + ● Command example: + ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ + + cargo run gspread cell get \ + --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' \ + --tab tab1 \ + --cell A1 + +--------------------------------------------------------------------------------------------------------------- + ● Output: Prints the value of the cell: + ↓ ↓ ↓ ↓ + + Value: "Name" + +--------------------------------------------------------------------------------------------------------------- + ● Errors: + ↓ ↓ ↓ ↓ + + ◦ Error::ApiError: + ---------------------------------------------------------------- + Occurs if the Google Sheets API returns an error, + such as an invalid spreadsheet ID, insufficient permissions + or invalid sheet name. + ---------------------------------------------------------------- + + ◦ Error::InvalidURL: + ---------------------------------------------------------------------- + Occurs when you pass a URL with an invalid spreadsheet format. + ---------------------------------------------------------------------- + +--------------------------------------------------------------------------------------------------------------- + "# ) ] + Get + { + #[ arg( long, help = "Full URL of Google Sheet.\n\ + It has to be inside of '' to avoid parse errors.\n\ + Example: 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}'" ) ] + url : String, + + #[ arg( long, help = "Sheet name.\nExample: Sheet1" ) ] + tab : String, + + #[ arg( long, help = "Cell id. You can set it in format:\n \ + - A1, where A is column name and 1 is row number\n\ + Example: --cell A4" ) ] + cell : String, + }, + + #[ command( name = "set", about = "Updates a single cell.", long_about = r#" +--------------------------------------------------------------------------------------------------------------- + CELL SET +--------------------------------------------------------------------------------------------------------------- +● Description: + ↓ ↓ ↓ ↓ ↓ ↓ + + Updates a single cell specified by `--cell` (in A1 notation) and `--val`. + +--------------------------------------------------------------------------------------------------------------- + ● Command example: + ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ + + cargo run gspread cell set \ + --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' \ + --tab tab1 \ + --cell A1 \ + --val 'New Value' + +--------------------------------------------------------------------------------------------------------------- + ● Output: Prints a message indicating the number of cells updated: + ↓ ↓ ↓ ↓ + + You successfully update 1 cell! + +--------------------------------------------------------------------------------------------------------------- + ● Errors: + ↓ ↓ ↓ ↓ + + ◦ Error::ApiError: + ---------------------------------------------------------------- + Occurs if the Google Sheets API returns an error, + such as an invalid spreadsheet ID, insufficient permissions + or invalid sheet name. + ---------------------------------------------------------------- + + ◦ Error::ParseError: + --------------------------------------------------------- + Occurs when serde_json::Value parse error + --------------------------------------------------------- + + ◦ Error::InvalidURL: + ---------------------------------------------------------------------- + Occurs when you pass a URL with an invalid spreadsheet format. + ---------------------------------------------------------------------- + +--------------------------------------------------------------------------------------------------------------- + "# ) ] + Set + { + #[ arg( long, help = "Full URL of Google Sheet.\n\ + It has to be inside of '' to avoid parse errors.\n\ + Example: 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}'" ) ] + url : String, + + #[ arg( long, help = "Sheet name.\nExample: Sheet1" ) ] + tab : String, + + #[ arg( long, help = "Cell id. You can set it in format:\n \ + - A1, where A is column name and 1 is row number\n\ + Example: --cell A4" ) ] + cell : String, + + #[ arg( long, help = "Value you want to set. It can be written on any language.\nExample: --val hello" ) ] + val : String + } + } + + /// # `command` + /// + /// Executes the specified subcommand for the `CELL` command. + /// + /// ## Parameters: + /// - `client`: + /// A `Client` type. + /// - `commands`: + /// A variant of the `Commands` enum specifying the operation to execute. + /// + /// ## Errors: + /// - Prints an error message if the spreadsheet ID extraction, retrieval, or update fails. + pub async fn command< S : Secret > + ( + client : &Client< '_, S >, + commands : Commands + ) + { + match commands + { + Commands::Get { url, tab, cell } => + { + let spreadsheet_id = match get_spreadsheet_id_from_url( url.as_str() ) + { + Ok( id ) => id, + Err( error ) => + { + eprintln!( "Error extracting spreadsheet ID: {}", error ); + return; + } + }; + + match actions::gspread_cell_get::action + ( + client, + spreadsheet_id, + tab.as_str(), + cell.as_str() + ) + .await + { + Ok( value ) => println!( "Value: {}", value ), + Err( error ) => println!( "Error:\n{}", error ), + } + }, + + Commands::Set { url, tab, cell, val } => + { + let spreadsheet_id = match get_spreadsheet_id_from_url( url.as_str() ) + { + Ok( id ) => id, + Err( error ) => + { + eprintln!( "Error extracting spreadsheet ID: {}", error ); + return; + } + }; + + match actions::gspread_cell_set::action + ( + client, + spreadsheet_id, + tab.as_str(), + cell.as_str(), + val.as_str() + ) + .await + { + Ok( number ) => println!( "You successfully update {} cell!", number ), + Err( error ) => println!( "Error:\n{}", error ), + } + } + + } + } +} + +crate::mod_interface! +{ + own use + { + command, + Commands, + }; +} \ No newline at end of file diff --git a/module/move/gspread/src/commands/gspread_clear.rs b/module/move/gspread/src/commands/gspread_clear.rs new file mode 100644 index 0000000000..87b55a3f96 --- /dev/null +++ b/module/move/gspread/src/commands/gspread_clear.rs @@ -0,0 +1,55 @@ +//! +//! clear command +//! + +mod private +{ + use crate::*; + use gcore::Secret; + use gcore::client::Client; + use commands::gspread::CommonArgs; + use actions::utils::get_spreadsheet_id_from_url; + + pub async fn command< S : Secret > + ( + client : &Client< '_, S >, + args : CommonArgs + ) + { + match args + { + CommonArgs { url, tab } => + { + let spreadsheet_id = match get_spreadsheet_id_from_url( url.as_str() ) + { + Ok( id ) => id, + Err( error ) => + { + eprintln!( "Error extracting spreadsheet ID: {}", error ); + return; + } + }; + + match actions::gspread_clear::action + ( + client, + spreadsheet_id, + &tab + ) + .await + { + Ok( range ) => println!( "Range {range} was successfully cleared" ), + Err( error ) => eprintln!( "Error:\n{error}" ) + } + } + } + } +} + +crate::mod_interface! +{ + own use + { + command, + }; +} \ No newline at end of file diff --git a/module/move/gspread/src/commands/gspread_clear_custom.rs b/module/move/gspread/src/commands/gspread_clear_custom.rs new file mode 100644 index 0000000000..71f4d10833 --- /dev/null +++ b/module/move/gspread/src/commands/gspread_clear_custom.rs @@ -0,0 +1,79 @@ + + +mod private +{ + use clap::Parser; + + use crate::*; + use gcore::Secret; + use gcore::client::Client; + use actions::utils::get_spreadsheet_id_from_url; + + #[ derive( Debug, Parser ) ] + pub struct Args + { + #[ arg( long, help = "Full URL of Google Sheet.\n\ + It has to be inside of '' to avoid parse errors.\n\ + Example: 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}'" ) ] + pub url : String, + + #[ arg( long, help = "Sheet name.\nExample: Sheet1" ) ] + pub tab : String, + + #[ arg( long, help = "A string with key pair view, like [\"A\", \"new_val\"], where A is a column index." ) ] + key_by : String, + + #[ arg( long, help = "Action to take if one or more rows are found. + Available: + - all - Clear all matched rows. + - first - Clear first matched. + - last - Clear last matched." ) ] + on_find : String + } + + pub async fn command< S : Secret > + ( + client : &Client< '_, S >, + command : Args + ) + { + match command + { + Args{ url, tab, key_by, on_find } => + { + let spreadsheet_id = match get_spreadsheet_id_from_url( url.as_str() ) + { + Ok( id ) => id, + Err( error ) => + { + eprintln!( "Error extracting spreadsheet ID: {}", error ); + return; + } + }; + + match actions::gspread_clear_custom::action + ( + client, + spreadsheet_id, + &tab, + &key_by, + &on_find + ) + .await + { + Ok( ranges ) => println!( "Updated ranges: {:?}", ranges ), + Err( error ) => eprintln!( "Error:\n{error}" ) + } + } + } + } +} + +crate::mod_interface! +{ + own use + { + Args, + command + }; +} \ No newline at end of file diff --git a/module/move/gspread/src/commands/gspread_column.rs b/module/move/gspread/src/commands/gspread_column.rs new file mode 100644 index 0000000000..d259d25196 --- /dev/null +++ b/module/move/gspread/src/commands/gspread_column.rs @@ -0,0 +1,192 @@ +//! +//! Command column. +//! + +mod private +{ + use clap::Subcommand; + use crate::*; + use gcore::Secret; + use gcore::client::Client; + use debug:: + { + RowWrapper, + Report + }; + use actions:: + { + self, + utils::get_spreadsheet_id_from_url + }; + + + /// # Commands + /// + /// Subcommands for `COLUMN` command + /// + /// ## Variants: + /// + /// ### `Get` + /// Retreive a column from a Google Sheet. + /// + /// **Arguments:** + /// - `url`: + /// The full URL of the Google Sheet. + /// Example: + /// `--url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}'` + /// + /// - `tab`: + /// The name of the specific sheet (tab) in the Google Spreadsheet. + /// Example: + /// `--tab 'Sheet1'` + /// + /// - `column_id`: + /// Column id. In the range from A to ZZZ. + /// Example: + /// `--column-id=A` + /// + /// **Example:** + /// ```bash + /// gspread column get + /// --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' \ + /// --tab 'tab1' \ + /// --column-id 'A' + /// ``` + #[ derive( Debug, Subcommand ) ] + #[ command( long_about = "\n\nSubcommands for `COLUMN` command." ) ] + pub enum Commands + { + #[ command( name = "get", about = "Retreive a column from a Google Sheet.", long_about = r#" +--------------------------------------------------------------------------------------------------------------- + COLUMN-GET +--------------------------------------------------------------------------------------------------------------- + ● Description: + ↓ ↓ ↓ ↓ ↓ ↓ + + Retrieves a column from a Google Sheet as specified by the `--column-id` argument. + +--------------------------------------------------------------------------------------------------------------- + ● Command example: + ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ + + cargo run gspread column get \ + --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' \ + --tab 'tab1' \ + --column-id 'A' + +--------------------------------------------------------------------------------------------------------------- + ● Output: Prints the retrieved column: + ↓ ↓ ↓ ↓ + + Column: + │ 0 │ + ─────────── + │ "Name" │ + │ 1 │ + │ "name2" │ + │ true │ + +--------------------------------------------------------------------------------------------------------------- + ● Errors: + ↓ ↓ ↓ ↓ + + ◦ Error::ApiError: + ---------------------------------------------------------------- + Occurs if the Google Sheets API returns an error, + such as an invalid spreadsheet ID, insufficient permissions + or invalid sheet name. + ---------------------------------------------------------------- + + ◦ Error::ParseError: + --------------------------------------------------------- + Occurs when serde_json::Value parse error. + --------------------------------------------------------- + + ◦ Error::InvalidURL: + ---------------------------------------------------------------------- + Occurs when you pass a URL with an invalid spreadsheet format. + ---------------------------------------------------------------------- + +--------------------------------------------------------------------------------------------------------------- + "# ) ] + Get + { + #[ arg( long, help = "Full URL of Google Sheet.\n\ + It has to be inside of '' to avoid parse errors.\n\ + Example: 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}'" ) ] + url : String, + + #[ arg( long, help = "Sheet name.\nExample: Sheet1" ) ] + tab : String, + + #[ arg( long, help = "Column id, in range from A to ZZZ" ) ] + column_id : String + } + } + + /// # `command` + /// + /// Executes the specified subcommand for the `COLUMN` command. + /// + /// ## Parameters: + /// - `client`: + /// A `Client` type. + /// - `commands`: + /// A variant of the `Commands` enum specifying the operation to execute. + /// + /// ## Errors: + /// - Prints an error message if the spreadsheet ID extraction, retrieval, or update fails. + pub async fn command< S : Secret > + ( + client : &Client< '_, S >, + commands : Commands + ) + { + match commands + { + Commands::Get { url, tab, column_id } => + { + let spreadsheet_id = match get_spreadsheet_id_from_url( &url ) + { + Ok( id ) => id, + Err( error ) => + { + eprintln!( "Error extracting spreadsheet ID: {}", error ); + return; + } + }; + + match actions::gspread_column_get::action + ( + client, + spreadsheet_id, + &tab, + &column_id + ) + .await + { + Ok( column ) => + { + let column_wrapped = column + .into_iter() + .map( | row | RowWrapper{ row : vec![ row ], max_len : 1 } ) + .collect(); + + println!( "Column:\n{}", Report{ rows : column_wrapped } ) + } + Err( error ) => eprintln!( "Error:\n{}", error ) + } + } + } + } + +} + +crate::mod_interface! +{ + own use + { + command, + Commands + }; +} \ No newline at end of file diff --git a/module/move/gspread/src/commands/gspread_copy.rs b/module/move/gspread/src/commands/gspread_copy.rs new file mode 100644 index 0000000000..1455c73722 --- /dev/null +++ b/module/move/gspread/src/commands/gspread_copy.rs @@ -0,0 +1,106 @@ +//! +//! Command copy +//! + +mod private +{ + use clap::Parser; + + use crate::*; + use gcore::Secret; + use gcore::client::Client; + use actions:: + { + self, + utils::get_spreadsheet_id_from_url + }; + + /// # Args + /// + /// Structure containing arguments of `copy` command. + /// + /// ## Fields: + /// - `url`: + /// The full URL of the Google Sheet. + /// Example: `'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}'` + /// - `sheet_id`: + /// Source sheet id. + /// Example: `1484163460` + /// - `dest`: + /// Destination spreadsheet url. + /// Example: `https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}` + #[ derive( Debug, Parser ) ] + pub struct Args + { + #[ arg( long, help = "Full URL of Google Sheet.\n\ + It has to be inside of '' to avoid parse errors.\n\ + Example: 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}'" ) ] + pub url : String, + + #[ arg( long, help = "Source Sheet id. You can find it in a sheet url, in the 'gid' query parameter.\n\ + Example: https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}\n\ + Sheet Id Example: 1484163460" ) ] + pub sheet_id : String, + + #[ arg( long, help = "Destination spreadsheet id. + It has to be inside of '' to avoid parse errors.\n\ + Example: 'https://docs.google.com/spreadsheets/d/{dest_spreadsheet_id}/edit?gid={dest_sheet_id}#gid={dest_sheet_id}'" ) ] + pub dest : String + } + + pub async fn command< S : Secret > + ( + client : &Client< '_, S >, + args : Args + ) + { + match args + { + Args { url, sheet_id, dest } => + { + let spreadsheet_id = match get_spreadsheet_id_from_url( url.as_str() ) + { + Ok( id ) => id, + Err( error ) => + { + eprintln!( "Error extracting spreadsheet ID: {}", error ); + return; + } + }; + + let dest = match get_spreadsheet_id_from_url( &dest ) + { + Ok( id ) => id, + Err( error ) => + { + eprintln!( "Error extracting spreadsheet ID: {}", error ); + return; + } + }; + + match actions::gspread_copy::action + ( + client, + spreadsheet_id, + &sheet_id, + dest + ) + .await + { + Ok( title ) => println!( "A sheet was successfully copied to a new one with title '{title}'" ), + Err( error ) => eprintln!( "Error:\n{error}" ) + } + } + } + } + +} + +crate::mod_interface! +{ + own use + { + Args, + command + }; +} \ No newline at end of file diff --git a/module/move/gspread/src/commands/gspread_header.rs b/module/move/gspread/src/commands/gspread_header.rs new file mode 100644 index 0000000000..5cce477af7 --- /dev/null +++ b/module/move/gspread/src/commands/gspread_header.rs @@ -0,0 +1,113 @@ +//! +//! Command "header" +//! + +mod private +{ + use std::fmt; + use crate::*; + use debug::RowWrapper; + use gcore::Secret; + use gcore::client::Client; + use commands::gspread::CommonArgs; + use actions; + use actions::utils::get_spreadsheet_id_from_url; + use format_tools::AsTable; + use utils::display_table::display_header; + + /// # Report + /// + /// A structure to display the retrieved header in the console using `format_tools`. + /// + /// ## Fields: + /// - `header`: + /// A `Vec< RowWrapper >` representing the retrieved header rows. + /// + /// ## Usage: + /// This structure is used in conjunction with the `fmt::Display` trait to render the header in a formatted table view. + #[ derive( Debug ) ] + pub struct Report + { + pub header : Vec< RowWrapper > + } + + impl fmt::Display for Report + { + /// Formats the header for display by calling the `display_header` function, + /// which uses appropriate functions from `format_tools`. + /// + /// ## Parameters: + /// - `f`: + /// A mutable reference to the `fmt::Formatter` used to write the formatted output. + /// + /// ## Returns: + /// - `fmt::Result` + fn fmt + ( + &self, + f : &mut fmt::Formatter + ) -> fmt::Result + { + display_header( &AsTable::new( &self.header ), f ) + } + } + + /// # `command` + /// + /// Processes the `header` command by retrieving the header (first row) from a specified Google Sheet + /// and displaying it in a table format in the console. + /// + /// ## Errors: + /// - Prints an error message if the spreadsheet ID extraction or header retrieval fails. + pub async fn command< S : Secret > + ( + client : &Client< '_, S >, + args : CommonArgs, + ) + { + match args + { + CommonArgs { url, tab } => + { + let spreadsheet_id = match get_spreadsheet_id_from_url( url.as_str() ) + { + Ok( id ) => id, + Err( error ) => + { + eprintln!( "Error extracting spreadsheet ID: {}", error ); + return; + } + }; + + match actions::gspread_header_get::action + ( + client, + spreadsheet_id, + tab.as_str() + ) + .await + { + Ok( header ) => + { + let header_wrapped = RowWrapper + { + max_len : header.len(), + row : header + }; + println!( "Header:\n{}", Report{ header : vec![ header_wrapped ] } ); + } + Err( error ) => eprintln!( "Error:\n{}", error ), + } + } + } + } +} + +crate::mod_interface! +{ + own use + { + command + }; +} + diff --git a/module/move/gspread/src/commands/gspread_row.rs b/module/move/gspread/src/commands/gspread_row.rs new file mode 100644 index 0000000000..eb7440c8c9 --- /dev/null +++ b/module/move/gspread/src/commands/gspread_row.rs @@ -0,0 +1,829 @@ + + +mod private +{ + use clap::Subcommand; + use serde_json::json; + use debug:: + { + Report, RowWrapper + }; + + use crate::*; + use gcore::Secret; + use gcore::client::Client; + use actions:: + { + self, + utils::get_spreadsheet_id_from_url + }; + + /// # Commands + /// + /// Subcommands for the `ROW` command. + /// + /// ## Variants: + /// + /// ### `Append` + /// Appends a new row to at the end of Google Sheet. + /// + /// **Arguments:** + /// - `url`: + /// The full URL of the Google Sheet. + /// Example: + /// `--url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}'` + /// + /// - `tab`: + /// The name of the specific sheet (tab) in the Google Spreadsheet. + /// Example: + /// `--tab 'Sheet1'` + /// + /// - `json`: + /// A string containing the key-value pairs for the new row. + /// The keys are column names (only uppercase Latin letters, e.g. `"A"`, `"B"`, etc.), + /// and the values are strings or other JSON-compatible data. + /// Depending on the shell, you may need to escape quotes. + /// Examples: + /// 1. `--json '{"A": "value1", "B": "value2"}'` + /// 2. `--json "{\\\"A\\\": \\\"value1\\\", \\\"B\\\": \\\"value2\\\"}"` + /// + /// **Example:** + /// ```bash + /// gspread row append \ + /// --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' \ + /// --tab 'tab1' \ + /// --json '{"A": "Hello", "B": "World"}' + /// ``` + /// + /// ### `Update` + /// Updates a specific row. + /// + /// **Arguments** + /// - `url`: + /// The full URL of the Google Sheet. + /// Example: + /// `--url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}'` + /// + /// - `tab`: + /// The name of the specific sheet (tab) in the Google Spreadsheet. + /// Example: + /// `--tab 'Sheet1'` + /// + /// - `json`: + /// A JSON string of column-value pairs that you want to update. + /// The keys should be valid column names (uppercase letters only), + /// and values are JSON-compatible. + /// Example: + /// `--json '{"id": 2, "A": 10, "B": "Some text"}'` + /// + /// - `select_row_by_key`: + /// A string specifying the identifier of the row to update. + /// Example: `"id"`. + /// + /// **Example:** + /// ```bash + /// gspread row update \ + /// --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' \ + /// --tab tab1 \ + /// --select-row-by-key "id" \ + /// --json '{"id": 2, "A": 1, "B": 2}' + /// ``` + /// + /// ### `UpdateCustom` + /// Updates one or more rows in a Google Sheet based on a custom key (or condition), + /// and offers control over what to do if no rows are found. + /// + /// **Arguments:** + /// - `url`: + /// The full URL of the Google Sheet. + /// Example: + /// `--url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}'` + /// + /// - `tab`: + /// The name of the specific sheet (tab) in the Google Spreadsheet. + /// Example: + /// `--tab 'Sheet1'` + /// + /// - `json`: + /// A JSON string of column-value pairs that you want to update. + /// The keys should be valid column names (uppercase letters only), + /// and values are JSON-compatible. + /// Example: + /// `--json '{"A": "10", "B": "Some text"}'` + /// + /// - `key_by`: + /// An expression specifying **which** rows to match. + /// Example: + /// or + /// `--key-by '["columnX", value_to_find]'` + /// + /// - `on_fail`: + /// What to do if **no rows are found** matching the key. + /// Possible values might be `Error`, `AppendRow`, or `Nothing` (depending on your logic). + /// + /// - `on_find`: + /// What to do if **one or multiple** rows are found. + /// Possible values might be `UpdateFirstMatchedRow`, `UpdateLastMatchedRow`, or `UpdateAllMatchedRow`. + /// + /// **Example:** + /// ```bash + /// gspread row update-custom \ + /// --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' \ + /// --tab tab1 \ + /// --json '{"A": "newVal", "B": "updatedVal"}' \ + /// --key-by '["C", 12]' \ + /// --on_fail append \ + /// --on_find all + /// ``` + /// + /// ### `Get` + /// Retrieves a specific row from a Google Sheet. + /// + /// **Arguments:** + /// - `url`: + /// The full URL of the Google Sheet. + /// Example: + /// `--url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}'` + /// + /// - `tab`: + /// The name of the specific sheet (tab) in the Google Spreadsheet. + /// Example: + /// `--tab 'Sheet1'` + /// + /// - `row-key`: + /// Row key (id). The range starts from 1. + /// Example: + /// `row-key 2` + /// + /// **Example:** + /// + /// gspread row get + /// --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' \ + /// --tab 'tab1' + /// + /// ### `GetCustom` + /// Retrieves one or more rows from a Google Sheet based on a custom key condition, + /// specifying how to handle multiple matches. + /// + /// **Arguments:** + /// - `url`: + /// The full URL of the Google Sheet. + /// Example: + /// `--url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}'` + /// + /// - `tab`: + /// The name of the specific sheet (tab) in the Google Spreadsheet. + /// Example: + /// `--tab 'Sheet1'` + /// + /// - `key_by`: + /// A JSON array of the form `[, ]`, defining which rows to match. + /// For instance, if you pass `["A", "Hello"]`, the function will look in column `A` + /// for cells whose value equals `"Hello"`. + /// Example: + /// `--key-by '["C", 12]'` + /// + /// - `on_find`: + /// Defines how to handle situations where multiple rows match the key. + /// Possible values (depending on your logic): + /// - `all`: Return **all** matched rows, + /// - `first`: Return **only the first** matched row, + /// - `last`: Return **only the last** matched row. + /// + /// **Example:** + /// ```bash + /// gspread row get-custom \ + /// --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' \ + /// --tab 'Sheet1' \ + /// --key-by '["C", 12]' \ + /// --on-find all + /// ``` + #[ derive( Debug, Subcommand ) ] + #[ command( long_about = "\n\nSubcommands for `ROW` command" ) ] + pub enum Commands + { + #[ command( name = "append", about = "Appends a new row at the end of Google Sheet.", long_about = r#" +--------------------------------------------------------------------------------------------------------------- + ROW APPEND +--------------------------------------------------------------------------------------------------------------- + ● Description: + ↓ ↓ ↓ ↓ ↓ ↓ + + Appends a new row at the end of the Google Sheet. + + The new row is generated by the `--json` argument, which should contain key-value pairs + where the key is a column ID and the value is the data to insert. Column IDs can range from `A` to `ZZZ`. + + Values are inserted according to their type: + • `{"A":1}` will parse the value as an integer. + • `{"A":true}` or `{"A":false}` will parse the value as a boolean. + • Any string should be quoted, e.g. `"true"`, `"Hello"` or `"123"`. + + If there is empty space between columns (for instance, providing values for columns C, D, and F), + then empty strings `("")` will be inserted into columns A, B, and E. + +--------------------------------------------------------------------------------------------------------------- + ● Command example: + ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ + + cargo run gspread row append \ + --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' \ + --tab 'tab1' \ + --json '{"A": "Hello", "B": "World"}' + +--------------------------------------------------------------------------------------------------------------- + ● Output: Prints a message with the amount of updated cells: + ↓ ↓ ↓ ↓ + + Row was successfully append at the end of the sheet! Amount of updated cells: 2 + +--------------------------------------------------------------------------------------------------------------- + ● Errors: + ↓ ↓ ↓ ↓ + + ◦ Error::ApiError: + ---------------------------------------------------------------- + Occurs if the Google Sheets API returns an error, + such as an invalid spreadsheet ID, insufficient permissions + or invalid sheet name. + ---------------------------------------------------------------- + + ◦ Error::ParseError: + ----------------------------------------------------------- + Occurs when serde_json can not parse an argument + ----------------------------------------------------------- + + ◦ Error::InvalidURL: + ------------------------------------------------------------------------ + Occurs when you passed url with an invalid format of your spreadsheet. + ------------------------------------------------------------------------ + +--------------------------------------------------------------------------------------------------------------- + "# ) ] + Append + { + #[ arg( long, help = "Full URL of Google Sheet.\n\ + It has to be inside of '' to avoid parse errors.\n\ + Example: 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}'" ) ] + url : String, + + #[ arg( long, help = "Sheet name.\nExample: Sheet1" ) ] + tab : String, + + #[ arg( long, help = "Value range. + The key is a column name (not a header name, but a column name, which can only contain Latin letters). + Depending on the shell, different handling might be required.\n\ + Examples:\n\ + 1. --json '{\"A\": 1, \"B\": \"Hello\"}'\n\ + 2. --json '{\\\"A\\\": 1, \\\"B\\\": \\\"Hello\\\"}'\n" ) ] + json : String + }, + + #[ command( name = "update", about = "Updates a single row.", long_about = r#" +--------------------------------------------------------------------------------------------------------------- + ROW UPDATE +--------------------------------------------------------------------------------------------------------------- + ● Description: + ↓ ↓ ↓ ↓ ↓ ↓ + + This command performs a batch update of a row specified by the `--select_row_by_key` argument + and its corresponding value in the `--json` argument. + + Essentially, you define which row to update by providing a key (e.g., "id") in `--select_row_by_key`, + and then within `--json`, you supply both the key-value pair for identifying the row (e.g., "id": 2) + and the columns to be updated with their new values (e.g., "A": 1, "B": 2). + +--------------------------------------------------------------------------------------------------------------- + ● Command example: + ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ + + cargo run gspread row update \ + --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' \ + --tab tab1 \ + --select-row-by-key "id" \ + --json '{"id": 2, "A": 1, "B": 2}' + +--------------------------------------------------------------------------------------------------------------- + ● Output: Prints a message with the amount of updated cells: + ↓ ↓ ↓ ↓ + + 2 cells were successfully updated! + +--------------------------------------------------------------------------------------------------------------- + ● Errors: + ↓ ↓ ↓ ↓ + + ◦ Error::ApiError: + ---------------------------------------------------------------- + Occurs if the Google Sheets API returns an error, + such as an invalid spreadsheet ID, insufficient permissions + or invalid sheet name. + ---------------------------------------------------------------- + + ◦ Error::InvalidURL: + ------------------------------------------------------------------------ + Occurs when you passed url with an invalid format of your spreadsheet. + ------------------------------------------------------------------------ + + ◦ Error::ParseError: + ---------------------------------------------------------------------- + Occurs when serde_json cannot parse the provided `--json` argument. + Or if you input wrong `--select_row_by_key` + ---------------------------------------------------------------------- + +--------------------------------------------------------------------------------------------------------------- + "# ) ] + Update + { + #[ arg( long, help = "Identifier of a row. Available identifiers: id (row's unique identifier).\n\ + Example: --select_row_by_key \"id\"" ) ] + select_row_by_key : String, + + #[ arg( long, help = "Value range. It must contain select_row_by_key. + The key is a column name (not a header name, but a column name, which can only contain Latin letters). + Every key and value must be a string. + Depending on the shell, different handling might be required.\n\ + Examples:\n\ + 1. --json '{\"id\": 3, \"A\": 1, \"B\": 2}'\n\ + 3. --json '{\\\"id\\\": 3, \\\"A\\\": \\\"Hello\\\", \\\"B\\\": true}'\n" ) ] + json : String, + + #[ arg( long, help = "Full URL of Google Sheet.\n\ + It has to be inside of '' to avoid parse errors.\n\ + Example: 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}'" ) ] + url : String, + + #[ arg( long, help = "Sheet name.\nExample: Sheet1" ) ] + tab : String + }, + + #[ command( name = "update-custom", about = "Updates rows according to '--key-by', '--on-find' and '--on-fail' arguments.", long_about = r#" +--------------------------------------------------------------------------------------------------------------- + ROW UPDATE-CUSTOM +--------------------------------------------------------------------------------------------------------------- + ● Description: + ↓ ↓ ↓ ↓ ↓ ↓ + + Updates range specified by `key-by`, `on-find` and `on-fail` actions. + + • `key-by` is a tuple of column ID and a value to find in that column. + For example, `--key-by ["A", 2]` means "We are looking for the value `2` in the column with ID `A`." + + • `on-find` is the action performed upon finding that value. There are 3 variants: + 1. Update only the first matched row. + 2. Update only the last matched row. + 3. Update all matched rows. + + • `on-fail` is the action performed if no match is found. There are 3 variants: + 1. Do nothing. + 2. Return an error. + 3. Append a new row (using `--json` data) at the end of the sheet. + + For example, consider the following table: + |-----------| + | A | B | C | + |-----------| + | 1 | . | . | + | 1 | . | . | + | 2 | . | . | + | 3 | . | . | + | 1 | . | . | + |-----------| + + If we run: `cargo run row update-custom ... --key-by ["A", 1] --on-find (action) --on-fail (action)`, + the program will find all rows which contain the value `1` in column `A` + and update them according to the specified `on-find` action. + + If there are no matches, the `--on-fail` action takes place. + +--------------------------------------------------------------------------------------------------------------- + ● Command example: + ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ + + cargo run gspread row update-custom \ + --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' \ + --tab 'tab1' \ + --json '{"A": "newVal", "B": "updatedVal"}' \ + --key-by '["C", 12]' \ + --on-fail error \ + --on-find first + +--------------------------------------------------------------------------------------------------------------- + ● Output: Depending on whether the value is found: + ↓ ↓ ↓ ↓ + + • If value was found: + 2 cells were successfully updated! + + • Otherwise (no match): + Row key was not found, provided action has worked. + +--------------------------------------------------------------------------------------------------------------- + ● Errors: + ↓ ↓ ↓ ↓ + + ◦ Error::ApiError: + ---------------------------------------------------------------- + Occurs if the Google Sheets API returns an error, + such as an invalid spreadsheet ID, insufficient permissions + or invalid sheet name. + ---------------------------------------------------------------- + + ◦ Error::ParseError: + ---------------------------------------------------------------- + Occurs when serde_json cannot parse the provided `--json`. + ---------------------------------------------------------------- + + ◦ Error::InvalidURL: + ---------------------------------------------------------------------- + Occurs when you pass a URL with an invalid spreadsheet format. + ---------------------------------------------------------------------- + +--------------------------------------------------------------------------------------------------------------- + "# ) ] + UpdateCustom + { + #[ arg( long, help = "Full URL of Google Sheet.\n\ + It has to be inside of '' to avoid parse errors.\n\ + Example: 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}'" ) ] + url : String, + + #[ arg( long, help = "Sheet name.\nExample: Sheet1" ) ] + tab : String, + + #[ arg( long, help = "Value range. + The key is a column name (not a header name, but a column name, which can only contain Latin letters). + Depending on the shell, different handling might be required.\n\ + Examples:\n\ + 1. --json '{\"A\": 1, \"B\": 2}'\n\ + 2. --json '{\\\"A\\\": \\\"Hello\\\", \\\"B\\\": \\\"World\\\"}'\n" ) ] + json : String, + + #[ arg( long, help = "A string with key pair view, like [\"A\", \"new_val\"], where A is a column index." ) ] + key_by : String, + + #[ arg( long, help = "Action to take if no rows are found. + Available: + - none - Does nothing. + - error - Return an error. + - append - Append a new row at the end of sheet." ) ] + on_fail : String, + + #[ arg( long, help = "Action to take if one or more rows are found. + Available: + - all - Update all matched rows, with provided values. + - first - Update first matched row with provided values. + - last - Update last matched row with provided data." ) ] + on_find : String + }, + + #[ command( name = "get", about = "Retrieves a single row.", long_about = r#" +--------------------------------------------------------------------------------------------------------------- + ROW GET +--------------------------------------------------------------------------------------------------------------- + ● Description: + ↓ ↓ ↓ ↓ ↓ ↓ + + Retrieves a specific row from a Google Sheet, identified by the `--row-key` argument. + +--------------------------------------------------------------------------------------------------------------- + ● Command example: + ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ + + gspread row get \ + --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' \ + --tab 'tab1' \ + --row-key 2 + +--------------------------------------------------------------------------------------------------------------- + ● Output: Prints the retrieved row: + ↓ ↓ ↓ ↓ + + Row: + │ 0 │ 1 │ 2 │ + ─────────────────────────── + │ 1 │ "updatedVal" │ 20 │ + +--------------------------------------------------------------------------------------------------------------- + ● Errors: + ↓ ↓ ↓ ↓ + + ◦ Error::ApiError: + ---------------------------------------------------------------- + Occurs if the Google Sheets API returns an error, + such as an invalid spreadsheet ID, insufficient permissions + or invalid sheet name. + ---------------------------------------------------------------- + + ◦ Error::ParseError: + --------------------------------------------------------- + Occurs when serde_json::Value parse error. + --------------------------------------------------------- + + ◦ Error::InvalidURL: + ---------------------------------------------------------------------- + Occurs when you passed url with invalid format of your spreadsheet. + ---------------------------------------------------------------------- + +--------------------------------------------------------------------------------------------------------------- + "# ) ] + Get + { + #[ arg( long, help = "Full URL of Google Sheet.\n\ + It has to be inside of '' to avoid parse errors.\n\ + Example: 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}'" ) ] + url : String, + + #[ arg( long, help = "Sheet name.\nExample: Sheet1" ) ] + tab : String, + + #[ arg( long, help = "A row key. Example: row_key=2" ) ] + row_key : u32, + }, + + #[ command( name = "get-custom", about = "Retrieves rows according to `--key-by` and `--on-find` arguments.", long_about = r#" +--------------------------------------------------------------------------------------------------------------- + ROW GET-CUSTOM +--------------------------------------------------------------------------------------------------------------- + ● Description: + ↓ ↓ ↓ ↓ ↓ ↓ + + Gets a range of rows specified by `key-by` and `on-find` actions. + + • `key-by` is a tuple of column ID and a value to find in that column. + For example, `--key-by ["A", 2]` means “We are looking for the value `2` in the column with ID `A`.” + + • `on-find` is the action to perform upon finding that value. There are 3 variants: + 1. Get only the first matched row. + 2. Get only the last matched row. + 3. Get all matched rows. + + For example, consider the following table: + |-----------| + | A | B | C | + |-----------| + | 1 | . | . | + | 1 | . | . | + | 2 | . | . | + | 3 | . | . | + | 1 | . | . | + |-----------| + + If we run: `cargo run row get-custom ... --key-by ["A", 1] --on-find (action)` + the program will find all rows which contain the value `1` in column `A` + and retrieve them according to the specified `on-find` action. + + If there are no matches, nothing happens. + +--------------------------------------------------------------------------------------------------------------- + ● Command example: + ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ + + cargo run gspread row get-custom \ + --url 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}' \ + --tab tab1 \ + --key-by '["A", 1]' \ + --on-find all + +--------------------------------------------------------------------------------------------------------------- + ● Output: Prints the retrieved rows: + ↓ ↓ ↓ ↓ + + Rows: + │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ + ───────────────────────────────── + │ "1" │ "" │ "" │ "" │ "" │ "a" │ + │ "1" │ "" │ "" │ "" │ "" │ "a" │ + │ "1" │ "" │ "" │ "" │ "" │ "a" │ + │ "1" │ "" │ "" │ "" │ "" │ "a" │ + │ "1" │ "" │ "" │ "" │ "" │ "a" │ + │ "1" │ "" │ "" │ "" │ "" │ "a" │ + │ "1" │ "" │ "" │ "" │ "" │ "a" │ + │ "1" │ "" │ "" │ "" │ "" │ "a" │ + +--------------------------------------------------------------------------------------------------------------- + ● Errors: + ↓ ↓ ↓ ↓ + + ◦ Error::ApiError: + ---------------------------------------------------------------- + Occurs if the Google Sheets API returns an error, + such as an invalid spreadsheet ID, insufficient permissions + or invalid sheet name. + ---------------------------------------------------------------- + + ◦ Error::ParseError: + --------------------------------------------------------- + Occurs when serde_json::Value parse error. + --------------------------------------------------------- + + ◦ Error::InvalidURL: + ---------------------------------------------------------------------- + Occurs when you pass a URL with an invalid spreadsheet format. + ---------------------------------------------------------------------- + +--------------------------------------------------------------------------------------------------------------- + "# ) ] + GetCustom + { + #[ arg( long, help = "Full URL of Google Sheet.\n\ + It has to be inside of '' to avoid parse errors.\n\ + Example: 'https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit?gid={sheet_id}#gid={sheet_id}'" ) ] + url : String, + + #[ arg( long, help = "Sheet name.\nExample: Sheet1" ) ] + tab : String, + + #[ arg( long, help = "A string with key pair view, like [\"A\", \"val\"], where A is a column index." ) ] + key_by : String, + + #[ arg( long, help = "Action to take if one or more rows are found. + Available: + - all - Retreive all matched rows. + - first - Retreive first matched row. + - last - Retreive last matched row." ) ] + on_find : String + } + } + + pub async fn command< S : Secret > + ( + client : &Client< '_, S >, + commands : Commands + ) + { + match commands + { + Commands::Append { url, tab, json } => + { + let spreadsheet_id = match get_spreadsheet_id_from_url( &url ) + { + Ok( id ) => id, + Err( error ) => + { + eprintln!( "Error extracting spreadsheet ID: {}", error ); + return; + } + }; + + match actions::gspread_row_append::action( client, spreadsheet_id, &tab, &json ).await + { + Ok( updated_cells ) => println! + ( + "Row was successfully append at the end of the sheet! Amount of updated cells: {} ", + updated_cells + ), + + Err( error ) => eprintln!( "Error\n{}", error ) + } + }, + + Commands::UpdateCustom { url, tab, json, key_by, on_fail, on_find } => + { + let spreadsheet_id = match get_spreadsheet_id_from_url( &url ) + { + Ok( id ) => id, + Err( error ) => + { + eprintln!( "Error extracting spreadsheet ID: {}", error ); + return; + } + }; + + match actions::gspread_row_update_custom::action + ( + client, + spreadsheet_id, + &tab, + &key_by, + &json, + &on_find, + &on_fail + ).await + { + Ok( val ) => + { + match val + { + 0 => println!( "Row key was not found, provided action has worked." ), + _ => println!( "{} cells were sucsessfully updated!", val ) + } + }, + Err( error ) => eprintln!( "Error\n{}", error ) + } + }, + + Commands::Update { select_row_by_key, json, url, tab } => + { + let spreadsheet_id = match get_spreadsheet_id_from_url( &url ) + { + Ok( id ) => id, + Err( error ) => + { + eprintln!( "Error extracting spreadsheet ID: {}", error ); + return; + } + }; + + match actions::gspread_row_update::action + ( + client, + &select_row_by_key, + &json, + spreadsheet_id, + &tab + ) + .await + { + Ok( val ) => println!( "{} cells were sucsessfully updated!", val ), + Err( error ) => println!( "Error:\n{}", error ) + } + }, + + Commands::Get { url, tab, row_key } => + { + let spreadsheet_id = match get_spreadsheet_id_from_url( &url ) + { + Ok( id ) => id, + Err( error ) => + { + eprintln!( "Error extracting spreadsheet ID: {}", error ); + return; + } + }; + + match actions::gspread_row_get::action + ( + client, + spreadsheet_id, + &tab, + json!( row_key ) + ) + .await + { + Ok( row ) => + { + let row_wrapped = RowWrapper + { + max_len : row.len(), + row : row + }; + + println!( "Row:\n{}", Report{ rows: vec![ row_wrapped ] } ); + }, + Err( error ) => eprintln!( "Error:\n{}", error ), + } + } + + Commands::GetCustom { url, tab, key_by, on_find } => + { + let spreadsheet_id = match get_spreadsheet_id_from_url( &url ) + { + Ok( id ) => id, + Err( error ) => + { + eprintln!( "Error extracting spreadsheet ID: {}", error ); + return; + } + }; + + match actions::gspread_row_get_custom::action + ( + client, + spreadsheet_id, + &tab, + &key_by, + &on_find + ) + .await + { + Ok( rows ) => + { + let max_len = rows + .iter() + .map( | row | row.len() ) + .max() + .unwrap_or( 0 ); + + let rows_wrapped: Vec< RowWrapper > = rows + .into_iter() + .map( | row | RowWrapper { row, max_len } ) + .collect(); + + println!( "Rows:\n{}", Report{ rows : rows_wrapped } ); + } + Err( error ) => eprintln!( "Error:\n{}", error ), + } + } + } + } +} + +crate::mod_interface! +{ + own use + { + Commands, + command + }; +} \ No newline at end of file diff --git a/module/move/gspread/src/commands/gspread_rows.rs b/module/move/gspread/src/commands/gspread_rows.rs new file mode 100644 index 0000000000..349eddf61c --- /dev/null +++ b/module/move/gspread/src/commands/gspread_rows.rs @@ -0,0 +1,82 @@ +//! +//! Command "rows" +//! + +mod private +{ + use crate::*; + use actions; + use gcore::Secret; + use gcore::client::Client; + use commands::gspread::CommonArgs; + use actions::utils::get_spreadsheet_id_from_url; + use debug:: + { + Report, + RowWrapper + }; + + /// # `command` + /// + /// Processes the `rows` command by retrieving rows from a specified Google Sheet + /// and displaying them in a table format in the console. + /// + /// ## Errors: + /// - Prints an error message if the spreadsheet ID extraction or row retrieval fails. + pub async fn command< S : Secret > + ( + client : &Client< '_, S >, + args : CommonArgs + ) + { + match args + { + CommonArgs { url, tab } => + { + let spreadsheet_id = match get_spreadsheet_id_from_url( url.as_str() ) + { + Ok( id ) => id, + Err( error ) => + { + eprintln!( "Error extracting spreadsheet ID: {}", error ); + return; + } + }; + + match actions::gspread_rows_get::action + ( + client, + spreadsheet_id, + tab.as_str() + ) + .await + { + Ok( rows ) => + { + let max_len = rows + .iter() + .map( | row | row.len() ) + .max() + .unwrap_or( 0 ); + + let rows_wrapped: Vec< RowWrapper > = rows + .into_iter() + .map( | row | RowWrapper { row, max_len } ) + .collect(); + + println!( "Rows:\n{}", Report{ rows : rows_wrapped } ); + } + Err( error ) => eprintln!( "Error:\n{}", error ), + } + } + } + } +} + +crate::mod_interface! +{ + own use + { + command + }; +} diff --git a/module/move/gspread/src/debug.rs b/module/move/gspread/src/debug.rs new file mode 100644 index 0000000000..9564d9f962 --- /dev/null +++ b/module/move/gspread/src/debug.rs @@ -0,0 +1,7 @@ +mod private {} + +crate::mod_interface! +{ + layer report; + layer row_wrapper; +} diff --git a/module/move/gspread/src/debug/report.rs b/module/move/gspread/src/debug/report.rs new file mode 100644 index 0000000000..fee6d9a853 --- /dev/null +++ b/module/move/gspread/src/debug/report.rs @@ -0,0 +1,55 @@ + + +mod private +{ + use std::fmt; + use format_tools::AsTable; + + use crate::*; + use debug::RowWrapper; + use utils::display_table::display_rows; + + /// # Report + /// + /// A structure to display retrieved rows in the console using `format_tools`. + /// + /// ## Fields: + /// - `rows`: + /// A `Vec< RowWrapper >` containing the rows to be displayed. + /// + /// ## Usage: + /// This structure is used in conjunction with the `fmt::Display` trait to render rows in a formatted table view. + pub struct Report + { + pub rows : Vec< RowWrapper > + } + + impl fmt::Display for Report + { + /// Formats the rows for display by calling the `display_rows` function, + /// which uses appropriate functions from `format_tools`. + /// + /// ## Parameters: + /// - `f`: + /// A mutable reference to the `fmt::Formatter` used to write the formatted output. + /// + /// ## Returns: + /// - `fmt::Result`: + fn fmt + ( + &self, + f : &mut fmt::Formatter + ) -> fmt::Result + { + display_rows( &AsTable::new( &self.rows ), f ) + } + } +} + +crate::mod_interface! +{ + orphan use + { + Report + }; +} \ No newline at end of file diff --git a/module/move/gspread/src/debug/row_wrapper.rs b/module/move/gspread/src/debug/row_wrapper.rs new file mode 100644 index 0000000000..66001fe7af --- /dev/null +++ b/module/move/gspread/src/debug/row_wrapper.rs @@ -0,0 +1,84 @@ +//! +//! Gspread wrapper for outputting data to console +//! +//! It is used for "header" and "rows" commands +//! + +mod private +{ + use std::borrow::Cow; + use format_tools:: + { + Fields, + IteratorTrait, + TableWithFields + }; + + /// # RowWrapper + /// + /// A structure used to display a row in the console in a table format. + /// + /// This structure is designed for displaying the results of HTTP requests in a tabular format + /// using the `format_tools` crate. It implements the `TableWithFields` and `Fields` traits + /// to enable this functionality. + /// + /// ## Fields: + /// - `row`: + /// A `Vec< JsonValue >` representing a single row of the table. This can include headers or data rows. + /// - `max_len`: + /// An `usize` specifying the maximum number of columns in the table. + /// This ensures proper alignment and display of the table in the console. + /// + /// ## Traits Implemented: + /// - `TableWithFields`: + /// - `Fields< &'_ str, Option< Cow< '_, str > > >`: + /// + /// ## Implementation Details: + /// - Missing cells in a row are filled with empty strings ( `""` ) to ensure all rows have `max_len` columns. + /// - Keys (column names) are dynamically generated based on the column index. + /// - Values are sanitized to remove unnecessary characters such as leading/trailing quotes. + #[ derive( Debug, Clone ) ] + pub struct RowWrapper + { + pub row : Vec< serde_json::Value >, + pub max_len : usize + } + + impl TableWithFields for RowWrapper {} + impl Fields< &'_ str, Option< Cow< '_, str > > > + for RowWrapper + { + type Key< 'k > = &'k str; + type Val< 'v > = Option< Cow< 'v, str > >; + fn fields( &self ) -> impl IteratorTrait< Item = ( &'_ str, Option > ) > + { + let mut dst = Vec::new(); + + for ( index, value ) in self.row.iter().enumerate() + { + let column_name = format!( "{} ", index ); + let title = Box::leak( column_name.into_boxed_str() ) as &str; + + dst.push( ( title, Some( Cow::Owned( value.to_string() ) ) ) ) + } + + // adding empty values for missing cells + for index in self.row.len()..self.max_len + { + let column_name = format!( "{}", index ); + let title = Box::leak( column_name.into_boxed_str() ) as &str; + dst.push( ( title, Some( Cow::Owned( "".to_string() ) ) ) ); + } + dst.into_iter() + } + } + +} + +crate::mod_interface! +{ + orphan use + { + RowWrapper + }; +} \ No newline at end of file diff --git a/module/move/gspread/src/gcore.rs b/module/move/gspread/src/gcore.rs new file mode 100644 index 0000000000..5d5c53dba6 --- /dev/null +++ b/module/move/gspread/src/gcore.rs @@ -0,0 +1,9 @@ + +mod private{} + +crate::mod_interface! +{ + layer client; + layer error; + layer secret; +} diff --git a/module/move/gspread/src/gcore/client.rs b/module/move/gspread/src/gcore/client.rs new file mode 100644 index 0000000000..4b568867a4 --- /dev/null +++ b/module/move/gspread/src/gcore/client.rs @@ -0,0 +1,1905 @@ +//! +//! Client to interact with Google Sheets API. +//! + +mod private +{ + use std::cell::RefCell; + use former::Former; + use serde_json::json; + use reqwest:: + { + self, + Url + }; + + use crate::*; + use gcore::Secret; + use gcore::error:: + { + Error, Result + }; + use ser:: + { + self, + Serialize, + Deserialize + }; + + /// # Auth + /// + /// Structure to keep oauth2 token. + /// + /// ## Fields: + /// - `secret`: + /// A structure which implemets [`Secret`] trait. + /// - `token`: + /// Oauth2 token in string representation. + pub struct Auth< 'a, S : Secret + 'a > + { + pub secret : &'a S, + token : RefCell< Option< String > > + } + + impl< 'a, S : Secret > Auth< 'a, S > + { + /// Just constructor. + pub fn new( secret : &'a S ) -> Self + { + Self + { + secret : secret, + token : RefCell::new( None ) + } + } + } + + /// # Gspread Client + /// + /// A struct that represents a client for interacting with Google Spreadsheets. + /// + /// This structure encapsulates the essential information and methods needed to + /// authenticate and send requests to the Google Sheets API. It uses the [`Former`] + /// procedural macro to provide builder-like functionality, allowing you to + /// configure fields (like `token` and `endpoint`) before finalizing an instance. + /// + /// ## Fields + /// + /// - `token` + /// - A `String` representing the OAuth2 access token needed to perform requests + /// against the Google Sheets API. + /// - Typically set using the `token(&Secret)` method (see below). + /// + /// - `endpoint` + /// - A `String` specifying the base API endpoint for Google Sheets. + /// - Defaults to `"https://sheets.googleapis.com/v4/spreadsheets"` if no value + /// is provided. + /// + /// ## Methods + /// + /// - **`spreadsheet` → [`SpreadSheetValuesMethod`]** + /// Returns [`SpreadSheetValuesMethod`]. + /// + /// ## Usage + /// + /// An instance of `Client` can be created via its `Former` implementation. You have to + /// set the `token` dynamically by providing a [`Secret`] to the `token( &Secret )` + /// method, which handles OAuth2 authentication under the hood. + /// You can use this client also for mock testing. In such case you need to provide `endpoint` + /// using `endpoint( url )` and there is no need to set `token`. + /// + /// Once the `Client` is fully constructed, you can use the `spreadsheet()` method + /// to access various Google Sheets API operations, such as reading or updating + /// spreadsheet cells. + #[ derive( Former ) ] + pub struct Client< 'a, S : Secret + 'a > + { + auth : Option< Auth< 'a, S > >, + #[ former( default = GOOGLE_API_URL ) ] + endpoint : &'a str, + } + + impl< S : Secret > Client< '_, S > + { + pub fn spreadsheet( &self ) -> SpreadSheetValuesMethod + { + SpreadSheetValuesMethod + { + client : self + } + } + + pub fn sheet( &self ) -> SpreadSheetMethod + { + SpreadSheetMethod + { + client : self + } + } + } + + + /// # SpreadSheetMethod + /// + /// A helper struct that provides methods for working with spreadsheet sheet in the + /// Google Sheets API. This struct is associated with a given [`Client`] instance and + /// offers specialized methods for working with sheets. + /// + /// ## Fields + /// + /// - `client` + /// - A reference to a [`Client`] object. + /// - Used to perform authenticated HTTP requests against the Google Sheets API. + /// + /// ## Methods + /// + /// - **`copy_to`**: + /// Copy a source sheet to a destination spreadsheet. + /// + /// ## Usage + /// + /// This struct is usually obtained by calling the `sheet()` method on a + /// fully-initialized [`Client`] instance: + pub struct SpreadSheetMethod< 'a, S : Secret > + { + client : &'a Client< 'a, S >, + } + + impl< S : Secret > SpreadSheetMethod< '_, S > + { + /// Build SheetCopyMethod. + pub fn copy_to< 'a > + ( + &'a self, + spreadsheet_id : &'a str, + sheet_id : &'a str, + dest : &'a str + ) -> SheetCopyMethod< 'a, S > + { + SheetCopyMethod + { + client : self.client, + _spreadsheet_id : spreadsheet_id, + _sheet_id : sheet_id, + _dest : dest + } + } + } + + + /// # SheetCopyMethod + /// + /// Represents a specialized request builder for copying a sheet. + /// + /// This struct is constructed internally by the library when calling + /// [`SpreadSheetMethod::copy_to`]. + /// + /// ## Fields + /// + /// - `client` + /// A reference to the [`Client`] used for sending authenticated requests. + /// - `_spreadsheet_id` + /// The `String` ID of the spreadsheet from which values are fetched. + /// - `_sheet_id` + /// The source sheet id. + /// - `_dest` + /// The destination spreadsheet id. + /// + /// ## Method + /// + /// - `doit()` + /// Sends the configured request to the Google Sheets API to copy a source sheet to destinayion one. + pub struct SheetCopyMethod< 'a, S : Secret > + { + client : &'a Client< 'a, S >, + _spreadsheet_id : &'a str, + _sheet_id : &'a str, + _dest : &'a str + } + + impl< S : Secret > SheetCopyMethod< '_, S > + { + /// Sends the POST request to + /// https://sheets.googleapis.com/v4/spreadsheets/{spreadsheetId}/sheets/{sheetId}:copyTo + /// + /// ## Returns: + /// - `Result< [SheetProperties] >` + /// + /// ## Errors: + /// - `ApiError` + /// - `ParseError` + pub async fn doit( &self ) -> Result< SheetProperties > + { + let endpoint = format! + ( + "{}/{}/sheets/{}:copyTo", + self.client.endpoint, + self._spreadsheet_id, + self._sheet_id + ); + + let request = SheetCopyRequest + { + dest : Some( self._dest.to_string() ) + }; + + let token = match &self.client.auth + { + Some( auth_data ) => + { + let mut token_ref = auth_data.token.borrow_mut(); + + if let Some( token ) = &*token_ref + { + token.clone() + } + else + { + let new_token = auth_data + .secret + .get_token() + .await + .map_err( | err | Error::ApiError( err.to_string() ) )?; + + *token_ref = Some( new_token.clone() ); + + new_token + } + } + None => "".to_string() + }; + + let response = reqwest::Client::new() + .post( endpoint ) + .json( &request ) + .bearer_auth( token ) + .send() + .await + .map_err( | err | Error::ApiError( err.to_string() ) )?; + + if !response.status().is_success() + { + let response_text = response + .text() + .await + .map_err( | err | Error::ParseError( err.to_string() ) )?; + + return Err( Error::ApiError( response_text ) ); + } + + let response_parsed = response.json::< SheetProperties >() + .await + .map_err( | err | Error::ParseError( err.to_string() ) )?; + + Ok( response_parsed ) + } + } + + /// # SpreadSheetValuesMethod + /// + /// A helper struct that provides methods for working with spreadsheet values in the + /// Google Sheets API. This struct is associated with a given [`Client`] instance and + /// offers specialized methods for retrieving and updating data within a spreadsheet. + /// + /// ## Fields + /// + /// - `client` + /// - A reference to a [`Client`] object. + /// - Used to perform authenticated HTTP requests against the Google Sheets API. + /// + /// ## Methods + /// + /// - **`values_get( + /// spreadsheet_id, range + /// )` → [`ValuesGetMethod`]** + /// Creates a new request object that retrieves the values within the specified `range` + /// of the spreadsheet identified by `spreadsheet_id`. + /// + /// - **`values_update( value_range, spreadsheet_id, range )` → [`ValuesUpdateMethod`]** + /// Creates a new request object that updates the values within the specified `range` + /// of the spreadsheet identified by `spreadsheet_id`, using the provided `value_range`. + /// + /// - **`values_batch_update( spreadsheet_id, req )` → [`ValuesBatchUpdateMethod`]** + /// Creates a new request object that performs multiple updates on the spreadsheet + /// identified by `spreadsheet_id`, based on the instructions defined in + /// `BatchUpdateValuesRequest`. + /// + /// - **`append( spreadsheet_id, range, value_range )` → [`ValuesAppendMethod`]** + /// Appends a new row at the end of sheet. + /// + /// - **`values_get_batch(spreadsheet_id)` -> [`ValuesBatchGetMethod`]** + /// Returns defined value ranges. + /// + /// - **`clear(spreadsheet_id, range) -> `Result<[ValuesClearResponse]>``** + /// Returns metadata of a cleared range. + /// + /// - **`clear_batch(spreadsheet_id, req) -> `Result<[BatchClearValuesResponse]>``** + /// Returns metadata of a cleared range. + /// + /// ## Usage + /// + /// This struct is usually obtained by calling the `spreadsheet()` method on a + /// fully-initialized [`Client`] instance: + pub struct SpreadSheetValuesMethod< 'a, S : Secret > + { + client : &'a Client< 'a, S >, + } + + impl< S : Secret > SpreadSheetValuesMethod< '_, S > + { + /// Creates a new request object that updates the values within the specified `range` + /// of the spreadsheet identified by `spreadsheet_id`, using the provided `value_range`. + pub fn values_get + ( + &self, + spreadsheet_id : &str, + range : &str + ) -> ValuesGetMethod< S > + { + ValuesGetMethod + { + client : self.client, + _spreadsheet_id : spreadsheet_id.to_string(), + _range : range.to_string(), + _major_dimension : Default::default(), + _value_render_option : Default::default(), + _date_time_render_option : Default::default() + } + } + + /// Returns defined value ranges. + pub fn values_get_batch< 'a > + ( + &'a self, + spreadsheet_id : &'a str, + ) -> ValuesBatchGetMethod< 'a, S > + { + ValuesBatchGetMethod + { + client : self.client, + _spreadsheet_id : spreadsheet_id, + _ranges : Default::default(), + _major_dimension : Default::default(), + _value_render_option : Default::default(), + _date_time_render_option : Default::default(), + } + } + + /// Creates a new request object that updates the values within the specified `range` + /// of the spreadsheet identified by `spreadsheet_id`, using the provided `value_range`. + pub fn values_update< 'a > + ( + &'a self, + value_range : ValueRange, + spreadsheet_id : &'a str, + range : &'a str + ) -> ValuesUpdateMethod< 'a, S > + { + ValuesUpdateMethod + { + client : self.client, + _value_range : value_range, + _spreadsheet_id : spreadsheet_id, + _range : range, + _value_input_option : ValueInputOption::default(), + _include_values_in_response : Default::default(), + _response_value_render_option : Default::default(), + _response_date_time_render_option : Default::default() + } + } + + /// Creates a new request object that performs multiple updates on the spreadsheet + /// identified by `spreadsheet_id`, based on the instructions defined in + /// `BatchUpdateValuesRequest`. + pub fn values_batch_update + ( + &self, + spreadsheet_id : &str, + req : BatchUpdateValuesRequest, + ) -> ValuesBatchUpdateMethod< S > + { + ValuesBatchUpdateMethod + { + client : self.client, + _spreadsheet_id : spreadsheet_id.to_string(), + _request : req, + } + } + + /// Appends a new row at the end of sheet. + pub fn append< 'a > + ( + &'a self, + spreadsheet_id : &'a str, + range : &'a str, + value_range : ValueRange + ) -> ValuesAppendMethod< 'a, S > + { + ValuesAppendMethod + { + client : self.client, + _value_range : value_range, + _spreadsheet_id : spreadsheet_id, + _range : range, + _value_input_option : ValueInputOption::default(), + _include_values_in_response : Default::default(), + _insert_data_option : Default::default(), + _response_date_time_render_option : Default::default(), + _response_value_render_option : Default::default() + } + } + + /// Clears a specified range. + pub fn clear< 'a > + ( + &'a self, + spreadsheet_id : &'a str, + range : &'a str + ) -> ValuesClearMethod< 'a, S > + { + ValuesClearMethod + { + client : self.client, + _spreadsheet_id : spreadsheet_id, + _range : range + } + } + + /// Clear a specified range. + pub fn clear_batch< 'a > + ( + &'a self, + spreadsheet_id : &'a str, + req : BatchClearValuesRequest + ) -> ValuesBatchClearMethod< 'a, S > + { + ValuesBatchClearMethod + { + client : self.client, + _spreadsheet_id : spreadsheet_id, + _request : req + } + } + } + + /// # ValuesGetMethod + /// + /// Represents a specialized request builder for retrieving values from a Google Spreadsheet. + /// + /// This struct is constructed internally by the library when calling + /// [`SpreadSheetValuesMethod::values_get`]. It holds references and parameters + /// required to execute a `GET` request against the Google Sheets API to fetch + /// spreadsheet data. + /// + /// ## Fields + /// + /// - `client` + /// A reference to the [`Client`] used for sending authenticated requests. + /// - `_spreadsheet_id` + /// The `String` ID of the spreadsheet from which values are fetched. + /// - `_range` + /// The `String` representing the cell range (e.g. `"A1:B10"`) to retrieve values for. + /// - `_major_dimension` + /// An optional [`Dimension`] that specifies whether the range is in rows or columns. + /// - `_value_render_option` + /// An optional [`ValueRenderOption`] that indicates how values should be + /// rendered in the response (e.g., formatted, unformatted or formula). + /// - `_date_time_render_option` + /// An optional [`DateTimeRenderOption`] specifying how date/time values are + /// rendered in the response. + /// + /// ## Method + /// + /// - `doit()` + /// Sends the configured request to the Google Sheets API to retrieve the + /// specified range of values. Returns a [`ValueRange`] on success, or an + /// [`Error`] if the API request fails. + pub struct ValuesGetMethod< 'a, S : Secret > + { + client : &'a Client< 'a, S >, + _spreadsheet_id : String, + _range : String, + _major_dimension : Option< Dimension >, + _value_render_option : Option< ValueRenderOption >, + _date_time_render_option : Option< DateTimeRenderOption > + } + + impl< S : Secret > ValuesGetMethod< '_, S > + { + /// The major dimension that results should use. For example, if the spreadsheet data is: `A1=1,B1=2,A2=3,B2=4`, then requesting `ranges=["A1:B2"],majorDimension=ROWS` returns `[[1,2],[3,4]]`, whereas requesting `ranges=["A1:B2"],majorDimension=COLUMNS` returns `[[1,3],[2,4]]`. + /// + /// Sets the *major dimension* query property to the given value. + pub fn major_dimension( mut self, new_val : Dimension ) -> Self + { + self._major_dimension = Some( new_val ); + self + } + + /// How values should be represented in the output. The default render option is ValueRenderOption.FORMATTED_VALUE. + /// + /// Sets the *value render option* query property to the given value. + pub fn value_render_option( mut self, new_val : ValueRenderOption ) -> Self + { + self._value_render_option = Some( new_val ); + self + } + + /// Executes the request configured by `ValuesGetMethod`. + /// + /// Performs an HTTP `GET` to retrieve values for the configured spreadsheet range. + /// On success, returns the [`ValueRange`] containing the fetched data. + /// If the request fails or the response cannot be parsed, returns an [`Error`]. + pub async fn doit( &self ) -> Result< ValueRange > + { + let endpoint = format! + ( + "{}/{}/values/{}", + self.client.endpoint, + self._spreadsheet_id, + self._range + ); + + let query = GetValuesRequest + { + major_dimension : self._major_dimension, + value_render_option : self._value_render_option, + date_time_render_option : self._date_time_render_option + }; + + let token = match &self.client.auth + { + Some( auth_data ) => + { + let mut token_ref = auth_data.token.borrow_mut(); + + if let Some( token ) = &*token_ref + { + token.clone() + } + else + { + let new_token = auth_data + .secret + .get_token() + .await + .map_err( | err | Error::ApiError( err.to_string() ) )?; + + *token_ref = Some( new_token.clone() ); + + new_token + } + } + None => "".to_string() + }; + + let response = reqwest::Client::new() + .get( endpoint ) + .query( &query ) + .bearer_auth( token ) + .send() + .await + .map_err( | err | Error::ApiError( err.to_string() ) )?; + + if !response.status().is_success() + { + let response_text = response + .text() + .await + .map_err( | err | Error::ParseError( err.to_string() ) )?; + + return Err( Error::ApiError( response_text ) ) + } + + let value_range = response.json::< ValueRange >() + .await + .map_err( | err | Error::ParseError( err.to_string() ) )?; + + Ok( value_range ) + } + } + + + /// A builder for retrieving values from multiple ranges in a spreadsheet using the Google Sheets API. + /// + /// This struct allows you to specify: + /// + /// - **Spreadsheet ID** (the unique identifier of the spreadsheet), + /// - **Ranges** in [A1 notation](https://developers.google.com/sheets/api/guides/concepts#a1_notation), + /// + /// Then, by calling [`ValuesBatchGetMethod::doit`], you send the `GET` request to retrieve all those ranges in a single batch. + /// On success, it returns a [`BatchGetValuesResponse`] with the data. On error, it returns an [`Error`]. + pub struct ValuesBatchGetMethod< 'a, S : Secret > + { + client : &'a Client< 'a, S >, + _spreadsheet_id : &'a str, + _ranges : Vec< String >, + _major_dimension : Option< Dimension >, + _value_render_option : Option< ValueRenderOption >, + _date_time_render_option : Option< DateTimeRenderOption > + } + + impl< 'a, S : Secret > ValuesBatchGetMethod< 'a, S > + { + /// Executes the request configured by `ValuesBatchGetMethod`. + /// + /// Performs an HTTP `GET` to retrieve values for the configured spreadsheet range. + /// On success, returns the [`BatchGetValuesResponse`] containing the fetched data. + /// If the request fails or the response cannot be parsed, returns an [`Error`]. + pub async fn doit( &self ) -> Result< BatchGetValuesResponse > + { + let mut url = format! + ( + "{}/{}/values:batchGet", + self.client.endpoint, + self._spreadsheet_id + ); + + let mut parsed_url = Url::parse( &url ) + .map_err( | err | Error::ParseError( err.to_string() ) )?; + + { + let mut pairs = parsed_url.query_pairs_mut(); + + for r in &self._ranges + { + pairs.append_pair( "ranges", r ); + } + } + + let token = match &self.client.auth + { + Some( auth_data ) => + { + let mut token_ref = auth_data.token.borrow_mut(); + + if let Some( token ) = &*token_ref + { + token.clone() + } + else + { + let new_token = auth_data + .secret + .get_token() + .await + .map_err( | err | Error::ApiError( err.to_string() ) )?; + + *token_ref = Some( new_token.clone() ); + + new_token + } + } + None => "".to_string() + }; + + url = parsed_url.into(); + + let response = reqwest::Client::new() + .get( url ) + .bearer_auth( token ) + .send() + .await + .map_err( | err | Error::ApiError( err.to_string() ) )?; + + if !response.status().is_success() + { + let response_text = response + .text() + .await + .map_err( | err | Error::ParseError( err.to_string() ) )?; + + return Err( Error::ApiError( format!( "{}", response_text ) ) ) + } + + let parsed_response = response.json::< BatchGetValuesResponse >() + .await + .map_err( | err | Error::ApiError( err.to_string() ) )?; + + Ok( parsed_response ) + } + + /// Set ranges to retrive in A1 notation format. + pub fn ranges( mut self, new_val : Vec< String > ) -> ValuesBatchGetMethod< 'a, S > + { + self._ranges = new_val; + self + } + } + + /// # ValuesUpdateMethod + /// + /// Represents a specialized request builder for updating values in a Google Spreadsheet. + /// + /// This struct is constructed internally by the library when calling + /// [`SpreadSheetValuesMethod::values_update`]. It holds references and parameters + /// required to execute a `PUT` request against the Google Sheets API to modify + /// spreadsheet data. + /// + /// ## Fields + /// + /// - `client` + /// A reference to the [`Client`] used for sending authenticated requests. + /// - `_value_range` + /// A [`ValueRange`] describing the new data to be written to the spreadsheet. + /// - `_spreadsheet_id` + /// A `&str` denoting the spreadsheet’s identifier. + /// - `_range` + /// A `&str` specifying the cell range (e.g. `"A1:B10"`) where the values should be updated. + /// - `_value_input_option` + /// A [`ValueInputOption`] that indicates how the input data should be parsed + /// (e.g., as user-entered or raw data). + /// - `_include_values_in_response` + /// An optional `bool` indicating whether the updated values should be + /// returned in the response. + /// - `_response_value_render_option` + /// An optional [`ValueRenderOption`] that specifies how updated values should + /// be rendered in the response. + /// - `_response_date_time_render_option` + /// An optional [`DateTimeRenderOption`] that specifies how date/time values + /// should be rendered in the response if `_include_values_in_response` is `true`. + /// + /// ## Method + /// + /// - `doit()` + /// Sends the configured request to the Google Sheets API to update the specified + /// range with new data. Returns an [`UpdateValuesResponse`] on success, or an + /// [`Error`] if the API request fails. + pub struct ValuesUpdateMethod< 'a, S : Secret > + { + client : &'a Client< 'a, S >, + _value_range : ValueRange, + _spreadsheet_id : &'a str, + _range : &'a str, + _value_input_option : ValueInputOption, + _include_values_in_response : Option< bool >, + _response_value_render_option : Option< ValueRenderOption >, + _response_date_time_render_option : Option< DateTimeRenderOption > + } + + impl< S : Secret > ValuesUpdateMethod< '_, S > + { + /// Executes the request configured by `ValuesUpdateMethod`. + /// + /// Performs an HTTP `PUT` to update spreadsheet values within the specified range. + /// On success, returns an [`UpdateValuesResponse`] describing the result of the + /// update operation. If the request fails or parsing the response is unsuccessful, + /// an [`Error`] is returned. + pub async fn doit( &self ) -> Result< UpdateValuesResponse > + { + let endpoint = format! + ( + "{}/{}/values/{}", + self.client.endpoint, + self._spreadsheet_id, + self._range + ); + + let query = UpdateValuesRequest + { + value_input_option : self._value_input_option, + include_values_in_response : self._include_values_in_response, + response_value_render_option : self._response_value_render_option, + response_date_time_render_option : self._response_date_time_render_option + }; + + let token = match &self.client.auth + { + Some( auth_data ) => + { + let mut token_ref = auth_data.token.borrow_mut(); + + if let Some( token ) = &*token_ref + { + token.clone() + } + else + { + let new_token = auth_data + .secret + .get_token() + .await + .map_err( | err | Error::ApiError( err.to_string() ) )?; + + *token_ref = Some( new_token.clone() ); + + new_token + } + } + None => "".to_string() + }; + + let response = reqwest::Client::new() + .put( endpoint ) + .query( &query ) + .json( &self._value_range ) + .bearer_auth( token ) + .send() + .await + .map_err( | err | Error::ApiError( err.to_string() ) )?; + + if !response.status().is_success() + { + let response_text = response + .text() + .await + .map_err( | err | Error::ParseError( err.to_string() ) )?; + + return Err( Error::ApiError( response_text ) ); + } + + let parsed_response = response.json::< UpdateValuesResponse >() + .await + .map_err( | err | Error::ParseError( err.to_string() ) )?; + + Ok( parsed_response ) + } + + } + + /// # ValuesBatchUpdateMethod + /// + /// Represents a specialized request builder for performing batch updates + /// of values in a Google Spreadsheet. + /// + /// This struct is constructed internally by the library when calling + /// [`SpreadSheetValuesMethod::values_batch_update`]. It holds the information + /// required to execute a `POST` request to apply multiple updates in a single + /// call to the Google Sheets API. + /// + /// ## Fields + /// + /// - `client` + /// A reference to the [`Client`] used for sending authenticated requests. + /// - `_spreadsheet_id` + /// The `String` ID of the spreadsheet to be updated. + /// - `_request` + /// A [`BatchUpdateValuesRequest`] containing multiple update instructions. + /// + /// ## Method + /// + /// - `doit()` + /// Sends the configured request to the Google Sheets API to perform multiple + /// updates on the target spreadsheet. Returns a [`BatchUpdateValuesResponse`] + /// on success, or an [`Error`] if the API request fails. + pub struct ValuesBatchUpdateMethod< 'a, S : Secret > + { + pub client : &'a Client< 'a, S >, + pub _spreadsheet_id : String, + pub _request : BatchUpdateValuesRequest + } + + impl< S : Secret > ValuesBatchUpdateMethod< '_, S > + { + /// Executes the request configured by `ValuesBatchUpdateMethod`. + /// + /// Performs an HTTP `POST` to apply a batch of updates to the specified + /// spreadsheet. On success, returns a [`BatchUpdateValuesResponse`] containing + /// details about the applied updates. If the request fails or the response + /// cannot be parsed, an [`Error`] is returned. + pub async fn doit( &self ) -> Result< BatchUpdateValuesResponse > + { + let endpoint = format! + ( + "{}/{}/values:batchUpdate", + self.client.endpoint, + self._spreadsheet_id + ); + + let token = match &self.client.auth + { + Some( auth_data ) => + { + let mut token_ref = auth_data.token.borrow_mut(); + + if let Some( token ) = &*token_ref + { + token.clone() + } + else + { + let new_token = auth_data + .secret + .get_token() + .await + .map_err( | err | Error::ApiError( err.to_string() ) )?; + + *token_ref = Some( new_token.clone() ); + + new_token + } + } + None => "".to_string() + }; + + let response = reqwest::Client::new() + .post( endpoint ) + .json( &self._request ) + .bearer_auth( token ) + .send() + .await + .map_err( | err | Error::ApiError( err.to_string() ) )?; + + if !response.status().is_success() + { + let response_text = response + .text() + .await + .map_err( | err | Error::ParseError( err.to_string() ) )?; + + return Err( Error::ApiError( response_text ) ); + } + + let parsed_response = response.json::< BatchUpdateValuesResponse >() + .await + .map_err( | err | Error::ParseError( err.to_string() ) )?; + + Ok( parsed_response ) + } + } + + /// A builder for appending values to a sheet. + /// + /// This struct lets you configure: + /// - The spreadsheet ID (`_spreadsheet_id`), + /// - The input data (`_value_range`), + /// + /// By calling [`ValuesAppendMethod::doit`], you perform an HTTP `POST` request + /// to `https://sheets.googleapis.com/v4/spreadsheets/{spreadsheetId}/values/{range}:append`. + /// + /// On success, it returns a [`ValuesAppendResponse`] containing metadata about the append result. + /// On error, returns an [`Error`]. + pub struct ValuesAppendMethod< 'a, S : Secret > + { + client : &'a Client< 'a, S >, + _value_range : ValueRange, + _spreadsheet_id : &'a str, + _range : &'a str, + _value_input_option : ValueInputOption, + _insert_data_option : Option< InsertDataOption >, + _include_values_in_response : bool, + _response_value_render_option : Option< ValueRenderOption >, + _response_date_time_render_option : Option< DateTimeRenderOption > + } + + impl< S : Secret > ValuesAppendMethod< '_, S > + { + /// Executes the configured append request. + /// + /// Sends a `POST` request to: + /// `https://sheets.googleapis.com/v4/spreadsheets/{spreadsheet_id}/values/{range}:append?valueInputOption=...&...` + /// + /// - Query parameters are built from `ValuesAppendRequest` (e.g. `valueInputOption`, `insertDataOption`, etc.). + /// - The JSON body contains a [`ValueRange`] with the actual data to append. + /// + /// Returns [`ValuesAppendResponse`] on success, or an [`Error`] if the request fails + /// or if response parsing fails. + /// + /// # Errors + /// - [`Error::ApiError`] if the HTTP status is not successful or the API returns an error. + /// - [`Error::ParseError`] if the body cannot be deserialized into [`ValuesAppendResponse`]. + pub async fn doit( &self ) -> Result< ValuesAppendResponse > + { + let endpoint = format! + ( + "{}/{}/values/{}:append", + self.client.endpoint, + self._spreadsheet_id, + self._range + ); + + let query = ValuesAppendRequest + { + value_input_option : self._value_input_option, + insert_data_option : self._insert_data_option, + include_values_in_response : self._include_values_in_response, + response_value_render_option : self._response_value_render_option, + response_date_time_render_option : self._response_date_time_render_option + }; + + let token = match &self.client.auth + { + Some( auth_data ) => + { + let mut token_ref = auth_data.token.borrow_mut(); + + if let Some( token ) = &*token_ref + { + token.clone() + } + else + { + let new_token = auth_data + .secret + .get_token() + .await + .map_err( | err | Error::ApiError( err.to_string() ) )?; + + *token_ref = Some( new_token.clone() ); + + new_token + } + } + None => "".to_string() + }; + + let response = reqwest::Client::new() + .post( endpoint ) + .query( &query ) + .json( &self._value_range ) + .bearer_auth( token ) + .send() + .await + .map_err( | err | Error::ApiError( err.to_string() ) )?; + + if !response.status().is_success() + { + let response_text = response + .text() + .await + .map_err( | err | Error::ParseError( err.to_string() ) )?; + + return Err( Error::ApiError( response_text ) ); + } + + let parsed_response = response.json::< ValuesAppendResponse >() + .await + .map_err( | err | Error::ParseError( err.to_string() ) )?; + + Ok( parsed_response ) + } + + /// #insert_data_option + /// + /// Set up new insertDataOption to request. + pub fn insert_data_option( mut self, new_val : InsertDataOption ) -> Self + { + self._insert_data_option = Some( new_val ); + self + } + } + + /// A builder for clearing values from a sheet. + /// + /// This struct lets you configure: + /// + /// By calling [`ValuesClearMethod::doit`], you perform an HTTP `POST` request + /// to `https://sheets.googleapis.com/v4/spreadsheets/{spreadsheetId}/values/{range}:clear`. + /// + /// On success, it returns a [`ValuesClearResponse`] containing metadata about the clear result. + /// On error, returns an [`Error`]. + pub struct ValuesClearMethod< 'a, S : Secret > + { + client : &'a Client< 'a, S >, + _spreadsheet_id : &'a str, + _range : &'a str + } + + impl< S : Secret > ValuesClearMethod< '_, S > + { + /// Executes the configured clear request. + /// + /// Sends a `POST` request to: + /// `https://sheets.googleapis.com/v4/spreadsheets/{spreadsheetId}/values/{range}:clear` + /// + /// Returns [`ValuesClearResponse`] on success, or an [`Error`] if the request fails + /// or if response parsing fails. + /// + /// # Errors + /// - [`Error::ApiError`] if the HTTP status is not successful or the API returns an error. + /// - [`Error::ParseError`] if the body cannot be deserialized into [`ValuesClearResponse`]. + pub async fn doit( &self ) -> Result< ValuesClearResponse > + { + let endpoint = format! + ( + "{}/{}/values/{}:clear", + self.client.endpoint, + self._spreadsheet_id, + self._range + ); + + let token = match &self.client.auth + { + Some( auth_data ) => + { + let mut token_ref = auth_data.token.borrow_mut(); + + if let Some( token ) = &*token_ref + { + token.clone() + } + else + { + let new_token = auth_data + .secret + .get_token() + .await + .map_err( | err | Error::ApiError( err.to_string() ) )?; + + *token_ref = Some( new_token.clone() ); + + new_token + } + } + None => "".to_string() + }; + + let response = reqwest::Client::new() + .post( endpoint ) + .json( &json!( {} ) ) + .bearer_auth( token ) + .send() + .await + .map_err( | err | Error::ApiError( err.to_string() ) )?; + + if !response.status().is_success() + { + let response_text = response + .text() + .await + .map_err( | err | Error::ParseError( err.to_string() ) )?; + + return Err( Error::ApiError( response_text ) ) + } + + let response_parsed = response.json::< ValuesClearResponse >() + .await + .map_err( | err | Error::ParseError( err.to_string() ) )?; + + Ok( response_parsed ) + } + } + + /// A builder for clearing values from a sheet. + /// + /// This struct lets you configure: + /// + /// By calling [`ValuesBatchClearMethod::doit`], you perform an HTTP `POST` request + /// to `https://sheets.googleapis.com/v4/spreadsheets/{spreadsheetId}/values:batchClear`. + /// + /// On success, it returns a [`BatchClearValuesResponse`] containing metadata about the clear result. + /// On error, returns an [`Error`]. + pub struct ValuesBatchClearMethod< 'a, S : Secret > + { + client : &'a Client< 'a, S >, + _spreadsheet_id : &'a str, + _request : BatchClearValuesRequest + } + + impl< S : Secret > ValuesBatchClearMethod< '_, S > + { + /// Executes the configured clear request. + /// + /// Sends a `POST` request to: + /// `https://sheets.googleapis.com/v4/spreadsheets/{spreadsheetId}/values:batchClear` + /// + /// Returns [`BatchClearValuesResponse`] on success, or an [`Error`] if the request fails + /// or if response parsing fails. + /// + /// # Errors + /// - [`Error::ApiError`] if the HTTP status is not successful or the API returns an error. + /// - [`Error::ParseError`] if the body cannot be deserialized into [`BatchClearValuesResponse`]. + pub async fn doit( &self ) -> Result< BatchClearValuesResponse > + { + let endpoint = format! + ( + "{}/{}/values:batchClear", + self.client.endpoint, + self._spreadsheet_id + ); + + let token = match &self.client.auth + { + Some( auth_data ) => + { + let mut token_ref = auth_data.token.borrow_mut(); + + if let Some( token ) = &*token_ref + { + token.clone() + } + else + { + let new_token = auth_data + .secret + .get_token() + .await + .map_err( | err | Error::ApiError( err.to_string() ) )?; + + *token_ref = Some( new_token.clone() ); + + new_token + } + } + None => "".to_string() + }; + + let response = reqwest::Client::new() + .post( endpoint ) + .json( &self._request ) + .bearer_auth( token ) + .send() + .await + .map_err( | err | Error::ApiError( err.to_string() ) )?; + + if !response.status().is_success() + { + let response_text = response + .text() + .await + .map_err( | err | Error::ParseError( err.to_string() ) )?; + + return Err( Error::ApiError( response_text ) ); + } + + let response_parsed = response.json::< BatchClearValuesResponse >() + .await + .map_err( | err | Error::ParseError( err.to_string() ) )?; + + Ok( response_parsed ) + } + } + + #[ derive( Debug, Serialize, Deserialize ) ] + pub struct SheetCopyRequest + { + #[ serde( rename = "destinationSpreadsheetId" ) ] + pub dest : Option< String > + } + + /// The kind of sheet. + #[ derive( Debug, Serialize, Deserialize) ] + pub enum SheetType + { + /// The sheet is a grid. + #[ serde( rename = "GRID" ) ] + Grid, + + /// The sheet has no grid and instead has an object like a chart or image. + #[ serde( rename = "OBJECT" ) ] + Object, + + /// The sheet connects with an external DataSource and shows the preview of data. + #[ serde( rename = "DATA_SOURCE" ) ] + DataSource + } + + /// Properties of a grid. + #[ derive( Debug, Serialize, Deserialize ) ] + pub struct GridProperties + { + /// The number of rows in the grid. + #[ serde( rename = "rowCount" ) ] + row_count : Option< u64 >, + + /// The number of columns in the grid. + #[ serde( rename = "columnCount" ) ] + column_count : Option< u32 >, + + /// The number of rows that are frozen in the grid. + #[ serde( rename = "frozenRowCount" ) ] + frozen_row_count : Option< u64 >, + + /// The number of columns that are frozen in the grid. + #[ serde( rename = "frozenColumnCount" ) ] + frozen_column_count : Option< u64 >, + + /// True if the grid isn't showing gridlines in the UI. + #[ serde( rename = "hideGridlines" ) ] + hide_grid_lines : Option< bool >, + + /// True if the row grouping control toggle is shown after the group. + #[ serde( rename = "rowGroupControlAfter" ) ] + row_group_control_after : Option< bool >, + + /// True if the column grouping control toggle is shown after the group. + #[ serde( rename = "columnGroupControlAfter" ) ] + column_group_control_after : Option< bool > + } + + /// Represents a color in the RGBA color space. + /// More information here [color google docs](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/other#Color) + #[ derive( Debug, Serialize, Deserialize ) ] + pub struct Color + { + /// The amount of red in the color as a value in the interval [0, 1]. + pub red : Option< f32 >, + + /// The amount of green in the color as a value in the interval [0, 1]. + pub green : Option< f32 >, + + /// The amount of blue in the color as a value in the interval [0, 1]. + pub blue : Option< f32 >, + + /// The fraction of this color that should be applied to the pixel. + pub alpha : Option< f32 > + } + + /// Theme color types. + #[ derive( Debug, Serialize, Deserialize ) ] + pub enum ThemeColorType + { + /// Represents the primary text color + #[ serde( rename = "TEXT" ) ] + Text, + + /// Represents the primary background color + #[ serde( rename = "BACKGROUND" ) ] + Background, + + /// Represents the first accent color + #[ serde( rename = "ACCENT1" ) ] + Accent1, + + /// Represents the second accent color + #[ serde( rename = "ACCENT2" ) ] + Accent2, + + #[ serde( rename = "ACCENT3" ) ] + /// Represents the third accent color + Accent3, + + #[ serde( rename = "ACCENT4" ) ] + /// Represents the fourth accent color + Accent4, + + #[ serde( rename = "ACCENT5" ) ] + /// Represents the fifth accent color + Accent5, + + #[ serde( rename = "ACCENT6" ) ] + /// Represents the sixth accent color + Accent6, + + /// Represents the color to use for hyperlinks + #[ serde( rename = "LINK" ) ] + Link + } + + /// A color value. + #[ derive( Debug, Serialize, Deserialize ) ] + pub enum ColorStyle + { + #[ serde( rename = "rgbColor" ) ] + RgbColor( Color ), + + #[ serde( rename = "themeColor" ) ] + ThemeColor( ThemeColorType ) + } + + /// An unique identifier that references a data source column. + #[ derive( Debug, Serialize, Deserialize ) ] + pub struct DataSourceColumnReference + { + /// The display name of the column. It should be unique within a data source. + pub name : Option< String > + } + + /// A column in a data source. + #[ derive( Debug, Serialize, Deserialize ) ] + pub struct DataSourceColumn + { + /// The column reference. + pub reference : Option< DataSourceColumnReference >, + + /// The formula of the calculated column. + pub formula : Option< String > + } + + /// An enumeration of data execution states. + #[ derive( Debug, Serialize, Deserialize ) ] + pub enum DataExecutionState + { + /// The data execution has not started. + #[ serde( rename = "NOT_STARTED" ) ] + NotStarted, + + /// The data execution has started and is running. + #[ serde( rename = "RUNNING" ) ] + Running, + + /// The data execution is currently being cancelled. + #[ serde( rename = "CANCELLING" ) ] + Cancelling, + + /// The data execution has completed successfully. + #[ serde( rename = "SUCCEEDED" ) ] + Succeeded, + + /// The data execution has completed with errors. + #[ serde( rename = "FAILED" ) ] + Failed + } + + /// An enumeration of data execution error code. + #[ derive( Debug, Serialize, Deserialize ) ] + pub enum DataExecutionErrorCode + { + /// The data execution timed out. + #[ serde( rename = "TIMED_OUT" ) ] + TimedOut, + + /// The data execution returns more rows than the limit. + #[ serde( rename = "TOO_MANY_ROWS" ) ] + TooManyRows, + + /// The data execution returns more columns than the limit. + #[ serde( rename = "TOO_MANY_COLUMNS" ) ] + TooManyColumns, + + /// The data execution returns more cells than the limit. + #[ serde( rename = "TOO_MANY_CELLS" ) ] + TooManyCells, + + /// Error is received from the backend data execution engine (e.g. BigQuery) + #[ serde( rename = "ENGINE" ) ] + Engine, + + /// One or some of the provided data source parameters are invalid. + #[ serde( rename = "PARAMETER_INVALID" ) ] + ParameterInvalid, + + /// The data execution returns an unsupported data type. + #[ serde( rename = "UNSUPPORTED_DATA_TYPE" ) ] + UnsupportedDataType, + + /// The data execution returns duplicate column names or aliases. + #[ serde( rename = "DUPLICATE_COLUMN_NAMES" ) ] + DuplicateColumnNames, + + /// The data execution is interrupted. Please refresh later. + #[ serde( rename = "INTERRUPTED" ) ] + Interrupted, + + /// The data execution is currently in progress, can not be refreshed until it completes. + #[ serde( rename = "CONCURRENT_QUERY" ) ] + ConcurrentQuery, + + /// Other errors. + #[ serde( rename = "OTHER" ) ] + Other, + + /// The data execution returns values that exceed the maximum characters allowed in a single cell. + #[ serde( rename = "TOO_MANY_CHARS_PER_CELL" ) ] + TooManyCharsPerCell, + + /// The database referenced by the data source is not found. + #[ serde( rename = "DATA_NOT_FOUND" ) ] + DataNotFound, + + /// The user does not have access to the database referenced by the data source. + #[ serde( rename = "PERMISSION_DENIED" ) ] + PermissionDenied, + + /// The data execution returns columns with missing aliases. + #[ serde( rename = "MISSING_COLUMN_ALIAS" ) ] + MissingColumnAlias, + + /// The data source object does not exist. + #[ serde( rename = "OBJECT_NOT_FOUND" ) ] + ObjectNotFound, + + /// The data source object is currently in error state. + #[ serde( rename = "OBJECT_IN_ERROR_STATE" ) ] + ObjectInErrorState, + + /// The data source object specification is invalid. + #[ serde( rename = "OBJECT_SPEC_INVALID" ) ] + ObjectSprecInvalid, + + /// The data execution has been cancelled. + #[ serde( rename = "DATA_EXECUTION_CANCELLED" ) ] + DataExecutionCancelled + } + + /// The data execution status. + /// More information [here](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/other#DataExecutionStatus) + #[ derive( Debug, Serialize, Deserialize ) ] + pub struct DataExecutinStatus + { + /// The state of the data execution. + pub state : Option< DataExecutionState >, + + /// The error code + #[ serde( rename = "errorCode" ) ] + pub error_code : Option< DataExecutionErrorCode >, + + /// The error message, which may be empty. + #[ serde( rename = "errorMessage" ) ] + pub error_message : Option< String >, + + /// lastRefreshTime + #[ serde( rename = "lastRefreshTime" ) ] + pub last_refresh_time : Option< String > + } + + /// Additional properties of a [DATA_SOURCE](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/sheets#SheetType) sheet. + #[ derive( Debug, Serialize, Deserialize ) ] + pub struct DataSourceSheetProperties + { + /// ID of the [DataSource](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets#DataSource) the sheet is connected to. + #[ serde( rename = "dataSourceId" ) ] + pub data_source_id : Option< String >, + + /// The columns displayed on the sheet, corresponding to the values in [RowData](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/sheets#RowData). + pub columns : Option< Vec< DataSourceColumn > >, + + /// The data execution status. + #[ serde( rename = "dataExecutionStatus" ) ] + pub data_executin_status : Option< DataExecutinStatus > + } + + /// Properties of a sheet. + #[ derive( Debug, Serialize, Deserialize ) ] + pub struct SheetProperties + { + /// The ID of the sheet. Must be non-negative. This field cannot be changed once set. + #[ serde( rename = "sheetId" ) ] + pub sheet_id : Option< u64 >, + + /// The name of the sheet. + pub title : Option< String >, + + /// The index of the sheet within the spreadsheet. When adding or updating sheet properties, if this field is excluded then + /// the sheet is added or moved to the end of the sheet list. When updating sheet indices or inserting sheets, movement + /// is considered in "before the move" indexes. For example, if there were three sheets (S1, S2, S3) in order to move S1 + /// ahead of S2 the index would have to be set to 2. A sheet index update request is ignored if the requested index is + /// identical to the sheets current index or if the requested new index is equal to the current sheet index + 1. + pub index : Option< u64 >, + + #[ serde( rename = "sheetType" ) ] + /// The type of sheet. Defaults to GRID. This field cannot be changed once set. + pub sheet_type : Option< SheetType >, + + /// Additional properties of the sheet if this sheet is a grid. (If the sheet is an object sheet, containing a chart or image, then this field will be absent.) When writing it is an error to set any grid properties on non-grid sheets. + #[ serde( rename = "gridProperties" ) ] + pub grid_properties : Option< GridProperties >, + + /// True if the sheet is hidden in the UI, false if it's visible. + pub hidden : Option< bool >, + + /// The color of the tab in the UI. Deprecated: Use tabColorStyle. + #[ serde( rename = "tabColor" ) ] + pub tab_color : Option< Color >, + + /// The color of the tab in the UI. If tabColor is also set, this field takes precedence. + #[ serde( rename = "tabColorStyle" ) ] + pub tab_color_style : Option< ColorStyle >, + + /// True if the sheet is an RTL sheet instead of an LTR sheet. + #[ serde( rename = "rightToLeft" ) ] + pub right_to_left : Option< bool >, + + /// Output only. If present, the field contains DATA_SOURCE sheet specific properties. + #[ serde( rename = "dataSourceSheetProperties" ) ] + pub data_source_sheet_properties : Option< DataSourceSheetProperties > + } + + + #[ derive( Debug, Serialize ) ] + pub struct GetValuesRequest + { + #[ serde( rename = "majorDimension" ) ] + major_dimension : Option< Dimension >, + + #[ serde( rename = "valueRenderOption" ) ] + value_render_option : Option< ValueRenderOption >, + + #[ serde( rename = "dateTimeRenderOption" ) ] + date_time_render_option : Option< DateTimeRenderOption > + } + + #[ derive( Debug, Serialize ) ] + pub struct BatchGetValuesRequest + { + ranges : Vec< String >, + + #[ serde( rename = "majorDimension" ) ] + major_dimension : Option< Dimension >, + + #[ serde( rename = "valueRenderOption" ) ] + value_render_option : Option< ValueRenderOption >, + + #[ serde( rename = "dateTimeRenderOption" ) ] + date_time_render_option : Option< DateTimeRenderOption > + } + + #[ derive( Debug, Serialize ) ] + pub struct UpdateValuesRequest + { + #[ serde( rename = "valueInputOption" )] + value_input_option : ValueInputOption, + + #[ serde( rename = "includeValuesInResponse" ) ] + include_values_in_response : Option< bool >, + + #[ serde( rename = "responseValueRenderOption" ) ] + response_value_render_option : Option< ValueRenderOption >, + + #[ serde( rename = "responseDateTimeRenderOption" ) ] + response_date_time_render_option : Option< DateTimeRenderOption > + } + + /// The request body. + #[ derive( Debug, Serialize, Clone ) ] + pub struct BatchUpdateValuesRequest + { + /// The new values to apply to the spreadsheet. + pub data : Vec< ValueRange >, + + #[ serde( rename = "valueInputOption" ) ] + /// How the input data should be interpreted. + pub value_input_option : ValueInputOption, + + /// Determines if the update response should include the values of the cells that were updated. By default, responses do not include the updated values. The updatedData field within each of the BatchUpdateValuesResponse.responses contains the updated values. If the range to write was larger than the range actually written, the response includes all values in the requested range (excluding trailing empty rows and columns). + #[ serde( rename = "includeValuesInResponse" ) ] + pub include_values_in_response : Option< bool >, + + /// Determines how values in the response should be rendered. The default render option is FORMATTED_VALUE. + #[ serde( rename = "responseValueRenderOption" ) ] + pub response_value_render_option : Option< ValueRenderOption >, + + /// Determines how dates, times, and durations in the response should be rendered. This is ignored if responseValueRenderOption is FORMATTED_VALUE. The default dateTime render option is SERIAL_NUMBER. + #[ serde( rename = "responseDateTimeRenderOption" ) ] + pub response_date_time_render_option : Option< DateTimeRenderOption >, + } + + #[ derive( Debug, Serialize ) ] + pub struct ValuesAppendRequest + { + #[ serde( rename = "valueInputOption" ) ] + pub value_input_option : ValueInputOption, + + #[ serde( rename = "insertDataOption" ) ] + pub insert_data_option : Option< InsertDataOption >, + + #[ serde( rename = "includeValuesInResponse" ) ] + pub include_values_in_response : bool, + + #[ serde( rename = "responseValueRenderOption" ) ] + pub response_value_render_option : Option< ValueRenderOption >, + + #[ serde( rename = "responseDateTimeRenderOption" ) ] + pub response_date_time_render_option : Option< DateTimeRenderOption > + } + + /// The request body. + #[ derive( Debug, Serialize, Deserialize ) ] + pub struct BatchClearValuesRequest + { + /// The ranges to clear, in A1 notation or R1C1 notation. + pub ranges : Vec< String > + } + + /// Response from [`values.batchGet`](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchGet). + #[ derive( Debug, Serialize, Deserialize ) ] + pub struct BatchGetValuesResponse + { + /// The ID of the spreadsheet. + #[ serde( rename = "spreadsheetId" ) ] + pub spreadsheet_id : Option< String >, + + /// A list of ValueRange objects with data for each requested range. + #[ serde( rename = "valueRanges" ) ] + pub value_ranges : Option< Vec< ValueRange > >, + } + + /// Response from [`values.update`](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/update). + #[ derive( Debug, Serialize, Deserialize, Clone ) ] + pub struct UpdateValuesResponse + { + /// The ID of the spreadsheet that was updated. + #[ serde( rename = "spreadsheetId" ) ] + pub spreadsheet_id : Option< String >, + + /// The range (A1 notation) that was updated. + #[ serde( rename = "updatedRange" ) ] + pub updated_range : Option< String >, + + /// How many rows were updated. + #[ serde( rename = "updatedRows" ) ] + pub updated_rows : Option< u32 >, + + /// How many columns were updated. + #[ serde( rename = "updatedColumns" ) ] + pub updated_columns : Option< u32 >, + + /// How many cells were updated. + #[ serde( rename = "updatedCells" ) ] + pub updated_cells : Option< u32 >, + + /// If `includeValuesInResponse` was `true`, this field contains the updated data. + #[ serde( rename = "updatedData" ) ] + pub updated_data : Option< ValueRange >, + } + + /// Response from [`values.batchUpdate`](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchUpdate). + #[ derive( Debug, Default, Serialize, Deserialize, Clone ) ] + pub struct BatchUpdateValuesResponse + { + /// The ID of the spreadsheet that was updated. + #[ serde( rename = "spreadsheetId" ) ] + pub spreadsheet_id : Option< String >, + + /// Total number of rows updated. + #[ serde( rename = "totalUpdatedRows" ) ] + pub total_updated_rows : Option< u32 >, + + /// Total number of columns updated. + #[ serde( rename = "totalUpdatedColumns" ) ] + pub total_updated_columns : Option< u32 >, + + /// Total number of cells updated. + #[ serde( rename = "totalUpdatedCells" ) ] + pub total_updated_cells : Option< u32 >, + + /// Total number of sheets with updates. + #[ serde( rename = "totalUpdatedSheets" ) ] + pub total_updated_sheets : Option< u32 >, + + /// The response for each range updated (if `includeValuesInResponse` was `true`). + pub responses : Option< Vec< ValueRange > >, + } + + /// Response from [`values.append`](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/append). + #[ derive( Debug, Serialize, Deserialize, Clone ) ] + pub struct ValuesAppendResponse + { + /// The ID of the spreadsheet to which data was appended. + #[ serde( rename = "spreadsheetId" ) ] + pub spreadsheet_id : Option< String >, + + /// The range (A1 notation) that covered the appended data before the append. + #[ serde( rename = "tableRange" ) ] + pub table_range : Option< String >, + + /// If `includeValuesInResponse` was `true`, this field contains metadata about the update. + pub updates : Option< UpdateValuesResponse >, + } + + /// Response from [values.clearBatch](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchClear) + #[ derive( Debug, Default, Serialize, Deserialize ) ] + pub struct BatchClearValuesResponse + { + /// The spreadsheet the updates were applied to. + #[ serde( rename = "spreadsheetId" ) ] + pub spreadsheet_id : Option< String >, + + /// The ranges that were cleared, in A1 notation. If the requests are for an unbounded range or a ranger larger than the bounds of the sheet, this is the actual ranges that were cleared, bounded to the sheet's limits. + #[ serde( rename = "clearedRanges" ) ] + pub cleared_ranges : Option< Vec< String > > + } + + /// Response from [`values.clear`](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/clear) + #[ derive( Debug, Serialize, Deserialize ) ] + pub struct ValuesClearResponse + { + /// The spreadsheet the updates were applied to. + #[ serde( rename = "spreadsheetId" ) ] + pub spreadsheet_id : Option< String >, + + /// The range (in A1 notation) that was cleared. (If the request was for an unbounded range or a ranger larger than the bounds of the sheet, this will be the actual range that was cleared, bounded to the sheet's limits.) + #[ serde( rename = "clearedRange" ) ] + pub cleared_range : Option< String > + } + + /// Determines how existing data is changed when new data is input. + #[ derive( Debug, Clone, Copy, Serialize, Deserialize ) ] + pub enum InsertDataOption + { + /// The new data overwrites existing data in the areas it is written. (Note: adding data to the end of the sheet will still insert new rows or columns so the data can be written.) + #[ serde( rename = "OVERWRITE" ) ] + Overwrite, + + /// Rows are inserted for the new data. + #[ serde( rename = "INSERT_ROWS" ) ] + InsertRows + } + + /// Determines how dates should be rendered in the output. + #[ derive( Debug, Clone, Copy, Serialize ) ] + pub enum DateTimeRenderOption + { + /// Instructs date, time, datetime, and duration fields to be output as doubles in "serial number" format, as popularized by Lotus 1-2-3. The whole number portion of the value (left of the decimal) counts the days since December 30th 1899. The fractional portion (right of the decimal) counts the time as a fraction of the day. For example, January 1st 1900 at noon would be 2.5, 2 because it's 2 days after December 30th 1899, and .5 because noon is half a day. February 1st 1900 at 3pm would be 33.625. This correctly treats the year 1900 as not a leap year. + #[ serde( rename = "SERIAL_NUMBER" ) ] + SerialNumber, + + /// Instructs date, time, datetime, and duration fields to be output as strings in their given number format (which depends on the spreadsheet locale). + #[ serde( rename = "FORMATTED_STRING" ) ] + FormattedString + } + + /// Determines how values should be rendered in the output. + #[ derive( Debug, Clone, Copy, Serialize ) ] + pub enum ValueRenderOption + { + /// Values will be calculated & formatted in the response according to the cell's formatting. Formatting is based on the spreadsheet's locale, not the requesting user's locale. For example, if A1 is 1.23 and A2 is =A1 and formatted as currency, then A2 would return "$1.23". + #[ serde( rename = "FORMATTED_VALUE" ) ] + FormattedValue, + + /// Values will be calculated, but not formatted in the reply. For example, if A1 is 1.23 and A2 is =A1 and formatted as currency, then A2 would return the number 1.23. + #[ serde( rename = "UNFORMATTED_VALUE" ) ] + UnformattedValue, + + /// Values will not be calculated. The reply will include the formulas. For example, if A1 is 1.23 and A2 is =A1 and formatted as currency, then A2 would return "=A1". + /// + /// Sheets treats date and time values as decimal values. This lets you perform arithmetic on them in formulas. For more information on interpreting date and time values, see About date & time values. + #[ serde( rename = "FORMULA" ) ] + Formula + } + + /// Determines how input data should be interpreted. + #[ derive( Debug, Clone, Copy, Default, Serialize ) ] + pub enum ValueInputOption + { + /// The values the user has entered will not be parsed and will be stored as-is. + #[ default ] + #[ serde( rename = "RAW" ) ] + Raw, + + /// The values will be parsed as if the user typed them into the UI. Numbers will stay as numbers, but strings may be converted to numbers, dates, etc. following the same rules that are applied when entering text into a cell via the Google Sheets UI. + #[ serde( rename = "USER_ENTERED" ) ] + UserEntered + } + + /// Indicates which dimension an operation should apply to. + #[ derive( Debug, Clone, Copy, Serialize, Deserialize ) ] + pub enum Dimension + { + /// Operates on the rows of a sheet. + #[ serde( rename = "ROWS" ) ] + Row, + + /// Operates on the columns of a sheet. + #[ serde( rename = "COLUMNS" ) ] + Column, + } + + /// Data within a range of the spreadsheet. + #[ derive( Debug, Clone, Default, serde::Serialize, serde::Deserialize ) ] + pub struct ValueRange + { + /// The range the values cover, in A1 notation. For output, this range indicates the entire requested range, even though the values will exclude trailing rows and columns. When appending values, this field represents the range to search for a table, after which values will be appended. + pub range : Option< String >, + + /// The major dimension of the values. + /// For output, if the spreadsheet data is: A1=1,B1=2,A2=3,B2=4, then requesting range=A1:B2,majorDimension=ROWS will return [[1,2],[3,4]], whereas requesting range=A1:B2,majorDimension=COLUMNS will return [[1,3],[2,4]]. + /// + /// For input, with range=A1:B2,majorDimension=ROWS then [[1,2],[3,4]] will set A1=1,B1=2,A2=3,B2=4. With range=A1:B2,majorDimension=COLUMNS then [[1,2],[3,4]] will set A1=1,B1=3,A2=2,B2=4. + /// + /// When writing, if this field is not set, it defaults to ROWS. + #[ serde( rename = "majorDimension" ) ] + pub major_dimension : Option< Dimension >, + + /// The data that was read or to be written. This is an array of arrays, the outer array representing all the data and each inner array representing a major dimension. Each item in the inner array corresponds with one cell. + /// + /// For output, empty trailing rows and columns will not be included. + /// + /// For input, supported value types are: bool, string, and double. Null values will be skipped. To set a cell to an empty value, set the string value to an empty string. + pub values : Option< Vec< Vec< serde_json::Value > > > + } + +} + + +crate::mod_interface! +{ + own use + { + Auth, + Client, + SheetProperties, + Dimension, + ValueRange, + InsertDataOption, + ValueInputOption, + ValueRenderOption, + ValuesAppendRequest, + ValuesAppendResponse, + UpdateValuesResponse, + BatchUpdateValuesRequest, + BatchUpdateValuesResponse, + ValuesClearResponse, + BatchClearValuesRequest, + BatchClearValuesResponse + }; +} \ No newline at end of file diff --git a/module/move/gspread/src/gcore/error.rs b/module/move/gspread/src/gcore/error.rs new file mode 100644 index 0000000000..a363c7b68a --- /dev/null +++ b/module/move/gspread/src/gcore/error.rs @@ -0,0 +1,155 @@ +//! +//! Gspread errors. +//! + + +mod private +{ + use derive_tools::AsRefStr; + use error_tools::typed::Error; + use crate::*; + use ser; + + /// # Error + /// + /// Represents errors that can occur while interacting with the Google Sheets API + /// or during related operations in the application. + /// + /// ## Variants: + /// + /// ### `ApiError` + /// + /// Represents an error returned by the Google Sheets API. + /// + /// **Details:** + /// This error occurs when the API returns a specific error message. + /// The error message from the Google Sheets API is stored and displayed. + /// + /// **Fields:** + /// - `String`: + /// The raw error returned by the API. + /// + /// ### `InvalidUrl` + /// + /// Represents an error caused by an invalid URL format. + /// + /// **Details:** + /// This error occurs when the provided URL does not match the expected format. + /// + /// **Fields:** + /// - `String`: + /// The invalid URL or a message describing the issue. + /// + /// ### `CellError` + /// + /// Represents an error related to a cell in the spreadsheet. + /// + /// **Details:** + /// This error indicates that a cell was not retrieved or updated successfully. + /// + /// **Fields:** + /// - `String`: + /// A message describing the issue with the cell. + /// + /// ### `InvalidJSON` + /// + /// Represents an error caused by invalid JSON input or parsing issues. + /// + /// **Details:** + /// This error occurs when the provided JSON data does not conform to the expected structure or format. + /// + /// **Fields:** + /// - `String`: + /// A detailed error message describing the JSON issue. + /// + /// ### `ParseError` + /// + /// Represents a generic parsing error. + /// + /// **Details:** + /// This error is raised when a string or other input cannot be parsed into the expected format or structure. + /// + /// **Fields:** + /// - `String`: + /// A message describing the parse error. + #[ ser::serde_as ] + #[ derive( Debug, Error, AsRefStr, ser::Serialize ) ] + #[ serde( tag = "type", content = "data" ) ] + pub enum Error + { + /// Represents an error returned by the Google Sheets API. + /// + /// # Details + /// This error occurs when the API returns a specific error message. + /// The error message from the Google Sheets API is stored and displayed. + /// + /// # Fields + /// - `String`: The raw error returned by the API. + #[ error( "Google Sheets returned error:\n{0}" ) ] + ApiError( String ), + + /// Represents an error returned by yup_oauth2. + /// + /// # Details + /// This error can error while token initialization. + /// + /// # Fields + /// - `String`: The raw error returned by token(). + #[ error( "Authentication error:\n{0}" ) ] + AuthError( String ), + + /// Represents an error caused by an invalid URL format. + /// + /// # Details + /// This error occurs when the provided URL does not match the expected format + /// + /// # Fields + /// - `String`: The invalid URL or a message describing the issue. + #[ error( "Invalid URL format:\n{0}" ) ] + InvalidUrl( String ), + + /// Represents an error related to a cell in the spreadsheet. + /// + /// # Details + /// This error indicates that a cell was not got or updated + /// + /// # Fields + /// - `String`: A message describing the issue with the cell. + #[ error( "Cell error:\n{0}" ) ] + CellError( String ), + + /// Represents an error caused by invalid JSON input or parsing issues. + /// + /// # Details + /// This error occurs when the provided JSON data does not conform to the expected + /// structure or format. + /// + /// # Fields + /// - `String`: A detailed error message describing the JSON issue. + #[ error( "Invalid JSON format:\n{0}" ) ] + InvalidJSON( String ), + + /// Represents a generic parsing error. + /// + /// # Details + /// This error is raised when a string or other input cannot be parsed + /// into the expected format or structure. + /// + /// # Fields + /// - `String`: A message describing the parse error. + #[ error( "Parse error:\n{0}" ) ] + ParseError( String ) + } + + /// Type alias for `std::result::Result< T, Error >`. + pub type Result< T > = std::result::Result< T, Error >; +} + +crate::mod_interface! +{ + own use + { + Error, + Result + }; +} \ No newline at end of file diff --git a/module/move/gspread/src/gcore/secret.rs b/module/move/gspread/src/gcore/secret.rs new file mode 100644 index 0000000000..fecb04cf0f --- /dev/null +++ b/module/move/gspread/src/gcore/secret.rs @@ -0,0 +1,396 @@ +//! +//! Module to manage with secrets. +//! + +mod private +{ + use crate::*; + use std:: + { + env, + sync::OnceLock, + }; + + use error_tools::typed::Error; + use ser::DisplayFromStr; + + /// # Secret's Errors + /// + /// This enumeration defines errors that can occur while working with secrets. + /// + /// **Errors:** + /// + /// - `SecretFileIllformed` + /// - Occurs when the secret file is not properly formatted. + /// - Associated data: + /// - `dotenv::Error`: Provides details about the specific formatting issue. + /// + /// - `VariableMissing` + /// - Indicates that a required variable is missing from the secret configuration. + /// - Associated data: + /// - `&'static str`: The name of the missing variable. + /// + /// - `VariableIllformed` + /// - Signals an issue while processing a specific secret variable. + /// - Associated data: + /// - `&'static str`: The name of the variable that caused the issue. + /// - `String`: Detailed error message or explanation. + #[ ser::serde_as ] + #[ derive( Debug, Error, ser::Serialize ) ] + #[ serde( tag = "type", content = "data" ) ] + pub enum Error + { + #[ error( "Secret file is illformed\n{0}" ) ] + SecretFileIllformed + ( + #[ from ] + #[ serde_as( as = "DisplayFromStr" ) ] + dotenv::Error + ), + + #[ error( "Secret missing the variable {0}" ) ] + VariableMissing( &'static str ), + + #[ error( "Secret error processing in the variable {0}\n{1}" ) ] + VariableIllformed( &'static str, String ), + + } + + /// # Result + /// + /// A type alias for `std::result::Result` with the error type `Error`. + pub type Result< R > = std::result::Result< R, Error >; + + pub trait Secret + { + #[ allow( async_fn_in_trait ) ] + async fn get_token( &self ) -> gcore::error::Result< String >; + } + + /// # ApplicationSecret + /// + /// A struct that represents configuration secrets loaded from a `.env` file. + /// + /// This structure contains essential fields required for authentication and token management, + /// such as client credentials and URIs. + /// + /// ## Fields + /// + /// - `CLIENT_SECRET` + /// - A `String` containing the client secret used for authentication. + /// - `CLIENT_ID` + /// - A `String` containing the client ID associated with the application. + /// - `AUTH_URI` + /// - A `String` representing the authentication URI used for OAuth2 flows. + /// - Defaults to `"https://accounts.google.com/o/oauth2/auth"` if not specified in the `.env` file. + /// - `TOKEN_URI` + /// - A `String` representing the token URI used to retrieve OAuth2 tokens. + /// - Defaults to `"https://oauth2.googleapis.com/token"` if not specified in the `.env` file. + /// + /// ## Usage + /// + /// The `Secret` struct is intended to be loaded from a `.env` file using the `dotenv` crate. + /// It provides methods for loading and accessing these secrets within the application. + /// + /// Example of fields in a `.env` file: + /// ```text + /// CLIENT_SECRET=your_client_secret + /// CLIENT_ID=your_client_id + /// AUTH_URI=https://accounts.google.com/o/oauth2/auth + /// TOKEN_URI=https://oauth2.googleapis.com/token + /// ``` + #[ derive( Debug ) ] + #[ allow( non_snake_case ) ] + pub struct ApplicationSecret + { + pub CLIENT_SECRET : String, + pub CLIENT_ID: String, + pub AUTH_URI : String, + pub TOKEN_URI : String, + } + + impl ApplicationSecret + { + #[ allow( non_snake_case ) ] + pub fn load() -> Result< Self > + { + let path = "./.secret/.env"; + + let r = dotenv::from_path( path ); + if let Err( ref err ) = r + { + if !matches!( err, dotenv::Error::Io( _ ) ) + { + return Err( r.expect_err( &format!( "Failed to load {path}" ) ).into() ); + } + } + + let config = Self + { + CLIENT_SECRET : var( "CLIENT_SECRET", None )?, + CLIENT_ID : var( "CLIENT_ID", None )?, + AUTH_URI : var ( "AUTH_URI", Some( DEFAULT_AUTH_URI ) )?, + TOKEN_URI : var ( "TOKEN_URI", Some( DEFAULT_TOKEN_URI ) )? + }; + Ok( config ) + } + + pub fn read() -> ApplicationSecret + { + Self::load().unwrap_or_else( | err | + { + let example = include_str!("../../.secret/readme.md"); + let explanation = format! + ( + r#" = Lack of secrets + +Failed to load secret or some its parameters. +{err} + + = Fix + +Add missing secret to .env file in .secret directory. Example: MISSING_SECRET=YOUR_MISSING_SECRET + + = More information + +{example} +"# + ); + panic!( "{}", explanation ); + } ) + } + + pub fn get() -> &'static ApplicationSecret + { + static INSTANCE : OnceLock< ApplicationSecret > = OnceLock::new(); + INSTANCE.get_or_init( || Self::read() ) + } + + } + + impl Secret for ApplicationSecret + { + async fn get_token( &self ) -> gcore::error::Result< String > + { + let secret : yup_oauth2::ApplicationSecret = yup_oauth2::ApplicationSecret + { + client_id : self.CLIENT_ID.clone(), + auth_uri : self.AUTH_URI.clone(), + token_uri : self.TOKEN_URI.clone(), + client_secret : self.CLIENT_SECRET.clone(), + .. Default::default() + }; + + let authenticator = yup_oauth2::InstalledFlowAuthenticator::builder( + secret, + yup_oauth2::InstalledFlowReturnMethod::HTTPRedirect, + ) + .build() + .await + .map_err( | err | gcore::error::Error::AuthError( err.to_string() ) )?; + + let scopes = &[ GOOGLE_SPREADSHEET_SCOPE ]; + + let access_token = authenticator + .token( scopes ) + .await + .map_err( | err | gcore::error::Error::AuthError( err.to_string() ) )?; + + let token = access_token.token().unwrap(); + Ok( token.to_string() ) + } + } + + + /// # ServiceAccountSecret + #[ derive( Debug ) ] + #[ allow( non_snake_case ) ] + pub struct ServiceAccountSecret + { + pub KEY_TYPE : String, + pub PROJECT_ID: String, + pub PRIVATE_KEY_ID : String, + pub PRIVATE_KEY : String, + pub CLIENT_EMAIL : String, + pub CLIENT_ID : String, + pub AUTH_URI : String, + pub TOKEN_URI : String, + pub AUTH_PROVIDER_X509_CERT_URL : String, + pub CLIENT_X509_CERT_URL : String, + } + + impl ServiceAccountSecret + { + #[ allow( non_snake_case ) ] + pub fn load() -> Result< Self > + { + let path = "./.secret/.env"; + + let r = dotenv::from_path( path ); + if let Err( ref err ) = r + { + if !matches!( err, dotenv::Error::Io( _ ) ) + { + return Err( r.expect_err( &format!( "Failed to load {path}" ) ).into() ); + } + } + + let config = Self + { + KEY_TYPE : var( "GOOGLE_KEY_TYPE", None )?, + PROJECT_ID : var( "GOOGLE_PROJECT_ID", None )?, + PRIVATE_KEY_ID : var ( "GOOGLE_PRIVATE_KEY_ID", None )?, + PRIVATE_KEY : var ( "GOOGLE_PRIVATE_KEY", None )?, + CLIENT_EMAIL : var( "GOOGLE_CLIENT_EMAIL", None )?, + CLIENT_ID : var( "GOOGLE_CLIENT_ID", None )?, + AUTH_URI : var( "GOOGLE_AUTH_URI", None )?, + TOKEN_URI : var( "GOOGLE_TOKEN_URI", None )?, + AUTH_PROVIDER_X509_CERT_URL : var( "GOOGLE_AUTH_PROVIDER_X509_CERT_URL", None )?, + CLIENT_X509_CERT_URL : var( "GOOGLE_CLIENT_X509_CERT_URL", None )?, + }; + Ok( config ) + } + + pub fn read() -> ServiceAccountSecret + { + Self::load().unwrap_or_else( | err | + { + let example = include_str!("../../.secret/readme.md"); + let explanation = format! + ( + r#" = Lack of secrets + +Failed to load secret or some its parameters. +{err} + + = Fix + +Add missing secret to .env file in .secret directory. Example: MISSING_SECRET=YOUR_MISSING_SECRET + + = More information + +{example} +"# + ); + panic!( "{}", explanation ); + }) + } + + pub fn get() -> &'static ServiceAccountSecret + { + static INSTANCE : OnceLock< ServiceAccountSecret > = OnceLock::new(); + INSTANCE.get_or_init( || Self::read() ) + } + + } + + impl Secret for ServiceAccountSecret + { + async fn get_token( &self ) -> gcore::error::Result< String > + { + let key = yup_oauth2::ServiceAccountKey + { + key_type : Some( self.KEY_TYPE.clone() ), + project_id : Some( self.PROJECT_ID.clone() ), + private_key_id : Some( self.PRIVATE_KEY_ID.clone() ), + private_key : self.PRIVATE_KEY.clone(), + client_email : self.CLIENT_EMAIL.clone(), + client_id : Some( self.CLIENT_ID.clone() ), + auth_uri : Some( self.AUTH_URI.clone() ), + token_uri : self.TOKEN_URI.clone(), + auth_provider_x509_cert_url : Some( self.AUTH_PROVIDER_X509_CERT_URL.clone() ), + client_x509_cert_url : Some( self.CLIENT_X509_CERT_URL.clone() ), + }; + + let auth = yup_oauth2::ServiceAccountAuthenticator::builder( key ) + .build() + .await + .map_err( | err | gcore::error::Error::AuthError( err.to_string() ) )?; + + let scopes = &[ GOOGLE_SPREADSHEET_SCOPE ]; + + let token = auth.token( scopes ).await.map_err( | err | gcore::error::Error::AuthError( err.to_string() ) )?; + + let token = token.token().unwrap(); + + Ok( token.to_string() ) + } + } + + /// # `var` + /// + /// Retrieves the value of an environment variable, or returns a default value if the variable is not set. + /// + /// **Parameters:** + /// - `name`: + /// A `&'static str` specifying the name of the environment variable to retrieve. + /// - `default`: + /// An `Option<&'static str>` containing the default value to return if the variable is not set. + /// If `None`, an error is returned when the variable is missing. + /// + /// **Returns:** + /// - `Result`: + fn var + ( + name : &'static str, + default : Option< &'static str >, + ) -> Result < String > + { + match env::var( name ) + { + Ok( val ) => Ok ( val ), + Err( _ ) => + { + if let Some( default_value ) = default + { + Ok( default_value.to_string() ) + } + else + { + Err ( Error::VariableMissing( name ) ) + } + } + } + } + + /// # `_var_path` + /// + /// Retrieves the value of an environment variable, interprets it as a path, and converts it to an absolute path. + /// + /// **Parameters:** + /// - `name`: + /// A `&'static str` specifying the name of the environment variable to retrieve. + /// - `default`: + /// An `Option<&'static str>` containing the default value to use if the variable is not set. + /// If `None`, an error is returned when the variable is missing. + /// + /// **Returns:** + /// - `Result` + 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, + ApplicationSecret, + ServiceAccountSecret, + }; +} \ No newline at end of file diff --git a/module/move/gspread/src/lib.rs b/module/move/gspread/src/lib.rs new file mode 100644 index 0000000000..d3614676ff --- /dev/null +++ b/module/move/gspread/src/lib.rs @@ -0,0 +1,39 @@ +use mod_interface::mod_interface; +use error_tools::thiserror; + +mod private +{ +} + +pub mod ser +{ + pub use serde:: + { + Serialize, + Deserialize + }; + pub use serde_json:: + { + error::Error, + self + }; + pub use serde_with::*; +} + +crate::mod_interface! +{ + + layer gcore; + layer debug; + layer commands; + layer actions; + layer utils; + + exposed use ::reflect_tools:: + { + Fields, + _IteratorTrait, + IteratorTrait, + }; + +} \ No newline at end of file diff --git a/module/move/gspread/src/utils.rs b/module/move/gspread/src/utils.rs new file mode 100644 index 0000000000..73ad488dfd --- /dev/null +++ b/module/move/gspread/src/utils.rs @@ -0,0 +1,7 @@ +mod private {} + +crate::mod_interface! +{ + layer display_table; + layer constants; +} \ No newline at end of file diff --git a/module/move/gspread/src/utils/constants.rs b/module/move/gspread/src/utils/constants.rs new file mode 100644 index 0000000000..ad16602b21 --- /dev/null +++ b/module/move/gspread/src/utils/constants.rs @@ -0,0 +1,19 @@ + +mod private +{ + pub const DEFAULT_TOKEN_URI: &'static str = "https://oauth2.googleapis.com/token"; + pub const DEFAULT_AUTH_URI: &'static str = "https://accounts.google.com/o/oauth2/auth"; + pub const GOOGLE_API_URL: &'static str = "https://sheets.googleapis.com/v4/spreadsheets"; + pub const GOOGLE_SPREADSHEET_SCOPE: &'static str = "https://www.googleapis.com/auth/spreadsheets"; +} + +crate::mod_interface! +{ + prelude use + { + DEFAULT_AUTH_URI, + DEFAULT_TOKEN_URI, + GOOGLE_API_URL, + GOOGLE_SPREADSHEET_SCOPE + }; +} \ No newline at end of file diff --git a/module/move/gspread/src/utils/display_table.rs b/module/move/gspread/src/utils/display_table.rs new file mode 100644 index 0000000000..259e59e1c1 --- /dev/null +++ b/module/move/gspread/src/utils/display_table.rs @@ -0,0 +1,99 @@ +//! +//! Module with functions to display HTTP requests results in a table view. +//! + +mod private +{ + use std::fmt; + use format_tools:: + { + TableFormatter, + print, + output_format, + TableOutputFormat + }; + + /// # `display_rows` + /// + /// Displays rows of data in a table view. + /// + /// This function calls `display_data` internally to format and render the data in a tabular format. + /// + /// ## Parameters: + /// - `data`: + /// A reference to an object implementing the `TableFormatter` trait, which provides the data to display. + /// - `f`: + /// A mutable reference to a `fmt::Formatter` used for formatting the output. + /// + /// ## Returns: + /// - `fmt::Result`: + pub fn display_rows< 'a > + ( + data : &'a impl TableFormatter< 'a >, + f : &mut fmt::Formatter< '_ > + ) -> fmt::Result + { + display_data( data, f, output_format::Table::default() ) + } + + /// # `display_header` + /// + /// Displays the header of a table view. + /// + /// This function calls `display_data` internally to format and render the header in a tabular format. + /// + /// ## Parameters: + /// - `data`: + /// A reference to an object implementing the `TableFormatter` trait, which provides the header data to display. + /// - `f`: + /// A mutable reference to a `fmt::Formatter` used for formatting the output. + /// + /// ## Returns: + /// - `fmt::Result`: + pub fn display_header < 'a > + ( + data : &'a impl TableFormatter< 'a >, + f : &mut fmt::Formatter< '_ > + ) -> fmt::Result + { + display_data( data, f, output_format::Table::default() ) + } + + /// # `display_data` + /// + /// Displays data in a table view with a specific output format. + /// + /// This function creates a printer and context objects and delegates the rendering logic to `TableFormatter::fmt`. + /// + /// ## Parameters: + /// - `data`: + /// A reference to an object implementing the `TableFormatter` trait, which provides the data to display. + /// - `f`: + /// A mutable reference to a `fmt::Formatter` used for formatting the output. + /// - `format`: + /// An object implementing the `TableOutputFormat` trait, defining the desired output format for the table. + /// + /// ## Returns: + /// - `fmt::Result`: + pub fn display_data < 'a > + ( + data : &'a impl TableFormatter< 'a >, + f : &mut fmt::Formatter< '_ >, + format : impl TableOutputFormat, + ) -> fmt::Result + { + let printer = print::Printer::with_format( &format ); + let mut context = print::Context::new( f, printer ); + TableFormatter::fmt( data, &mut context ) + } + +} + +crate::mod_interface! +{ + own use + { + display_rows, + display_header + }; +} \ No newline at end of file diff --git a/module/move/gspread/tests/mock/append_row.rs b/module/move/gspread/tests/mock/append_row.rs new file mode 100644 index 0000000000..915f4509ba --- /dev/null +++ b/module/move/gspread/tests/mock/append_row.rs @@ -0,0 +1,217 @@ +//! +//! Tests for `append_row` function. +//! + +use gspread::gcore::client::BatchUpdateValuesResponse; +use httpmock::prelude::*; +use serde_json::json; +use std::collections::HashMap; + +use gspread::*; +use actions::gspread::append_row; +use gcore::ApplicationSecret; +use gcore::client::Client; + + +/// # What +/// We test appending a row at the and of a sheet. +/// +/// # How +/// 1. Start a mock server. +/// 2. Create a client. +/// 3. Call `append_row` function wich sends a POST request to /{spreadshett_id}/values/{range}:append +/// 4. Check results. +#[tokio::test] +async fn test_mock_append_row_should_work() +{ + let spreadsheet_id = "12345"; + let body_batch_update = BatchUpdateValuesResponse + { + spreadsheet_id : Some( spreadsheet_id.to_string() ), + total_updated_rows : Some( 1 ), + total_updated_columns : Some( 3 ), + total_updated_cells : Some( 3 ), + total_updated_sheets : Some( 1 ), + responses : None, + }; + + let body_values_append = json!({ + "updates": { + "updatedRange": "tab2!A5" + } + }); + + let server = MockServer::start(); + + let mock_append = server.mock( | when, then | { + when.method( POST ) + .path( "/12345/values/tab2!A1:append" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body( body_values_append.clone() ); + } ); + + let mock_batch_update = server.mock( | when, then | { + when.method( POST ) + .path( "/12345/values:batchUpdate" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj( &body_batch_update ); + } ); + + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + let mut row_key_val = HashMap::new(); + row_key_val.insert( "A".to_string(), json!( 1 ) ); + row_key_val.insert( "B".to_string(), json!( 2 ) ); + row_key_val.insert( "C".to_string(), json!( 3 ) ); + + let response = append_row( &client, spreadsheet_id, "tab2", &row_key_val ) + .await + .expect( "append_row failed." ); + + mock_append.assert(); + mock_batch_update.assert(); + + assert_eq!( response.spreadsheet_id, Some( spreadsheet_id.to_string() ) ); + assert_eq!( response.total_updated_cells, Some( 3 ) ); +} + +#[ allow( non_snake_case ) ] +#[ tokio::test ] +async fn test_mock_append_row_begining_from_C_column_should_work() +{ + let spreadsheet_id = "12345"; + let body_batch_update = BatchUpdateValuesResponse + { + spreadsheet_id : Some( spreadsheet_id.to_string() ), + total_updated_rows : Some( 1 ), + total_updated_columns : Some( 7 ), + total_updated_cells : Some( 7 ), + total_updated_sheets : Some( 1 ), + responses : None, + }; + let body_values_append = json!({ + "updates": { + "updatedRange": "tab2!A5" + } + }); + + // 1. Start a mock server. + let server = MockServer::start(); + + let mock_append = server.mock( | when, then | { + when.method( POST ) + .path( "/12345/values/tab2!A1:append" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body( body_values_append.clone() ); + } ); + + let mock_batch_update = server.mock( | when, then | { + when.method( POST ) + .path( "/12345/values:batchUpdate" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj( &body_batch_update ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Call `append_row`. + let mut row_key_val = HashMap::new(); + row_key_val.insert( "C".to_string(), json!( 1 ) ); + row_key_val.insert( "D".to_string(), json!( 2 ) ); + row_key_val.insert( "F".to_string(), json!( 3 ) ); + row_key_val.insert( "G".to_string(), json!( 4 ) ); + + let response = append_row( &client, spreadsheet_id, "tab2", &row_key_val ) + .await + .expect( "append_row failed." ); + + // 4. Check results. + mock_append.assert(); + mock_batch_update.assert(); + + assert_eq!( response.spreadsheet_id, Some( spreadsheet_id.to_string() ) ); + assert_eq!( response.total_updated_cells, Some( 7 ) ); +} + + +/// # What +/// We test that we can not pass a HashMap with invalid column index. +/// +/// # How +/// 1. Start a mock server. +/// 2. Create a client. +/// 3. Call `append_row` wich sends a POST request to /{spreadsheet_id}/values/{range}:append +#[ tokio::test ] +#[ should_panic ] +async fn test_mock_append_row_with_bad_values_should_panic() +{ + let spreadsheet_id = "12345"; + // 1. Start a mock server. + let server = MockServer::start(); + let _mock = server.mock( | when, then | { + when.method( POST ) + .path( "/12345/values/tab2!A1:append" ); + then.status( 400 ) + .header( "Content-Type", "application/json" ) + .body( r#"{ "error": "column index is invalid" }"# ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Call `append_row`. + let mut row_key_val = HashMap::new(); + row_key_val.insert( "AAAAA".to_string(), json!( 1 ) ); + row_key_val.insert( "BBBBA".to_string(), json!( 2 ) ); + row_key_val.insert( "CCCCA".to_string(), json!( 3 ) ); + + let _response = append_row( &client, spreadsheet_id, "tab2", &row_key_val ) + .await + .expect( "append_row failed. Ok!" ); +} + +#[ tokio::test ] +#[ should_panic ] +async fn test_mock_append_row_with_bad_values2_should_panic() +{ + let spreadsheet_id = "12345"; + // 1. Start a mock server. + let server = MockServer::start(); + let _mock = server.mock( | when, then | { + when.method( POST ) + .path( "/12345/values/tab2!A1:append" ); + then.status( 400 ) + .header( "Content-Type", "application/json" ) + .body( r#"{ "error": "column name is invalid" }"# ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Call `append_row`. + let mut row_key_val = HashMap::new(); + row_key_val.insert( "123".to_string(), json!( 1 ) ); + row_key_val.insert( "a".to_string(), json!( 2 ) ); + row_key_val.insert( "qdqwq".to_string(), json!( 3 ) ); + + let _response = append_row( &client, spreadsheet_id, "tab2", &row_key_val ) + .await + .expect( "append_row failed. Ok!" ); +} \ No newline at end of file diff --git a/module/move/gspread/tests/mock/clear.rs b/module/move/gspread/tests/mock/clear.rs new file mode 100644 index 0000000000..70668c4699 --- /dev/null +++ b/module/move/gspread/tests/mock/clear.rs @@ -0,0 +1,153 @@ +//! +//! Tests for `clear` function. +//! + +use httpmock::prelude::*; +use gspread::*; +use actions::gspread::clear; +use gcore::ApplicationSecret; +use gcore::client:: +{ + Client, + ValuesClearResponse +}; + + +/// # What +/// We test clearing a sheet by specifying its name. +/// +/// # How +/// 1. Start a mock server. +/// 2. Create a client. +/// 3. Call `clear` function which sends a POST request to /{spreadsheet_id}/values/{sheet_name}!A:ZZZ:clear +/// 4. Check results. +#[ tokio::test ] +async fn test_mock_clear_should_work() +{ + let spreadsheet_id = "12345"; + let sheet_name = "tab2"; + + let body = ValuesClearResponse + { + spreadsheet_id : Some( spreadsheet_id.to_string() ), + cleared_range : Some( "tab2!A:ZZZ".to_string() ) + }; + + // 1. Start a mock server. + let server = MockServer::start(); + let mock = server.mock( | when, then | + { + when.method( POST ) + .path( "/12345/values/tab2!A:ZZZ:clear" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj( &body ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Call `clear`. + let response = clear( &client, spreadsheet_id, sheet_name ) + .await + .expect( "clear failed." ); + + // 4. Check results. + mock.assert(); + + assert_eq!( response.spreadsheet_id, Some( spreadsheet_id.to_string() ) ); + assert_eq!( response.cleared_range, Some( "tab2!A:ZZZ".to_string() ) ); +} + + +/// # What +/// We test clearing a sheet when there is no data to clear. +/// +/// # How +/// 1. Start a mock server. +/// 2. Create a client. +/// 3. Call `clear` which sends a POST request to /{spreadsheet_id}/values/{sheet_name}!A:ZZZ:clear +/// 4. Check results. +#[ tokio::test ] +async fn test_mock_clear_empty_result_should_work() +{ + let spreadsheet_id = "12345"; + let sheet_name = "tab2"; + let body = ValuesClearResponse + { + spreadsheet_id : Some( spreadsheet_id.to_string() ), + cleared_range : None + }; + + // 1. Start a mock server. + let server = MockServer::start(); + let mock = server.mock( | when, then | + { + when.method( POST ) + .path( "/12345/values/tab2!A:ZZZ:clear" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj( &body ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Call `clear`. + let response = clear( &client, spreadsheet_id, sheet_name ) + .await + .expect( "clear failed." ); + + // 4. Check results. + mock.assert(); + + assert_eq!( response.spreadsheet_id, Some( spreadsheet_id.to_string() ) ); + assert_eq!( response.cleared_range, None ); +} + + +/// # What +/// We test error handling if the server responds with an error. +/// +/// # How +/// 1. Start a mock server. +/// 2. Create a client. +/// 3. Call `clear` with invalid parameters or server error. +/// 4. We expect a panic. +#[ tokio::test ] +#[ should_panic ] +async fn test_mock_clear_with_error_should_panic() +{ + let spreadsheet_id = "12345"; + let sheet_name = "invalid_sheet"; + + // 1. Start a mock server. + let server = MockServer::start(); + let _mock = server.mock( | when, then | + { + when.method( POST ) + .path( "/12345/values/invalid_sheet!A:ZZZ:clear" ); + then.status( 404 ) + .header( "Content-Type", "application/json" ) + .body( r#"{ "error": { "message": "Sheet not found" } }"# ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Call `clear`. + let response = clear( &client, spreadsheet_id, sheet_name ) + .await + .expect( "clear failed. Ok!" ); + + println!( "{:?}", response ); +} diff --git a/module/move/gspread/tests/mock/clear_by_custom_row_key.rs b/module/move/gspread/tests/mock/clear_by_custom_row_key.rs new file mode 100644 index 0000000000..4662f20ea2 --- /dev/null +++ b/module/move/gspread/tests/mock/clear_by_custom_row_key.rs @@ -0,0 +1,276 @@ +//! +//! Tests for `clear_by_custom_row_key` function. +//! + +use httpmock::prelude::*; +use serde_json::json; +use gspread::*; +use actions::gspread:: +{ + clear_by_custom_row_key, + OnFind +}; +use gcore::ApplicationSecret; +use gcore::client:: +{ + BatchClearValuesResponse, + Client, + Dimension, + ValueRange +}; + + +/// # What +/// We test clearing matched rows by a custom key in a specific column. +/// +/// # How +/// 1. Start a mock server. +/// 2. Mock the first request to get the column (GET). +/// 3. Mock the second request to batch clear matched rows (POST). +/// 4. Check results. +#[ tokio::test ] +async fn test_mock_clear_by_custom_row_key_should_work() +{ + let spreadsheet_id = "12345"; + let sheet_name = "tab2"; + let column_id = "A"; + let on_find = OnFind::FirstMatchedRow; + let key_value = json!( "B" ); + let column_values = vec![ vec![ json!( "A" ), json!( "B" ), json!( "C" ) ] ]; + let response_body = ValueRange + { + range : Some( "tab2!A:A".to_string() ), + major_dimension : Some( Dimension::Column ), + values : Some( column_values.clone() ), + }; + + // 1. Start a mock server. + let server = MockServer::start(); + + // 2. Mock the GET request for the column. + let get_mock = server.mock( | when, then | + { + when.method( GET ) + .path( "/12345/values/tab2!A:A" ) + .query_param( "majorDimension", "COLUMNS" ) + .query_param( "valueRenderOption", "UNFORMATTED_VALUE" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj( &response_body ); + } ); + + // 3. Mock the POST request to batch clear. + let response_body = BatchClearValuesResponse + { + spreadsheet_id : Some( spreadsheet_id.to_string() ), + cleared_ranges : Some( vec![ "tab2!A2:ZZZ2".to_string() ] ) + }; + + let post_mock = server.mock( | when, then | + { + when.method( POST ) + .path( "/12345/values:batchClear" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj( &response_body ); + } ); + + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 4. Call `clear_by_custom_row_key`. + let result = clear_by_custom_row_key + ( + &client, + spreadsheet_id, + sheet_name, + ( column_id, key_value ), + on_find + ) + .await + .expect( "clear_by_custom_row_key failed." ); + + get_mock.assert(); + post_mock.assert(); + + assert_eq!( result.spreadsheet_id, Some( spreadsheet_id.to_string() ) ); + assert_eq!( result.cleared_ranges, Some( vec![ "tab2!A2:ZZZ2".to_string() ] ) ); +} + + +/// # What +/// We test clearing rows when column is empty or no rows match. +/// +/// # How +/// 1. Start a mock server. +/// 2. Mock the GET request that returns no values in the column. +/// 3. Check that the function returns a default response without calling batch clear. +#[ tokio::test ] +async fn test_mock_clear_by_custom_row_key_no_matches_should_return_default() +{ + let spreadsheet_id = "12345"; + let sheet_name = "tab2"; + let column_id = "A"; + let on_find = OnFind::FirstMatchedRow; + let key_value = json!( "whatever" ); + let response_body = ValueRange + { + range : Some( String::from( "tab2!A:A" ) ), + major_dimension : Some( Dimension::Column ), + values : None + }; + + // 1. Start a mock server. + let server = MockServer::start(); + // 2. Mock the GET request - returning no column data. + let get_mock = server.mock( | when, then | + { + when.method( GET ) + .path( "/12345/values/tab2!A:A" ) + .query_param( "majorDimension", "COLUMNS" ) + .query_param( "valueRenderOption", "UNFORMATTED_VALUE" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj( &response_body ); + } ); + + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // Call `clear_by_custom_row_key`. + let result = clear_by_custom_row_key + ( + &client, + spreadsheet_id, + sheet_name, + ( column_id, key_value ), + on_find + ) + .await + .expect( "clear_by_custom_row_key failed." ); + + get_mock.assert(); + + assert_eq!( result.spreadsheet_id, None ); + assert_eq!( result.cleared_ranges, None ); +} + + +/// # What +/// We test error handling when the first request (get_column) fails. +/// +/// # How +/// 1. Start a mock server. +/// 2. Mock the GET request with an error (e.g., 400). +/// 3. We expect the function to return an error (here we `.expect()` => panic). +#[ tokio::test ] +#[ should_panic ] +async fn test_mock_clear_by_custom_row_key_error_in_get_column_should_panic() +{ + let spreadsheet_id = "12345"; + let sheet_name = "tab2"; + let column_id = "1234"; + let on_find = OnFind::FirstMatchedRow; + let key_value = json!( "B" ); + + let server = MockServer::start(); + let _get_mock = server.mock( | when, then | + { + when.method( GET ) + .path( "/12345/values/tab2!A:A" ) + .query_param( "majorDimension", "COLUMNS" ) + .query_param( "valueRenderOption", "UNFORMATTED_VALUE" ); + then.status( 400 ) + .header( "Content-Type", "application/json" ) + .body( r#"{ "error": "Invalid column ID" }"# ); + } ); + + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // This call should fail and panic because we `.expect(...)`. + let result = clear_by_custom_row_key + ( + &client, + spreadsheet_id, + sheet_name, + ( column_id, key_value ), + on_find + ) + .await + .expect( "clear_by_custom_row_key failed. Ok!" ); + + println!( "{:?}", result ); +} + + +/// # What +/// We test error handling when batch clear fails. +/// +/// 1. The function successfully retrieves column data. +/// 2. The function attempts to clear batch, but that request fails. +/// 3. The function should bubble up the error (here we `.expect()` => panic). +#[ tokio::test ] +#[ should_panic ] +async fn test_mock_clear_by_custom_row_key_error_in_batch_clear_should_panic() +{ + let spreadsheet_id = "12345"; + let sheet_name = "tab2"; + let column_id = "A"; + let on_find = OnFind::FirstMatchedRow; + let key_value = json!( "B" ); + let column_values = vec![ vec![ json!( "A" ), json!( "B" ), json!( "C" ) ] ]; + let response_body = ValueRange + { + range : Some( "tab2!A:A".to_string() ), + major_dimension : Some( Dimension::Column ), + values : Some( column_values.clone() ), + }; + + let server = MockServer::start(); + let _get_mock = server.mock( | when, then | + { + when.method( GET ) + .path( "/12345/values/tab2!A:A" ) + .query_param( "majorDimension", "COLUMNS" ) + .query_param( "valueRenderOption", "UNFORMATTED_VALUE" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj( &response_body ); + } ); + + // Mock POST for batchClear - will fail. + let _post_mock = server.mock( | when, then | + { + when.method( POST ) + .path( "/12345/values:batchClear" ); + then.status( 500 ) + .header( "Content-Type", "application/json" ) + .body( r#"{ "error": { "message": "Internal Server Error" } }"# ); + } ); + + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // This call should fail and panic because the second request returns 500. + let result = clear_by_custom_row_key + ( + &client, + spreadsheet_id, + sheet_name, + ( column_id, key_value ), + on_find + ) + .await + .expect( "clear_by_custom_row_key failed. Ok!" ); + + println!( "{:?}", result ); +} diff --git a/module/move/gspread/tests/mock/common_tests.rs b/module/move/gspread/tests/mock/common_tests.rs new file mode 100644 index 0000000000..b6a3343b1e --- /dev/null +++ b/module/move/gspread/tests/mock/common_tests.rs @@ -0,0 +1,81 @@ +//! +//! Common tests for every function. +//! + +use httpmock::prelude::*; +use gspread::*; +use actions::gspread::get_cell; +use gcore:: +{ + client::Client, + ApplicationSecret +}; + + +/// # What +/// We check that any function will panic with wrong `spreadsheet_id`. +/// +/// # How +/// 1. Start a mock server. +/// 2. Create a client. +/// 3. Send a HTTP request. +#[ tokio::test ] +#[ should_panic ] +async fn test_mock_wrong_spreadsheet_id_should_panic() +{ + // 1. Start server. + let server = MockServer::start(); + let _ = server.mock( | when, then | { + when.method( GET ) + .path( "/12345/values/tab2!A2" ); + then + .status( 200 ) + .header( "Content-Type", "application/json" ) + .body( r#""# ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Send HTTP request. + let _ = get_cell( &client, "", "tab2", "A2" ) + .await + .expect( "get_cell failed" ); +} + +/// # What +/// We check that any function will panic with wrong `sheet_name`. +/// +/// # How +/// 1. Start a mock server. +/// 2. Create a client. +/// 3. Send a HTTP request. +#[ tokio::test ] +#[ should_panic ] +async fn test_mock_wrong_sheet_name_should_panic() +{ + // 1. Start server. + let server = MockServer::start(); + let _ = server.mock( | when, then | { + when.method( GET ) + .path( "/12345/values/tab2!A2" ); + then + .status( 200 ) + .header( "Content-Type", "application/json" ) + .body( r#""# ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Send HTTP request. + let _ = get_cell( &client, "12345", "wrong_name", "A2" ) + .await + .expect( "get_cell failed" ); +} \ No newline at end of file diff --git a/module/move/gspread/tests/mock/copy_to.rs b/module/move/gspread/tests/mock/copy_to.rs new file mode 100644 index 0000000000..dbe6d31c25 --- /dev/null +++ b/module/move/gspread/tests/mock/copy_to.rs @@ -0,0 +1,129 @@ +//! +//! Tests for `copy_to` function. +//! + +use httpmock::prelude::*; +use serde_json::json; +use gspread::*; +use actions::gspread::copy_to; +use gcore:: +{ + client::Client, + ApplicationSecret +}; + +/// # What +/// We test copying a sheet from one spreadsheet to another. +/// +/// # How +/// 1. Start a mock server. +/// 2. Create a client pointing to that mock server. +/// 3. Mock a `POST` request to /{spreadsheet_id}/sheets/{sheet_id}:copyTo. +/// 4. Call `copy_to`. +/// 5. Verify the response (e.g. `sheetId` and `title`). +#[ tokio::test ] +async fn test_mock_copy_to_should_work() +{ + let server = MockServer::start(); + let spreadsheet_id = "12345"; + let sheet_id = "67890"; + let destination_spreadsheet_id = "destination123"; + + let body = json! + ( + { + "sheetId" : 999, + "title" : "CopiedSheet", + "index" : 2 + } + ); + + // 1. Mock the POST request for copying the sheet. + let copy_mock = server.mock( | when, then | { + when.method( POST ) + .path( format!( "/{}/sheets/{}:copyTo", spreadsheet_id, sheet_id ) ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body( body.clone() ); + } ); + + // 2. Create a client pointing to our mock server. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Call `copy_to`. + let response = copy_to + ( + &client, + spreadsheet_id, + sheet_id, + destination_spreadsheet_id + ) + .await + .expect( "copy_to failed" ); + + // 4. Verify that the mock was indeed called. + copy_mock.assert(); + + // 5. Check the returned `SheetProperties`. + assert_eq!( response.sheet_id, Some( 999 ), "Expected sheetId to be 999" ); + assert_eq!( response.title.as_deref(), Some( "CopiedSheet" ), "Expected title to be 'CopiedSheet'" ); + assert_eq!( response.index, Some( 2 ), "Expected index to be 2" ); +} + +/// # What +/// We test error handling for `copy_to` when the API responds with an error. +/// +/// # How +/// 1. Start a mock server. +/// 2. Create a client. +/// 3. Mock a `POST` request that returns an error (400). +/// 4. Call `copy_to` and expect a panic (due to `.expect(...)`). +#[ tokio::test ] +#[ should_panic ] +async fn test_mock_copy_to_should_panic() +{ + let server = MockServer::start(); + let spreadsheet_id = "12345"; + let sheet_id = "67890"; + let destination_spreadsheet_id = "destination123"; + + // 1. Mock a failing POST request. + let _copy_mock = server.mock( | when, then | { + when.method( POST ) + .path( format!( "/{}/sheets/{}:copyTo", spreadsheet_id, sheet_id ) ); + then.status( 400 ) + .header( "Content-Type", "application/json" ) + .json_body + ( + json! + ( + { + "error" : { "message" : "Invalid request or missing permissions." } + } + ) + ); + }); + + // 2. Create a client pointing to our mock server. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Call `copy_to`, which should panic because we `.expect(...)`. + let response = copy_to + ( + &client, + spreadsheet_id, + sheet_id, + destination_spreadsheet_id + ) + .await + .expect( "copy_to failed. Ok!" ); + + // We'll never reach here because of the panic. + println!( "{:?}", response ); +} diff --git a/module/move/gspread/tests/mock/get_cell.rs b/module/move/gspread/tests/mock/get_cell.rs new file mode 100644 index 0000000000..0791b4231c --- /dev/null +++ b/module/move/gspread/tests/mock/get_cell.rs @@ -0,0 +1,132 @@ +//! +//! Tests for `get_cell` function. +//! + +use httpmock::prelude::*; +use serde_json::json; +use gspread::*; +use actions::gspread::get_cell; +use gcore::ApplicationSecret; +use gcore::client:: +{ + Client, + Dimension, + ValueRange +}; + +/// # What +/// We check that reading a specific cell from a Google Spreadsheet returns the expected result. +/// +/// # How +/// 1. Start a mock server. +/// 2. Create a client. +/// 3. Send a GET request to "/{spreadsheet_id}/values/{range}". +/// 4. Check for correct results. +#[ tokio::test ] +async fn test_mock_get_cell_should_work() +{ + let body = ValueRange + { + major_dimension : Some( Dimension::Row ), + range : Some( "tab2!A2".to_string() ), + values : Some( vec![ vec![ json!( "Steeve" ) ] ] ) + }; + + // 1. Ceating a server. + let server = MockServer::start(); + let mock = server.mock( | when, then | { + when.method( GET ) + .path( "/12345/values/tab2!A2" ); + then + .status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj( &body ); + } ); + + // 2. Creating a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Sending a PUT request. + let result = get_cell( &client, "12345", "tab2", "A2" ) + .await + .expect( "get_cell failed" ); + + // 4. Checking results. + mock.assert(); + + assert_eq!( result, serde_json::Value::String( "Steeve".to_string() ) ); +} + +#[ tokio::test ] +async fn test_mock_get_empty_cell_should_work() +{ + let body = ValueRange + { + major_dimension : Some( Dimension::Row ), + range : Some( "tab2!A2".to_string() ), + values : Some( vec![ vec![ json!( "" ) ] ] ) + }; + + // 1. Ceating a server. + let server = MockServer::start(); + let mock = server.mock( | when, then | { + when.method( GET ) + .path( "/12345/values/tab2!A2" ); + then + .status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj( &body ); + } ); + + // 2. Creating a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Sending a PUT request. + let result = get_cell( &client, "12345", "tab2", "A2" ) + .await + .expect( "get_cell failed" ); + + // 4. Checking results. + mock.assert(); + + assert_eq!( result, serde_json::Value::String( "".to_string() ) ); +} + +/// # What +/// We test that function has to return an error if invalid cellid was provideed. +/// +/// # How +/// 1. Start a mock server. +/// 2. Call `get_cell` and pass there a bad cell id. +#[ tokio::test ] +#[ should_panic ] +async fn test_mock_get_cell_with_bad_range_should_panic() +{ + // 1. Ceating a server. + let server = MockServer::start(); + let _mock = server.mock( | when, then | { + when.method( GET ) + .path( "/12345/values/tab2!AAAA2" ); + then + .status( 400 ) + .header( "Content-Type", "application/json" ) + .body( r#"{ error: invalid range. }"# ); + } ); + + // 2. Creating a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Sending a PUT request. + let _result = get_cell( &client, "12345", "tab2", "AAAA2" ) + .await + .expect( "get_cell failed" ); +} \ No newline at end of file diff --git a/module/move/gspread/tests/mock/get_column.rs b/module/move/gspread/tests/mock/get_column.rs new file mode 100644 index 0000000000..5c85723808 --- /dev/null +++ b/module/move/gspread/tests/mock/get_column.rs @@ -0,0 +1,169 @@ +//! +//! Tests for `get_column` function. +//! + +use httpmock::prelude::*; +use serde_json::json; +use gspread::*; +use actions::gspread::get_column; +use gcore::ApplicationSecret; +use gcore::client:: +{ + Client, + Dimension, + ValueRange, +}; + +/// # What +/// We test retrieving a single column from a sheet by its column ID. +/// +/// # How +/// 1. Start a mock server. +/// 2. Create a client. +/// 3. Call `get_column` function which sends a GET request to /{spreadsheet_id}/values/{sheet_name}!{column_id}:{column_id} +/// 4. Check results. +#[ tokio::test ] +async fn test_mock_get_column_should_work() +{ + let spreadsheet_id = "12345"; + let sheet_name = "tab2"; + let column_id = "A"; + + let mock_response_values = vec![ vec![ json!( "Value1" ), json!( "Value2" ), json!( "Value3" ) ] ]; + + // 1. Start a mock server. + let server = MockServer::start(); + let mock = server.mock( | when, then | + { + when.method( GET ) + .path( "/12345/values/tab2!A:A" ) + .query_param( "majorDimension", "COLUMNS" ) + .query_param( "valueRenderOption", "UNFORMATTED_VALUE" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj + ( + &ValueRange + { + range : Some( "tab2!A:A".to_string() ), + major_dimension : Some( Dimension::Column ), + values : Some( mock_response_values.clone() ), + } + ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Call `get_column`. + let column = get_column( &client, spreadsheet_id, sheet_name, column_id ) + .await + .expect( "get_column failed." ); + + // 4. Check results. + mock.assert(); + + assert_eq!( column.len(), 3 ); + assert_eq!( column[0], json!( "Value1" ) ); + assert_eq!( column[1], json!( "Value2" ) ); + assert_eq!( column[2], json!( "Value3" ) ); +} + + +/// # What +/// We test retrieving a column when no data exists for the given column ID. +/// +/// # How +/// 1. Start a mock server. +/// 2. Create a client. +/// 3. Call `get_column` function which sends a GET request to /{spreadsheet_id}/values/{sheet_name}!{column_id}:{column_id} +/// 4. Check results (an empty array is returned). +#[ tokio::test ] +async fn test_mock_get_empty_column_should_work() +{ + let spreadsheet_id = "12345"; + let sheet_name = "tab2"; + let column_id = "Z"; + let response_body = ValueRange + { + range : Some( "tab2!Z:Z".to_string() ), + major_dimension : Some( Dimension::Column ), + ..Default::default() + }; + + // 1. Start a mock server. + let server = MockServer::start(); + let mock = server.mock( | when, then | + { + when.method( GET ) + .path( "/12345/values/tab2!Z:Z" ) + .query_param( "majorDimension", "COLUMNS" ) + .query_param( "valueRenderOption", "UNFORMATTED_VALUE" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj( &response_body ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Call `get_column`. + let column = get_column( &client, spreadsheet_id, sheet_name, column_id ) + .await + .expect( "get_column failed." ); + + // 4. Check results. + mock.assert(); + + assert_eq!( column.len(), 0 ); +} + + +/// # What +/// We test error handling if the server responds with an error. +/// +/// # How +/// 1. Start a mock server. +/// 2. Create a client. +/// 3. Call `get_column` with a column ID that triggers an error. +/// 4. We expect a panic (since the function returns an error and we `.expect()`). +#[ tokio::test ] +#[ should_panic ] +async fn test_mock_get_column_with_error_should_panic() +{ + let spreadsheet_id = "12345"; + let sheet_name = "tab2"; + let column_id = "INVALID"; + + // 1. Start a mock server. + let server = MockServer::start(); + let _mock = server.mock( | when, then | + { + when.method( GET ) + .path( "/12345/values/tab2!INVALID:INVALID" ) + .query_param( "majorDimension", "COLUMNS" ) + .query_param( "valueRenderOption", "UNFORMATTED_VALUE" ); + then.status( 400 ) + .header( "Content-Type", "application/json" ) + .body( r#"{ "error": { "message": "Invalid column ID" } }"# ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Call `get_column`. + let column = get_column( &client, spreadsheet_id, sheet_name, column_id ) + .await + .expect( "get_column failed. Ok!" ); + + println!( "{:?}", column ); +} diff --git a/module/move/gspread/tests/mock/get_header.rs b/module/move/gspread/tests/mock/get_header.rs new file mode 100644 index 0000000000..1d611cd1e5 --- /dev/null +++ b/module/move/gspread/tests/mock/get_header.rs @@ -0,0 +1,194 @@ +//! +//! Tests for `get_header()` function. +//! It can return only one of the common errors. +//! + +use gspread::gcore::ApplicationSecret; +use httpmock::prelude::*; + +use serde_json::json; +use gspread::actions::gspread::get_header; +use gspread::gcore::client:: +{ + Client, + Dimension, + ValueRange +}; + +/// # What +/// We check that requesting the header row (first row) of a sheet in a Google Spreadsheet +/// returns the correct set of column values. +/// +/// It works: +/// - With the whole header, +/// - With empty columns between columns, +/// - With empty column at the start. +/// +/// # How +/// 1. Start a mock server. +/// 2. Create a client. +/// 3. Call `get_header()` function wich sends a GET request to /{spreadshett_id}/values/{range}. +/// 4. Check results. +#[ tokio::test ] +async fn test_mock_get_header_should_work() +{ + let spreadsheet_id = "12345"; + let body = ValueRange + { + major_dimension : Some( Dimension::Row ), + range : Some( "tab2!A1:ZZZ1".to_string() ), + values : Some( vec![ vec![ json!( "ID" ), json!( "Name" ), json!( "Email" ) ] ] ) + }; + + // 1. Start a mock server. + let server = MockServer::start(); + let mock = server.mock( | when, then | { + when.method( GET ) + .path( "/12345/values/tab2!A1:ZZZ1" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj( &body ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Send a GET request + let header = get_header( &client, spreadsheet_id, "tab2" ) + .await + .expect( "get_header failed" ); + + // 4. Check results. + mock.assert(); + + assert_eq!( header.len(), 3, "Header row should have 3 columns" ); + + assert_eq!( header[0], serde_json::Value::String( "ID".to_string() ) ); + assert_eq!( header[1], serde_json::Value::String( "Name".to_string() ) ); + assert_eq!( header[2], serde_json::Value::String( "Email".to_string() ) ); +} + +#[ tokio::test ] +async fn test_mock_get_header_with_empty_column_betwee_columns_should_work() +{ + let spreadsheet_id = "12345"; + let body = ValueRange + { + major_dimension : Some( Dimension::Row ), + range : Some( "tab2!A1:ZZZ1".to_string() ), + values : Some( vec![ vec![ json!( "ID" ), json!( "" ), json!( "Email" ) ] ] ) + }; + + // 1. Start a mock server. + let server = MockServer::start(); + let mock = server.mock( | when, then | { + when.method( GET ) + .path( "/12345/values/tab2!A1:ZZZ1" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj( &body ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Send a GET request + let header = get_header( &client, spreadsheet_id, "tab2" ) + .await + .expect( "get_header failed" ); + + // 4. Check results. + mock.assert(); + + assert_eq!( header.len(), 3, "Header row should have 3 columns" ); + + assert_eq!( header[0], serde_json::Value::String( "ID".to_string() ) ); + assert_eq!( header[1], serde_json::Value::String( "".to_string() ) ); + assert_eq!( header[2], serde_json::Value::String( "Email".to_string() ) ); +} + +#[ tokio::test ] +async fn test_mock_get_header_with_empty_first_column_should_work() +{ + let spreadsheet_id = "12345"; + let body = ValueRange + { + major_dimension : Some( Dimension::Row ), + range : Some( "tab2!A1:ZZZ1".to_string() ), + values : Some( vec![ vec![ json!( "" ), json!( "Name" ), json!( "Email" ) ] ] ) + }; + + // 1. Start a mock server. + let server = MockServer::start(); + let mock = server.mock( | when, then | { + when.method( GET ) + .path( "/12345/values/tab2!A1:ZZZ1" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj( &body ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Send a GET request + let header = get_header( &client, spreadsheet_id, "tab2" ) + .await + .expect( "get_header failed" ); + + // 4. Check results. + mock.assert(); + + assert_eq!( header.len(), 3, "Header row should have 3 columns" ); + + assert_eq!( header[0], serde_json::Value::String( "".to_string() ) ); + assert_eq!( header[1], serde_json::Value::String( "Name".to_string() ) ); + assert_eq!( header[2], serde_json::Value::String( "Email".to_string() ) ); +} + +#[ tokio::test ] +async fn test_mock_get_header_with_empty_column_columns_should_work() +{ + let spreadsheet_id = "12345"; + let body = ValueRange + { + major_dimension : Some( Dimension::Row ), + range : Some( "tab2!A1:ZZZ1".to_string() ), + values : Some( vec![ vec![] ] ) + }; + + // 1. Start a mock server. + let server = MockServer::start(); + let mock = server.mock( | when, then | { + when.method( GET ) + .path( "/12345/values/tab2!A1:ZZZ1" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj( &body ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Send a GET request + let header = get_header( &client, spreadsheet_id, "tab2" ) + .await + .expect( "get_header failed" ); + + // 4. Check results. + mock.assert(); + + assert_eq!( header.len(), 0, "Header row should have 0 columns" ); +} \ No newline at end of file diff --git a/module/move/gspread/tests/mock/get_row.rs b/module/move/gspread/tests/mock/get_row.rs new file mode 100644 index 0000000000..dd2c01dbf0 --- /dev/null +++ b/module/move/gspread/tests/mock/get_row.rs @@ -0,0 +1,162 @@ +//! +//! Tests for `get_row` function. +//! + +use httpmock::prelude::*; +use serde_json::json; +use gspread::*; +use actions::gspread::get_row; +use gcore::ApplicationSecret; +use gcore::client:: +{ + Client, + ValueRange +}; + + +/// # What +/// We test retrieving a single row from a sheet by its key. +/// +/// # How +/// 1. Start a mock server. +/// 2. Create a client. +/// 3. Call `get_row` function which sends a GET request to /{spreadsheet_id}/values/{sheet_name}!A{row_key}:ZZZ{row_key} +/// 4. Check results. +#[ tokio::test ] +async fn test_mock_get_row_should_work() +{ + let spreadsheet_id = "12345"; + let sheet_name = "tab2"; + let row_key = json!( 10 ); + + let mock_response_values = vec![ vec![ json!( "Hello" ), json!( "World" ) ] ]; + + // 1. Start a mock server. + let server = MockServer::start(); + let mock = server.mock( | when, then | + { + when.method( GET ) + .path( "/12345/values/tab2!A10:ZZZ10" ) + .query_param( "valueRenderOption", "UNFORMATTED_VALUE" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj + ( + &ValueRange + { + range : Some( "tab2!A10:ZZZ10".to_string() ), + major_dimension : None, + values : Some( mock_response_values.clone() ), + } + ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Call `get_row`. + let row = get_row( &client, spreadsheet_id, sheet_name, row_key ) + .await + .expect( "get_row failed." ); + + // 4. Check results. + mock.assert(); + + assert_eq!( row.len(), 2 ); + assert_eq!( row[ 0 ], json!( "Hello" ) ); + assert_eq!( row[ 1 ], json!( "World" ) ); +} + + +/// # What +/// We test retrieving a row when no data exists for the given row key. +/// +/// # How +/// 1. Start a mock server. +/// 2. Create a client. +/// 3. Call `get_row` function which sends a GET request to /{spreadsheet_id}/values/{sheet_name}!A999:ZZZ999 +/// 4. Check results. +#[ tokio::test ] +async fn test_mock_get_row_no_data_should_work() +{ + let spreadsheet_id = "12345"; + let sheet_name = "tab2"; + let row_key = json!( 999 ); + let response_body = ValueRange + { + range : Some( "tab2!A999:ZZZ999".to_string() ), + ..Default::default() + }; + + // 1. Start a mock server. + let server = MockServer::start(); + let mock = server.mock( | when, then | + { + when.method( GET ) + .path( "/12345/values/tab2!A999:ZZZ999" ) + .query_param( "valueRenderOption", "UNFORMATTED_VALUE" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj( &response_body ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Call `get_row`. + let row = get_row( &client, spreadsheet_id, sheet_name, row_key ) + .await + .expect( "get_row failed." ); + + // 4. Check results. + mock.assert(); + + assert_eq!( row.len(), 0 ); +} + + +/// # What +/// We test error handling if the server responds with an error. +/// +/// # How +/// 1. Start a mock server. +/// 2. Create a client. +/// 3. Call `get_row` with a row key that triggers an error (e.g. row key out of range). +/// 4. We expect a panic (since the function returns an error and we `.expect()`). +#[ tokio::test ] +#[ should_panic ] +async fn test_mock_get_row_with_error_should_panic() +{ + let spreadsheet_id = "12345"; + let sheet_name = "tab2"; + let row_key = json!( "bad_key" ); + + // 1. Start a mock server. + let server = MockServer::start(); + let _mock = server.mock( | when, then | + { + when.method( GET ) + .path( "/12345/values/tab2!Abad_key:ZZZbad_key" ) + .query_param( "valueRenderOption", "UNFORMATTED_VALUE" ); + then.status( 400 ) + .header( "Content-Type", "application/json" ) + .body( r#"{ "error": { "message": "Invalid row key" } }"# ); + } ); + + let endpoint = server.url( "" ); + let client: Client<'_, ApplicationSecret> = Client::former() + .endpoint( &*endpoint ) + .form(); + + let row = get_row( &client, spreadsheet_id, sheet_name, row_key ) + .await + .expect( "get_row failed. Ok!" ); + + println!( "{:?}", row ); +} diff --git a/module/move/gspread/tests/mock/get_row_custom.rs b/module/move/gspread/tests/mock/get_row_custom.rs new file mode 100644 index 0000000000..428b1e41dd --- /dev/null +++ b/module/move/gspread/tests/mock/get_row_custom.rs @@ -0,0 +1,176 @@ +//! +//! Tests for `get_row_by_custom_row_key`. +//!. + +use httpmock::prelude::*; +use serde_json::json; +use gspread::*; +use actions::gspread:: +{ + get_row_by_custom_row_key, + OnFind +}; +use gcore:: +{ + client::Client, + ApplicationSecret +}; + +/// # What +/// This test checks that `get_row_by_custom_row_key` returns an empty vector +/// when the specified key value does not exist in the given column. +/// +/// # How +/// 1. Start a mock server. +/// 2. Create a `Client` pointing to that mock server. +/// 3. Mock a `GET` request to return no matching values in the desired column. +/// 4. Mock the `values:batchGet` request but expect it to be called **0 times**. +/// 5. Call `get_row_by_custom_row_key`. +/// 6. Assert that an empty `Vec` is returned, and `batchGet` was never triggered. +#[ tokio::test ] +async fn test_mock_get_row_by_custom_row_key_no_matches() +{ + let server = MockServer::start(); + let spreadsheet_id = "12345"; + + let get_mock = server.mock( | when, then | { + when.method( GET ) + .path( "/12345/values/tab1!E:E" ); + then.status( 200 ) + .header( "Content-Type", "application/json") + .json_body + ( + json! + ( + { + "range" : "tab1!E:E", + "majorDimension" : "COLUMNS", + "values" : [ [ "111", "111", "111" ] ] + } + ) + ); + } ); + + let batch_get_mock = server.mock( | when, then | { + when.method( GET ) + .path( "/12345/values:batchGet" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body + ( + json! + ( + { + "spreadsheetId" : "12345", + "valueRanges" : [] + } + ) + ); + } ); + + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + let fetched_rows = get_row_by_custom_row_key + ( + &client, + spreadsheet_id, + "tab1", + ( "E", json!( "targetVal" ) ), + OnFind::AllMatchedRow, + ) + .await + .expect( "get_row_by_custom_row_key failed" ); + + assert!( fetched_rows.is_empty(), "Expected no matched rows." ); + + get_mock.assert(); + batch_get_mock.assert(); +} + + +/// # What +/// This test checks `get_row_by_custom_row_key` when multiple rows match the key, +/// but we only want the **last** matched row (`OnFind::LastMatchedRow`). +/// +/// # How +/// 1. Start a mock server. +/// 2. Create a `Client`. +/// 3. Mock the GET request, simulating multiple matches. +/// 4. Mock the batchGet request for the last matching row (say row 5). +/// 5. Call `get_row_by_custom_row_key` with `OnFind::LastMatchedRow`. +/// 6. Verify only row #5's data is returned. +#[ tokio::test ] +async fn test_mock_get_row_by_custom_row_key_multiple_matches_last() +{ + let server = MockServer::start(); + let spreadsheet_id = "12345"; + + let get_mock = server.mock( | when, then | { + when.method( GET ) + .path( "/12345/values/tab1!E:E" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body + ( + json! + ( + { + "range" : "tab1!E:E", + "majorDimension" : "COLUMNS", + "values" : [ [ "foo", "targetVal", "bar", "targetVal" ] ] + } + ) + ); + } ); + + let batch_get_mock = server.mock( | when, then | { + when.method( GET ) + .path( "/12345/values:batchGet" ) + .query_param( "ranges", "tab1!A4:ZZZ4" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body + ( + json! + ( + { + "spreadsheetId" : "12345", + "valueRanges" : [ + { + "range" : "tab1!A4:ZZZ4", + "majorDimension" : "ROWS", + "values" : [ [ "Charlie", "X", "targetVal" ] ] + } + ] + } + ) + ); + } ); + + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + let fetched_rows = get_row_by_custom_row_key + ( + &client, + spreadsheet_id, + "tab1", + ( "E", json!( "targetVal" ) ), + OnFind::LastMatchedRow, + ) + .await + .expect( "get_row_by_custom_row_key failed" ); + + assert_eq!( fetched_rows.len(), 1 ); + assert_eq!( fetched_rows[ 0 ].len(), 3 ); + assert_eq!( fetched_rows[ 0 ][ 0 ], json!( "Charlie" ) ); + assert_eq!( fetched_rows[ 0 ][ 2 ], json!( "targetVal" ) ); + + get_mock.assert(); + batch_get_mock.assert(); +} diff --git a/module/move/gspread/tests/mock/get_rows.rs b/module/move/gspread/tests/mock/get_rows.rs new file mode 100644 index 0000000000..b212a1ebc4 --- /dev/null +++ b/module/move/gspread/tests/mock/get_rows.rs @@ -0,0 +1,229 @@ +//! +//! Tests for `get_rows` function. +//! + +use gspread::gcore::ApplicationSecret; +use httpmock::prelude::*; + +use serde_json::json; +use gspread::actions::gspread::get_rows; +use gspread::gcore::client:: +{ + Client, + Dimension, + ValueRange +}; + +/// # What +/// We check that requesting all rows from the second row onward (below the header) +/// correctly parses the response and returns the expected result. +/// +/// It works: +/// - With the whole rows. +/// - With rows with empty columns. +/// - With empty rows in the middle. +/// +/// # How +/// 1. Start a mock server. +/// 2. Create a client. +/// 3. Call `get_rows` which sends a GET request to "/{spreadsheet_id}/values/{range}". +/// 4. Check results. +#[ tokio::test ] +async fn test_mock_get_rows_should_work() +{ + let spreadsheet_id = "12345"; + let body = ValueRange + { + major_dimension : Some( Dimension::Row ), + range : Some( "tab2!A2:ZZZ".to_string() ), + values : Some + ( + vec! + [ + vec![ json!( "Row2Col1" ), json!( "Row2Col2" ) ], + vec![ json!( "Row3Col1" ), json!( "Row3Col2" ) ] + ] + ) + }; + + // 1. Start a mock server. + let server = MockServer::start(); + let mock = server.mock( | when, then | { + when.method( GET ) + .path( "/12345/values/tab2!A2:ZZZ" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj( &body ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Call `get_rows` + let rows = get_rows( &client, spreadsheet_id, "tab2" ) + .await + .expect( "get_rows failed" ); + + // 4. Check results. + mock.assert(); + + assert_eq!( rows.len(), 2 ); + assert_eq!( rows[ 0 ].len(), 2 ); + assert_eq!( rows[ 0 ][ 0 ], json!( "Row2Col1" ) ); + assert_eq!( rows[ 0 ][ 1 ], json!( "Row2Col2" ) ); + + assert_eq!( rows[ 1 ].len(), 2 ); + assert_eq!( rows[ 1 ][ 0 ], json!( "Row3Col1" ) ); + assert_eq!( rows[ 1 ][ 1 ], json!( "Row3Col2" ) ); +} + +#[ tokio::test ] +async fn test_mock_get_rows_with_empty_columns() +{ + let spreadsheet_id = "12345"; + let body = ValueRange + { + major_dimension : Some( Dimension::Row ), + range : Some( "tab2!A2:ZZZ".to_string() ), + values : Some + ( + vec! + [ + vec![ json!( "Row2Col1" ), json!( "" ), json!( "Row2Col3" ) ], + vec![ json!( "Row3Col1" ), json!( "" ), json!( "Row3Col3" ) ] + ] + ) + }; + + // 1. Start a mock server. + let server = MockServer::start(); + let mock = server.mock( | when, then | { + when.method( GET ) + .path( "/12345/values/tab2!A2:ZZZ" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj( &body ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Call `get_rows` + let rows = get_rows( &client, spreadsheet_id, "tab2" ) + .await + .expect( "get_rows failed" ); + + // 4. Check results. + mock.assert(); + + assert_eq!( rows.len(), 2 ); + assert_eq!( rows[ 0 ].len(), 3 ); + assert_eq!( rows[ 0 ][ 0 ], json!( "Row2Col1" ) ); + assert_eq!( rows[ 0 ][ 1 ], json!( "" ) ); + assert_eq!( rows[ 0 ][ 2 ], json!( "Row2Col3" ) ); + + assert_eq!( rows[ 1 ].len(), 3 ); + assert_eq!( rows[ 1 ][ 0 ], json!( "Row3Col1" ) ); + assert_eq!( rows[ 1 ][ 1 ], json!( "" ) ); + assert_eq!( rows[ 1 ][ 2 ], json!( "Row3Col3" ) ); +} + +#[ tokio::test ] +async fn test_mock_get_rows_with_empty_row_in_the_middle() +{ + let spreadsheet_id = "12345"; + let body = ValueRange + { + major_dimension : Some( Dimension::Row ), + range : Some( "tab2!A2:ZZZ".to_string() ), + values : Some + ( + vec! + [ + vec![ json!( "Row2Col1" ), json!( "Row2Col2" ), json!( "Row2Col3" ) ], + vec![ json!( "" ), json!( "" ), json!( "" ) ], + vec![ json!( "Row3Col1" ), json!( "Row3Col2" ), json!( "Row3Col3" ) ], + ] + ) + }; + + // 1. Start a mock server. + let server = MockServer::start(); + let mock = server.mock( | when, then | { + when.method( GET ) + .path( "/12345/values/tab2!A2:ZZZ" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj( &body ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Call `get_rows` + let rows = get_rows( &client, spreadsheet_id, "tab2" ) + .await + .expect( "get_rows failed" ); + + // 4. Check results. + mock.assert(); + + assert_eq!( rows.len(), 3 ); + assert_eq!( rows[ 0 ].len(), 3 ); + assert_eq!( rows[ 0 ][ 0 ], json!( "Row2Col1" ) ); + assert_eq!( rows[ 0 ][ 1 ], json!( "Row2Col2" ) ); + assert_eq!( rows[ 0 ][ 2 ], json!( "Row2Col3" ) ); + + assert_eq!( rows[ 1 ].len(), 3 ); + assert_eq!( rows[ 1 ][ 0 ], json!( "" ) ); + assert_eq!( rows[ 1 ][ 1 ], json!( "" ) ); + assert_eq!( rows[ 1 ][ 2 ], json!( "" ) ); + + assert_eq!( rows[ 2 ].len(), 3 ); + assert_eq!( rows[ 2 ][ 0 ], json!( "Row3Col1" ) ); + assert_eq!( rows[ 2 ][ 1 ], json!( "Row3Col2" ) ); + assert_eq!( rows[ 2 ][ 2 ], json!( "Row3Col3" ) ); +} + +#[ tokio::test ] +async fn test_mock_get_rows_empty_should_work() +{ + let spreadsheet_id = "12345"; + let body = ValueRange + { + major_dimension : Some( Dimension::Row ), + range : Some( "tab2!A2:ZZZ".to_string() ), + values : Some( vec![] ) + }; + + // 1. Start a mock server. + let server = MockServer::start(); + let _mock = server.mock( | when, then | { + when.method( GET ) + .path( "/12345/values/tab2!A2:ZZZ" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj( &body ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + let rows = get_rows( &client, spreadsheet_id, "tab2" ) + .await + .expect( "get_rows failed" ); + + assert_eq!( rows.len(), 0 ); +} \ No newline at end of file diff --git a/module/move/gspread/tests/mock/mod.rs b/module/move/gspread/tests/mock/mod.rs new file mode 100644 index 0000000000..acc0d52cc8 --- /dev/null +++ b/module/move/gspread/tests/mock/mod.rs @@ -0,0 +1,17 @@ +#[ allow( unused_imports ) ] +use super::*; + +mod common_tests; +mod get_header; +mod get_row; +mod get_rows; +mod get_row_custom; +mod append_row; +mod get_cell; +mod set_cell; +mod update_row; +mod update_rows_by_custom_row_key; +mod get_column; +mod clear; +mod clear_by_custom_row_key; +mod copy_to; \ No newline at end of file diff --git a/module/move/gspread/tests/mock/set_cell.rs b/module/move/gspread/tests/mock/set_cell.rs new file mode 100644 index 0000000000..544a76f5f0 --- /dev/null +++ b/module/move/gspread/tests/mock/set_cell.rs @@ -0,0 +1,128 @@ +//! +//! Tests for `set_cell` function. +//! + +use gspread::gcore::ApplicationSecret; +use httpmock::prelude::*; + +use serde_json::json; +use gspread::actions::gspread::set_cell; +use gspread::gcore::client:: +{ + Client, + Dimension, + ValueRange, + UpdateValuesResponse +}; + +/// # What +/// We check that setting a value in a specific cell of a Google Spreadsheet works correctly. +/// +/// # How +/// 1. Start a mock server. +/// 2. Create a client. +/// 3. Send a PUT request to /{spreadsheet_id}/values/{range}?valueInputOption=RAW. +/// 4. Check results. +#[ tokio::test ] +async fn test_mock_set_cell_should_work() +{ + // 1. Start a mock server. + let spreadsheet_id = "12345"; + let range = "tab2!A1"; + let value_range = ValueRange + { + major_dimension : Some( Dimension::Row ), + range : Some( range.to_string() ), + values : Some( vec![ vec![ json!( "Val" ) ] ] ) + }; + + let response_body = UpdateValuesResponse + { + spreadsheet_id : Some( spreadsheet_id.to_string() ), + updated_cells : Some( 1 ), + updated_columns : Some( 1 ), + updated_range : Some( range.to_string() ), + updated_rows : Some( 1 ), + updated_data : Some( value_range ) + }; + + let server = MockServer::start(); + + let mock = server.mock( | when, then | { + when.method( PUT ) + .path( "/12345/values/tab2!A1" ) + .query_param( "valueInputOption", "RAW" ); + then + .status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj( &response_body ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Send a PUT request. + let result = set_cell + ( + &client, + spreadsheet_id, + "tab2", + "A1", + json!( "Val" ) + ) + .await + .expect( "set_cell failed with mock" ); + + // 4. Check results. + mock.assert(); + + assert_eq!( result.spreadsheet_id.as_deref(), Some( spreadsheet_id ) ); + assert_eq!( result.updated_range.as_deref(), Some( range ) ); + assert_eq!( result.updated_rows, Some( 1 ) ); + assert_eq!( result.updated_columns, Some( 1 ) ); + assert_eq!( result.updated_cells, Some( 1 ) ); + + if let Some( updated_data ) = &result.updated_data + { + let values = updated_data.values.as_ref().unwrap(); + assert_eq!( values, &vec![ vec![ json!( "Val" ) ] ] ); + } +} + +/// # What +/// We test that `set_cell` function will return error with bad cell_id. +/// +/// # How +/// 1. Start a mock server. +/// 2. Send a PUT request to /{spreadsheet_id}/values/{range}?valueInputOption=RAW. +#[ tokio::test ] +#[ should_panic ] +async fn test_mock_set_cell_bad_cell_id_should_panic() +{ + // 1. Start a mock server. + let server = MockServer::start(); + let _mock = server.mock( | when, then | { + when.method( PUT ) + .path( "/12345/values/tab2!AAAA1" ) + .query_param( "valueInputOption", "RAW" ); + then + .status( 400 ) + .header( "Content-Type", "application/json" ) + .body( r#"{ error: invalid range. }"# ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Send a PUT request. + let _result = set_cell( &client, "12345", "tab2", "A1", json!( "Val" ) ) + .await + .expect( "set_cell failed with mock. Ok." ); +} \ No newline at end of file diff --git a/module/move/gspread/tests/mock/update_row.rs b/module/move/gspread/tests/mock/update_row.rs new file mode 100644 index 0000000000..ab8c6cbedc --- /dev/null +++ b/module/move/gspread/tests/mock/update_row.rs @@ -0,0 +1,238 @@ +//! +//! Tests for `update_row` function. +//! + +use httpmock::prelude::*; + +use serde_json::json; +use gspread::*; +use actions::gspread::update_row; +use gcore::ApplicationSecret; +use gcore::client:: +{ + BatchUpdateValuesResponse, + Client, + Dimension, + ValueRange +}; + +/// # What +/// We check that updating a row in a Google Spreadsheet returns the correct response. +/// +/// # How +/// 1. Start a mock server. +/// 2. Create a client. +/// 3. Call `update_row()`, passing the necessary parameters. +/// 4. Check results. +#[ tokio::test ] +async fn test_mock_update_row_should_work() +{ + let spreadsheet_id = "12345"; + let value_ranges = vec! + [ + ValueRange + { + major_dimension : Some( Dimension::Row ), + range : Some( "tab2!A5".to_string() ), + values : Some( vec![ vec![ json!( "Hello" ) ] ] ) + }, + + ValueRange + { + major_dimension : Some( Dimension::Row ), + range : Some( "tab2!A7".to_string() ), + values : Some( vec![ vec![ json!( 123 ) ] ] ) + }, + ]; + + let response_body = BatchUpdateValuesResponse + { + spreadsheet_id : Some( spreadsheet_id.to_string() ), + total_updated_rows : Some( 2 ), + total_updated_columns : Some( 1 ), + total_updated_cells : Some( 2 ), + total_updated_sheets : Some( 1 ), + responses : Some( value_ranges ) + }; + + // 1. Start a mock server. + let server = MockServer::start(); + let mock = server.mock( | when, then | { + when.method( POST ) + .path( "/12345/values:batchUpdate" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj( &response_body ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Call `update_row` function. + let mut row_key_val = std::collections::HashMap::new(); + row_key_val.insert( "A".to_string(), json!( "Hello" ) ); + row_key_val.insert( "B".to_string(), json!( 123 ) ); + + let batch_result = update_row + ( + &client, + spreadsheet_id, + "tab2", + json!( "5" ), + row_key_val + ) + .await + .expect( "update_row failed in mock test" ); + + // 4. Check results. + mock.assert(); + + assert_eq!( batch_result.spreadsheet_id.as_deref(), Some( spreadsheet_id ) ); + assert_eq!( batch_result.total_updated_cells, Some( 2 ) ); + assert_eq!( batch_result.total_updated_rows, Some( 2 ) ); + assert_eq!( batch_result.total_updated_columns, Some( 1 ) ); + + if let Some( responses ) = &batch_result.responses + { + assert_eq!( responses.len(), 2 ); + } +} + +#[ tokio::test ] +async fn test_mock_update_row_with_empty_values_should_work() +{ + let spreadsheet_id = "12345"; + let response_body = BatchUpdateValuesResponse + { + spreadsheet_id : Some( spreadsheet_id.to_string() ), + total_updated_rows : None, + total_updated_columns : None, + total_updated_cells : None, + total_updated_sheets : None, + responses : None + }; + + // 1. Start a mock server. + let server = MockServer::start(); + let mock = server.mock( | when, then | { + when.method( POST ) + .path( "/12345/values:batchUpdate" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj( &response_body ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Call `update_row` function. + let row_key_val = std::collections::HashMap::new(); + + let batch_result = update_row + ( + &client, + spreadsheet_id, + "tab2", + json!( "5" ), + row_key_val + ) + .await + .expect( "update_row failed in mock test" ); + + // 4. Check results. + mock.assert(); + + assert_eq!( batch_result.spreadsheet_id.as_deref(), Some( spreadsheet_id ) ); + assert_eq!( batch_result.total_updated_cells, None ); + assert_eq!( batch_result.total_updated_rows, None ); + assert_eq!( batch_result.total_updated_columns, None ); +} + +/// # What +/// We test that function will return an error if invalid paramentrs were passed. +/// +/// # How +/// 1. Start a mock server. +/// 2. Create a client. +/// 3. Call `update_row` which sends a POST request to /{spreadsheet_id}/values:batchUpdate +#[ tokio::test ] +#[ should_panic ] +async fn test_mock_update_row_with_invalid_row_key_should_panic() +{ + // 1. Start a mock server. + let server = MockServer::start(); + let _mock = server.mock( | when, then | { + when.method( POST ) + .path( "/12345/values:batchUpdate" ); + then.status( 400 ) + .header( "Content-Type", "application/json" ) + .body( "{ error: invalid row_key }" ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Call `update_row` function. + let mut row_key_val = std::collections::HashMap::new(); + row_key_val.insert( "A".to_string(), json!( "Hello" ) ); + row_key_val.insert( "B".to_string(), json!( 123 ) ); + + let _batch_result = update_row + ( + &client, + "12345", + "tab2", + json!( "Invalid row_key" ), + row_key_val + ) + .await + .expect( "update_row failed in mock test. Ok!" ); +} + +#[ tokio::test ] +#[ should_panic ] +async fn test_mock_update_row_with_invalid_row_key_val_should_panic() +{ + // 1. Start a mock server. + let server = MockServer::start(); + let _mock = server.mock( | when, then | { + when.method( POST ) + .path( "/12345/values:batchUpdate" ); + then.status( 400 ) + .header( "Content-Type", "application/json" ) + .body( "{ error: invalid column index }" ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Call `update_row` function. + let mut row_key_val = std::collections::HashMap::new(); + // It is invalid. Allowed range: A -> ZZZ + row_key_val.insert( "AAAAAA".to_string(), json!( "Hello" ) ); + // It is also ionvalid + row_key_val.insert( "12".to_string(), json!( 123 ) ); + + let _batch_result = update_row + ( + &client, + "12345", + "tab2", + json!( "Invalid row_key" ), + row_key_val + ) + .await + .expect( "update_row failed in mock test. Ok!" ); +} \ No newline at end of file diff --git a/module/move/gspread/tests/mock/update_rows_by_custom_row_key.rs b/module/move/gspread/tests/mock/update_rows_by_custom_row_key.rs new file mode 100644 index 0000000000..2f3e4bf93a --- /dev/null +++ b/module/move/gspread/tests/mock/update_rows_by_custom_row_key.rs @@ -0,0 +1,580 @@ +//! +//! Tests to update +//! + +use httpmock::prelude::*; +use serde_json::json; +use gspread::*; +use actions::gspread:: +{ + update_rows_by_custom_row_key, + OnFail, + OnFind +}; +use gcore::ApplicationSecret; +use gcore::client:: +{ + BatchUpdateValuesResponse, + Client, + Dimension, + ValueRange +}; + + +/// # What +/// We check that updating rows in a Google Spreadsheet returns the correct response. +/// +/// # How +/// 1. Start a mock server. +/// 2. Create a client +/// 3. Call `update_rows_by_custom_row_key`. +/// 4. Check results. +#[ tokio::test ] +async fn test_mock_update_rows_by_custom_row_key_on_fail_nothing_should_work() +{ + // 1. Start a mock server. + let server = MockServer::start(); + let spreadsheet_id = "12345"; + + let get_mock = server.mock( | when, then | { + when.method( GET ) + .path( "/12345/values/tab1!E:E" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body + ( + json! + ( + { + "range" : "tab1!E:E", + "majorDimension" : "COLUMNS", + "values" : [ [ "12", "12", "12", "12" ] ] + } + ) + ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Call update_rows_by_custom_row_key. + let mut row_key_val = std::collections::HashMap::new(); + row_key_val.insert( "A".to_string(), json!( "Hello" ) ); + row_key_val.insert( "B".to_string(), json!( 123 ) ); + + let batch_result = update_rows_by_custom_row_key + ( + &client, + spreadsheet_id, + "tab1", + ( "E", json!( 122 ) ), + row_key_val, + OnFind::FirstMatchedRow, + OnFail::Nothing + ) + .await + .expect( "update_rows_by_custom_row_key failed" ); + + assert_eq!( batch_result.spreadsheet_id.as_deref(), None ); + assert_eq!( batch_result.total_updated_cells, None ); + assert_eq!( batch_result.total_updated_rows, None ); + assert_eq!( batch_result.total_updated_columns, None ); + + get_mock.assert(); +} + +/// # What +/// We check that updating rows in a Google Spreadsheet returns the correct response. +/// +/// # How +/// 1. Start a mock server. +/// 2. Create a client. +/// 3. Call `update_rows_by_custom_row_key`. +#[ tokio::test ] +#[ should_panic ] +async fn test_mock_update_rows_by_custom_row_key_on_fail_error_should_panic() +{ + // Start a mock server. + let server = MockServer::start(); + let spreadsheet_id = "12345"; + + let _get_mock = server.mock( | when, then | { + when.method( GET ) + .path( "/12345/values/tab1!E:E" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body + ( + json! + ( + { + "range" : "tab1!E:E", + "majorDimension" : "COLUMNS", + "values" : [ [ "12", "12", "12", "12" ] ] + } + ) + ); + } ); + + // 2. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 3. Call update_rows_by_custom_row_key + let mut row_key_val = std::collections::HashMap::new(); + row_key_val.insert( "A".to_string(), json!( "Hello" ) ); + row_key_val.insert( "B".to_string(), json!( 123 ) ); + + let _batch_result = update_rows_by_custom_row_key + ( + &client, + spreadsheet_id, + "tab1", + ( "E", json!( 122 ) ), + row_key_val, + OnFind::FirstMatchedRow, + OnFail::Error + ) + .await + .expect( "update_rows_by_custom_row_key failed" ); +} + +/// # What +/// We test that in case where we didn't find passed cell, OnFail::AppendRow in works correct. +/// +/// # How +/// 1. Start a mock server for getting our tabel. +/// 2. Start a mock server for adding a row. +/// 3. Create a client +/// 4. Call `update_rows_by_custom_row_key`. +/// 5. Check resaults. +#[ tokio::test ] +async fn test_mock_update_rows_by_custom_row_key_on_find_append_row_should_work() +{ + // 1. Start get_mock. + let server = MockServer::start(); + let spreadsheet_id = "12345"; + let body_batch_update = BatchUpdateValuesResponse + { + spreadsheet_id : Some( spreadsheet_id.to_string() ), + total_updated_rows : Some( 1 ), + total_updated_columns : Some( 7 ), + total_updated_cells : Some( 7 ), + total_updated_sheets : Some( 1 ), + responses : None, + }; + let body_values_append = json!({ + "updates": { + "updatedRange": "tab2!A5" + } + }); + + let get_mock = server.mock( | when, then | { + when.method( GET ) + .path( "/12345/values/tab1!E:E" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body + ( + json! + ( + { + "range" : "tab1!E:E", + "majorDimension" : "COLUMNS", + "values" : [ [ "12", "12", "12", "12" ] ] + } + ) + ); + } ); + + // 2. Start append_row_mock. + let append_row_mock = server.mock( | when, then | { + when.method( POST ) + .path( "/12345/values/tab1!A1:append" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj( &body_values_append ); + } ); + + let mock_batch_update = server.mock( | when, then | { + when.method( POST ) + .path( "/12345/values:batchUpdate" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj( &body_batch_update ); + } ); + + // 3. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 4. Call update_rows_by_custom_row_key. + let mut row_key_val = std::collections::HashMap::new(); + row_key_val.insert( "A".to_string(), json!( "Hello" ) ); + row_key_val.insert( "B".to_string(), json!( 123 ) ); + + let batch_result = update_rows_by_custom_row_key + ( + &client, + spreadsheet_id, + "tab1", + ( "E", json!( 122 ) ), + row_key_val, + OnFind::FirstMatchedRow, + OnFail::AppendRow + ) + .await + .expect( "update_rows_by_custom_row_key failed" ); + + // 5. Check results. + assert_eq!( batch_result.spreadsheet_id.as_deref(), Some( "12345" ) ); + assert_eq!( batch_result.total_updated_rows, Some( 1 ) ); + + get_mock.assert(); + append_row_mock.assert(); + mock_batch_update.assert(); +} + +/// # What +/// We test that in case where we didn't find passed cell, OnFail::AppendRow in works correct. +/// +/// # How +/// 1. Start a mock server for getting our tabel. +/// 2. Start a mock server for adding a row. +/// 3. Create a client +/// 4. Call `update_rows_by_custom_row_key`. +/// 5. Check resaults. +#[ tokio::test ] +async fn test_mock_update_rows_by_custom_row_key_on_find_update_first_row_should_work() +{ + // 1. Start get_mock. + let server = MockServer::start(); + let spreadsheet_id = "12345"; + + let get_mock = server.mock( | when, then | { + when.method( GET ) + .path( "/12345/values/tab1!E:E" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body + ( + json! + ( + { + "range" : "tab1!E:E", + "majorDimension" : "COLUMNS", + "values" : [ [ "12", "12", "12", "12" ] ] + } + ) + ); + } ); + + let mocked_value_ranges = vec! + [ + ValueRange + { + major_dimension : Some( Dimension::Row ), + range : Some( format!( "tab1!A2" ) ), + values : Some( vec![ vec![ json!( "Hello" ) ] ] ), + }, + + ValueRange + { + major_dimension : Some( Dimension::Row ), + range : Some( format!( "tab1!B2" ) ), + values : Some( vec![ vec![ json!( 123 ) ] ] ), + } + ]; + + let response_body = BatchUpdateValuesResponse + { + spreadsheet_id : Some( spreadsheet_id.to_string() ), + total_updated_rows : Some( 1 ), + total_updated_sheets : Some( 1 ), + total_updated_cells : Some( 2 ), + total_updated_columns : Some( 2 ), + responses : Some( mocked_value_ranges ) + }; + + // 2. Start update_mock. + let update_mock = server.mock( | when, then | { + when.method( POST ) + .path( "/12345/values:batchUpdate" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj( &response_body ); + } ); + + // 3. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 4. Call update_rows_by_custom_row_key. + let mut row_key_val = std::collections::HashMap::new(); + row_key_val.insert( "A".to_string(), json!( "Hello" ) ); + row_key_val.insert( "B".to_string(), json!( 123 ) ); + + let batch_result = update_rows_by_custom_row_key + ( + &client, + spreadsheet_id, + "tab1", + ( "E", json!( "12" ) ), + row_key_val, + OnFind::FirstMatchedRow, + OnFail::Error + ) + .await + .expect( "update_rows_by_custom_row_key failed" ); + + // 5. Check results. + assert_eq!( batch_result.spreadsheet_id.as_deref(), Some( "12345" ) ); + assert_eq!( batch_result.total_updated_cells, Some( 2 ) ); + assert_eq!( batch_result.total_updated_columns, Some( 2 ) ); + assert_eq!( batch_result.total_updated_rows, Some( 1 ) ); + assert_eq!( batch_result.total_updated_sheets, Some( 1 ) ); + + let responses = batch_result + .responses + .expect( "No responses found in BatchUpdateValuesResponse" ); + assert_eq!( responses.len(), 2 ); + + get_mock.assert(); + update_mock.assert(); +} + +/// # What +/// We test that in case where we didn't find passed cell, OnFail::UpdateAllMatchesRows in works correct. +/// +/// # How +/// 1. Start a mock server for getting our tabel. +/// 2. Start a mock server for update rows. +/// 3. Create a client +/// 4. Call `update_rows_by_custom_row_key`. +/// 5. Check resaults. +#[ tokio::test ] +async fn test_mock_update_rows_by_custom_row_key_on_find_update_all_rows_should_work() +{ + // 1. Start get_mock. + let server = MockServer::start(); + let spreadsheet_id = "12345"; + + let get_mock = server.mock( | when, then | { + when.method( GET ) + .path( "/12345/values/tab1!E:E" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body + ( + json! + ( + { + "range" : "tab1!E:E", + "majorDimension" : "COLUMNS", + "values" : [ [ "12", "12", "12", "12" ] ] + } + ) + ); + } ); + + let mut mocked_value_ranges = vec![]; + for i in 1..=4 + { + mocked_value_ranges.push + ( + ValueRange + { + major_dimension : Some( Dimension::Row ), + range : Some( format!( "tab1!A{}", i ) ), + values : Some( vec![ vec![ json!( "Hello" ) ] ] ), + } + ); + mocked_value_ranges.push + ( + ValueRange + { + major_dimension : Some( Dimension::Row ), + range : Some( format!( "tab1!B{}", i ) ), + values : Some( vec![ vec![ json!( 123 ) ] ] ), + } + ); + } + + let response_body = BatchUpdateValuesResponse + { + spreadsheet_id : Some( spreadsheet_id.to_string() ), + total_updated_rows : Some( 4 ), + total_updated_sheets : Some( 1 ), + total_updated_cells : Some( 8 ), + total_updated_columns : Some( 2 ), + responses : Some( mocked_value_ranges ) + }; + + // 2. Start update_mock. + let update_mock = server.mock( | when, then | { + when.method( POST ) + .path( "/12345/values:batchUpdate" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj( &response_body ); + } ); + + // 3. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 4. Call update_rows_by_custom_row_key. + let mut row_key_val = std::collections::HashMap::new(); + row_key_val.insert( "A".to_string(), json!( "Hello" ) ); + row_key_val.insert( "B".to_string(), json!( 123 ) ); + + let batch_result = update_rows_by_custom_row_key + ( + &client, + spreadsheet_id, + "tab1", + ( "E", json!( "12" ) ), + row_key_val, + OnFind::AllMatchedRow, + OnFail::Error + ) + .await + .expect( "update_rows_by_custom_row_key failed" ); + + println!( "{:?}", batch_result ); + + // 5. Check results. + assert_eq!( batch_result.spreadsheet_id.as_deref(), Some( "12345" ) ); + assert_eq!( batch_result.total_updated_cells, Some( 8 ) ); + assert_eq!( batch_result.total_updated_columns, Some( 2 ) ); + assert_eq!( batch_result.total_updated_rows, Some( 4 ) ); + assert_eq!( batch_result.total_updated_sheets, Some( 1 ) ); + + let responses = batch_result + .responses + .expect( "No responses found in BatchUpdateValuesResponse" ); + assert_eq!( responses.len(), 8 ); + + get_mock.assert(); + update_mock.assert(); +} + +/// # What +/// We test that in case where we find passed cell, OnFail::UpdateLastMatchedRow in works correct. +/// +/// # How +/// 1. Start a mock server for getting our tabel. +/// 2. Start a mock server for update a row. +/// 3. Create a client +/// 4. Call `update_rows_by_custom_row_key`. +/// 5. Check resaults. +#[ tokio::test ] +async fn test_mock_update_rows_by_custom_row_key_on_find_update_last_row_should_work() +{ + // 1. Start get_mock. + let server = MockServer::start(); + let spreadsheet_id = "12345"; + + let get_mock = server.mock( | when, then | { + when.method( GET ) + .path( "/12345/values/tab1!E:E" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body + ( + json! + ( + { + "range" : "tab1!E:E", + "majorDimension" : "COLUMNS", + "values" : [ [ "12", "12", "12", "12" ] ] + } + ) + ); + } ); + + let mocked_value_ranges = vec! + [ + ValueRange + { + major_dimension : Some( Dimension::Row ), + range : Some( format!( "tab1!A2" ) ), + values : Some( vec![ vec![ json!( "Hello" ) ] ] ), + }, + ValueRange + { + major_dimension : Some( Dimension::Row ), + range : Some( format!( "tab1!B2" ) ), + values : Some( vec![ vec![ json!( 123 ) ] ] ), + } + ]; + + let response_body = BatchUpdateValuesResponse + { + spreadsheet_id : Some( spreadsheet_id.to_string() ), + total_updated_rows : Some( 1 ), + total_updated_sheets : Some( 1 ), + total_updated_cells : Some( 2 ), + total_updated_columns : Some( 2 ), + responses : Some( mocked_value_ranges ) + }; + + // 2. Start update_mock. + let update_mock = server.mock( | when, then | { + when.method( POST ) + .path( "/12345/values:batchUpdate" ); + then.status( 200 ) + .header( "Content-Type", "application/json" ) + .json_body_obj( &response_body ); + } ); + + // 3. Create a client. + let endpoint = server.url( "" ); + let client : Client< '_, ApplicationSecret > = Client::former() + .endpoint( &*endpoint ) + .form(); + + // 4. Call update_rows_by_custom_row_key. + let mut row_key_val = std::collections::HashMap::new(); + row_key_val.insert( "A".to_string(), json!( "Hello" ) ); + row_key_val.insert( "B".to_string(), json!( 123 ) ); + + let batch_result = update_rows_by_custom_row_key + ( + &client, + spreadsheet_id, + "tab1", + ( "E", json!( "12" ) ), + row_key_val, + OnFind::LastMatchedRow, + OnFail::Error + ) + .await + .expect( "update_rows_by_custom_row_key failed" ); + + // 5. Check results. + assert_eq!( batch_result.spreadsheet_id.as_deref(), Some( "12345" ) ); + assert_eq!( batch_result.total_updated_rows, Some( 1 ) ); + assert_eq!( batch_result.total_updated_sheets, Some( 1 ) ); + assert_eq!( batch_result.total_updated_cells, Some( 2 ) ); + assert_eq!( batch_result.total_updated_columns, Some( 2 ) ); + + let responses = batch_result + .responses + .expect( "No responses found in BatchUpdateValuesResponse" ); + assert_eq!( responses.len(), 2); + + get_mock.assert(); + update_mock.assert(); +} \ No newline at end of file diff --git a/module/move/gspread/tests/smoke_test.rs b/module/move/gspread/tests/smoke_test.rs new file mode 100644 index 0000000000..28e533e551 --- /dev/null +++ b/module/move/gspread/tests/smoke_test.rs @@ -0,0 +1,11 @@ +#[ test ] +fn local_smoke_test() +{ + test_tools::smoke_test_for_local_run(); +} + +#[ test ] +fn published_smoke_test() +{ + test_tools::smoke_test_for_published_run(); +} \ No newline at end of file diff --git a/module/move/gspread/tests/tests.rs b/module/move/gspread/tests/tests.rs new file mode 100644 index 0000000000..48d25893a0 --- /dev/null +++ b/module/move/gspread/tests/tests.rs @@ -0,0 +1,7 @@ +#[ allow( unused_imports ) ] +use gspread as the_module; +#[ allow( unused_imports ) ] +use test_tools::exposed::*; + +#[ cfg( feature = "default" ) ] +mod mock; \ No newline at end of file diff --git a/module/move/optimization_tools/Cargo.toml b/module/move/optimization_tools/Cargo.toml index de8500b846..4a276984c9 100644 --- a/module/move/optimization_tools/Cargo.toml +++ b/module/move/optimization_tools/Cargo.toml @@ -17,8 +17,8 @@ categories = [ "algorithms", "development-tools" ] keywords = [ "fundamental", "general-purpose" ] # xxx : qqq : switch that on -# [lints] -# workspace = true +#[lints] +#workspace = true [package.metadata.docs.rs] features = [ "full" ] @@ -40,7 +40,9 @@ lp_parse = [ "dep:exmex" ] derive_tools = { workspace = true, features = [ "derive_more", "full", "strum" ] } deterministic_rand = { workspace = true, features = [ "default" ] } iter_tools = { workspace = true, features = [ "default" ] } -meta_tools = { workspace = true, features = [ "meta_constructors" ] } +# meta_tools = { workspace = true, features = [ "meta_constructors" ] } +meta_tools = { workspace = true, features = [] } +collection_tools = { workspace = true } # qqq : use intead of meta_tools error_tools = { workspace = true, features = ["default"] } env_logger = "0.10.1" log = "0.4.20" @@ -58,7 +60,7 @@ plotters = { version = "0.3.5", default-features = false, features = [ "bitmap_backend", ] } plotters-backend = { version = "0.3.5", optional = true } -piston_window = { version = "0.120.0", optional = true } +piston_window = { version = "0.132.0", optional = true } exmex = { version = "0.18.0", features = [ "partial" ], optional = true } rayon = "1.8.0" thiserror = "1.0.56" diff --git a/module/move/optimization_tools/License b/module/move/optimization_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/move/optimization_tools/License +++ b/module/move/optimization_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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 6d5ef8559f..72c80c1308 100644 --- a/module/move/plot_interface/License +++ b/module/move/plot_interface/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/move/plot_interface/Readme.md index 0e604acfb8..6102a14813 100644 --- a/module/move/plot_interface/Readme.md +++ b/module/move/plot_interface/Readme.md @@ -5,11 +5,12 @@ [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_plot_interface_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_plot_interface_push.yml) [![docs.rs](https://img.shields.io/docsrs/plot_interface?color=e3e8f0&logo=docs.rs)](https://docs.rs/plot_interface) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) -Plot interface. +**NOT ready for production** + + ```rust ``` @@ -27,4 +28,4 @@ git clone https://github.com/Wandalen/wTools cd wTools cd examples/plot_interface_trivial cargo run -``` +``` --> diff --git a/module/move/plot_interface/src/plot/abs/change.rs b/module/move/plot_interface/src/plot/abs/change.rs index b6ba9fc235..fc14b77ec9 100644 --- a/module/move/plot_interface/src/plot/abs/change.rs +++ b/module/move/plot_interface/src/plot/abs/change.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::own::*; diff --git a/module/move/plot_interface/src/plot/abs/changer.rs b/module/move/plot_interface/src/plot/abs/changer.rs index 99e39449e0..9e09820670 100644 --- a/module/move/plot_interface/src/plot/abs/changer.rs +++ b/module/move/plot_interface/src/plot/abs/changer.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::own::*; diff --git a/module/move/plot_interface/src/plot/abs/context.rs b/module/move/plot_interface/src/plot/abs/context.rs index 526b5bf488..c9f844e802 100644 --- a/module/move/plot_interface/src/plot/abs/context.rs +++ b/module/move/plot_interface/src/plot/abs/context.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::own::*; diff --git a/module/move/plot_interface/src/plot/abs/identity.rs b/module/move/plot_interface/src/plot/abs/identity.rs index d8be2ccff7..1fe2b0e613 100644 --- a/module/move/plot_interface/src/plot/abs/identity.rs +++ b/module/move/plot_interface/src/plot/abs/identity.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::own::*; diff --git a/module/move/plot_interface/src/plot/abs/registry.rs b/module/move/plot_interface/src/plot/abs/registry.rs index b6d662b429..21a2cd6be7 100644 --- a/module/move/plot_interface/src/plot/abs/registry.rs +++ b/module/move/plot_interface/src/plot/abs/registry.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::own::*; diff --git a/module/move/plot_interface/src/plot/color.rs b/module/move/plot_interface/src/plot/color.rs index b14a3e268e..fc2b94c17f 100644 --- a/module/move/plot_interface/src/plot/color.rs +++ b/module/move/plot_interface/src/plot/color.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::own::*; diff --git a/module/move/plot_interface/src/plot/sys/context.rs b/module/move/plot_interface/src/plot/sys/context.rs index e5c23e71f6..ee2f95fbf3 100644 --- a/module/move/plot_interface/src/plot/sys/context.rs +++ b/module/move/plot_interface/src/plot/sys/context.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::own::*; diff --git a/module/move/plot_interface/src/plot/sys/context_changer.rs b/module/move/plot_interface/src/plot/sys/context_changer.rs index 2f87310469..fa33094931 100644 --- a/module/move/plot_interface/src/plot/sys/context_changer.rs +++ b/module/move/plot_interface/src/plot/sys/context_changer.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::own::*; diff --git a/module/move/plot_interface/src/plot/sys/drawing.rs b/module/move/plot_interface/src/plot/sys/drawing.rs index 7fdca77c2d..1ec732286b 100644 --- a/module/move/plot_interface/src/plot/sys/drawing.rs +++ b/module/move/plot_interface/src/plot/sys/drawing.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::own::*; diff --git a/module/move/plot_interface/src/plot/sys/drawing/change_new.rs b/module/move/plot_interface/src/plot/sys/drawing/change_new.rs index 914678a907..4661f9587b 100644 --- a/module/move/plot_interface/src/plot/sys/drawing/change_new.rs +++ b/module/move/plot_interface/src/plot/sys/drawing/change_new.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::own::*; diff --git a/module/move/plot_interface/src/plot/sys/drawing/changer.rs b/module/move/plot_interface/src/plot/sys/drawing/changer.rs index bfe7cf170f..7fd62e8e44 100644 --- a/module/move/plot_interface/src/plot/sys/drawing/changer.rs +++ b/module/move/plot_interface/src/plot/sys/drawing/changer.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::own::*; diff --git a/module/move/plot_interface/src/plot/sys/drawing/command.rs b/module/move/plot_interface/src/plot/sys/drawing/command.rs index f98cedfd22..998272ee16 100644 --- a/module/move/plot_interface/src/plot/sys/drawing/command.rs +++ b/module/move/plot_interface/src/plot/sys/drawing/command.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { // use crate::own::*; diff --git a/module/move/plot_interface/src/plot/sys/drawing/queue.rs b/module/move/plot_interface/src/plot/sys/drawing/queue.rs index c68de594ba..c3148011bb 100644 --- a/module/move/plot_interface/src/plot/sys/drawing/queue.rs +++ b/module/move/plot_interface/src/plot/sys/drawing/queue.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { // use crate::own::*; diff --git a/module/move/plot_interface/src/plot/sys/drawing/rect_change_new.rs b/module/move/plot_interface/src/plot/sys/drawing/rect_change_new.rs index 7b1a3acfc7..57fe8b5898 100644 --- a/module/move/plot_interface/src/plot/sys/drawing/rect_change_new.rs +++ b/module/move/plot_interface/src/plot/sys/drawing/rect_change_new.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::own::*; diff --git a/module/move/plot_interface/src/plot/sys/drawing/rect_change_region.rs b/module/move/plot_interface/src/plot/sys/drawing/rect_change_region.rs index bdbb18321d..84c1634301 100644 --- a/module/move/plot_interface/src/plot/sys/drawing/rect_change_region.rs +++ b/module/move/plot_interface/src/plot/sys/drawing/rect_change_region.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::own::*; diff --git a/module/move/plot_interface/src/plot/sys/drawing/rect_changer.rs b/module/move/plot_interface/src/plot/sys/drawing/rect_changer.rs index 85d56d9b48..cb5ddf757f 100644 --- a/module/move/plot_interface/src/plot/sys/drawing/rect_changer.rs +++ b/module/move/plot_interface/src/plot/sys/drawing/rect_changer.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::own::*; diff --git a/module/move/plot_interface/src/plot/sys/stroke_brush.rs b/module/move/plot_interface/src/plot/sys/stroke_brush.rs index 08c73b350b..edfbfc4878 100644 --- a/module/move/plot_interface/src/plot/sys/stroke_brush.rs +++ b/module/move/plot_interface/src/plot/sys/stroke_brush.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::own::*; diff --git a/module/move/plot_interface/src/plot/sys/stroke_brush/change_color.rs b/module/move/plot_interface/src/plot/sys/stroke_brush/change_color.rs index ae615f89a4..76bd951613 100644 --- a/module/move/plot_interface/src/plot/sys/stroke_brush/change_color.rs +++ b/module/move/plot_interface/src/plot/sys/stroke_brush/change_color.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::own::*; diff --git a/module/move/plot_interface/src/plot/sys/stroke_brush/change_new.rs b/module/move/plot_interface/src/plot/sys/stroke_brush/change_new.rs index d147f3241b..caa1c2f75c 100644 --- a/module/move/plot_interface/src/plot/sys/stroke_brush/change_new.rs +++ b/module/move/plot_interface/src/plot/sys/stroke_brush/change_new.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::own::*; diff --git a/module/move/plot_interface/src/plot/sys/stroke_brush/change_width.rs b/module/move/plot_interface/src/plot/sys/stroke_brush/change_width.rs index 192b42e8ad..758fbe75a7 100644 --- a/module/move/plot_interface/src/plot/sys/stroke_brush/change_width.rs +++ b/module/move/plot_interface/src/plot/sys/stroke_brush/change_width.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::own::*; diff --git a/module/move/plot_interface/src/plot/sys/stroke_brush/changer.rs b/module/move/plot_interface/src/plot/sys/stroke_brush/changer.rs index c6f8ab0f5f..d6208455a0 100644 --- a/module/move/plot_interface/src/plot/sys/stroke_brush/changer.rs +++ b/module/move/plot_interface/src/plot/sys/stroke_brush/changer.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::own::*; diff --git a/module/move/plot_interface/src/plot/sys/target.rs b/module/move/plot_interface/src/plot/sys/target.rs index 96f38bfe51..820f3a3b97 100644 --- a/module/move/plot_interface/src/plot/sys/target.rs +++ b/module/move/plot_interface/src/plot/sys/target.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { // use crate::prelude::*; diff --git a/module/move/plot_interface/tests/smoke_test.rs b/module/move/plot_interface/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/move/plot_interface/tests/smoke_test.rs +++ b/module/move/plot_interface/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/move/refiner/License b/module/move/refiner/License index 6d5ef8559f..72c80c1308 100644 --- a/module/move/refiner/License +++ b/module/move/refiner/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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 6d5ef8559f..72c80c1308 100644 --- a/module/move/sqlx_query/License +++ b/module/move/sqlx_query/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/src/lib.rs b/module/move/sqlx_query/src/lib.rs index b0855a6219..53d4a4043e 100644 --- a/module/move/sqlx_query/src/lib.rs +++ b/module/move/sqlx_query/src/lib.rs @@ -17,7 +17,7 @@ #![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/../../../", "Readme.md" ) ) ] -/// Internal namespace. +/// Define a private namespace for all its items. #[ cfg( feature = "enabled" ) ] mod private { diff --git a/module/move/sqlx_query/tests/smoke_test.rs b/module/move/sqlx_query/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/move/sqlx_query/tests/smoke_test.rs +++ b/module/move/sqlx_query/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/move/unitore/Cargo.toml b/module/move/unitore/Cargo.toml index 8f98fba818..08313bc8e0 100644 --- a/module/move/unitore/Cargo.toml +++ b/module/move/unitore/Cargo.toml @@ -32,7 +32,7 @@ enabled = [] [dependencies] error_tools = { workspace = true, features = [ "default" ] } -proper_path_tools = { workspace = true, features = [ "default" ] } +pth = { workspace = true, features = [ "default" ] } tokio = { version = "1.36.0", features = [ "rt", "rt-multi-thread", "io-std", "macros" ] } hyper = { version = "1.1.0", features = [ "client" ] } hyper-tls = "0.6.0" @@ -43,7 +43,7 @@ toml = "0.8.10" serde = "1.0.196" url = { version = "2.0", features = ["serde"] } humantime-serde = "1.1.1" -gluesql = "0.15.0" +gluesql = "0.16.2" async-trait = "0.1.41" wca = { workspace = true } mockall = "0.12.1" diff --git a/module/move/unitore/src/action/config.rs b/module/move/unitore/src/action/config.rs index 9dfc356fe0..a2da010f41 100644 --- a/module/move/unitore/src/action/config.rs +++ b/module/move/unitore/src/action/config.rs @@ -16,7 +16,7 @@ use gluesql::{ prelude::Payload, sled_storage::SledStorage }; /// Add configuration file with subscriptions to storage. pub async fn config_add( mut storage : FeedStorage< SledStorage >, path : &PathBuf ) -> Result< impl Report > { - let path = proper_path_tools::path::normalize( path ); + let path = pth::path::normalize( path ); let mut err_str = format!( "Invalid path for config file {:?}", path ); @@ -63,7 +63,7 @@ pub async fn config_add( mut storage : FeedStorage< SledStorage >, path : &PathB /// Remove configuration file from storage. pub async fn config_delete( mut storage : FeedStorage< SledStorage >, path : &PathBuf ) -> Result< impl Report > { - let path = proper_path_tools::path::normalize( path ); + let path = pth::path::normalize( path ); let path = path.canonicalize().context( format!( "Invalid path for config file {:?}", path ) )?; let config = Config::new( path.to_string_lossy().to_string() ); diff --git a/module/move/unitore/src/command/config.rs b/module/move/unitore/src/command/config.rs index bbd436ccb9..b1e678a732 100644 --- a/module/move/unitore/src/command/config.rs +++ b/module/move/unitore/src/command/config.rs @@ -18,7 +18,28 @@ impl ConfigCommand /// Create command for adding config. pub fn add() -> Result< Command > { - let rt = tokio::runtime::Runtime::new()?; + #[ tokio::main ] + async fn add_command( o : VerifiedCommand ) + { + // qqq: could we print something on None value? + let Some( path ) = o.args.get_owned::< PathBuf >( 0 ) else { return; }; + + let path_to_storage = std::env::var( "UNITORE_STORAGE_PATH" ).ok() + .unwrap_or_else( || String::from( "./_data" ) ); + let config = Config::default().path( path_to_storage ); + + let res = ( || async + { + let feed_storage = FeedStorage::init_storage( &config ).await?; + config_add( feed_storage, &path ).await + } )().await; + + match res + { + Ok( report ) => report.report(), + Err( err ) => println!( "{:?}", err ), + } + } Ok ( @@ -37,38 +58,7 @@ impl ConfigCommand " link = \"https://feeds.bbci.co.uk/news/world/rss.xml\"\n", )) .subject().hint( "Path" ).kind( Type::Path ).optional( false ).end() - .routine( move | o : VerifiedCommand | - { - let path_arg = o.args - .get_owned::< wca::Value >( 0 ); - - if let Some( path ) = path_arg - { - let path : PathBuf = path.into(); - - let res = rt.block_on - ( async move - { - let path_to_storage = std::env::var( "UNITORE_STORAGE_PATH" ) - .unwrap_or( String::from( "./_data" ) ) - ; - - let config = Config::default() - .path( path_to_storage ) - ; - - let feed_storage = FeedStorage::init_storage( &config ).await?; - config_add( feed_storage, &path ).await - } - ); - - match res - { - Ok( report ) => report.report(), - Err( err ) => println!( "{:?}", err ), - } - } - }) + .routine( add_command ) .end() ) } @@ -76,8 +66,29 @@ impl ConfigCommand /// Create command for deleting config. pub fn delete() -> Result< Command > { - let rt = tokio::runtime::Runtime::new()?; - + #[ tokio::main ] + async fn delete_command( o : VerifiedCommand ) + { + // qqq: could we print something on None value? + let Some( path ) = o.args.get_owned::< PathBuf >( 0 ) else { return; }; + + let path_to_storage = std::env::var( "UNITORE_STORAGE_PATH" ).ok() + .unwrap_or_else( || String::from( "./_data" ) ); + let config = Config::default().path( path_to_storage ); + + let res = ( || async + { + let feed_storage = FeedStorage::init_storage( &config ).await?; + config_delete( feed_storage, &path ).await + } )().await; + + match res + { + Ok( report ) => report.report(), + Err( err ) => println!( "{:?}", err ), + } + } + Ok( Command::former() .phrase( "config.delete" ) @@ -87,38 +98,7 @@ impl ConfigCommand " Example: .config.delete ./config/feeds.toml", )) .subject().hint( "Path" ).kind( Type::Path ).optional( false ).end() - .routine( move | o : VerifiedCommand | - { - let path_arg = o.args - .get_owned::< wca::Value >( 0 ); - - if let Some( path ) = path_arg - { - let path : PathBuf = path.into(); - - let res = rt.block_on - ( async move - { - let path_to_storage = std::env::var( "UNITORE_STORAGE_PATH" ) - .unwrap_or( String::from( "./_data" ) ) - ; - - let config = Config::default() - .path( path_to_storage ) - ; - - let feed_storage = FeedStorage::init_storage( &config ).await?; - config_delete( feed_storage, &path ).await - } - ); - - match res - { - Ok( report ) => report.report(), - Err( err ) => println!( "{:?}", err ), - } - } - }) + .routine( delete_command ) .end() ) } diff --git a/module/move/unitore/src/entity/config.rs b/module/move/unitore/src/entity/config.rs index 92f9f550d6..536c1c5142 100644 --- a/module/move/unitore/src/entity/config.rs +++ b/module/move/unitore/src/entity/config.rs @@ -44,7 +44,7 @@ pub trait ConfigStore // qqq : use AbsolutePath newtype from `path_tools` // qqq : normalize all paths with `path_tools::path::normalize` -// https://docs.rs/proper_path_tools/latest/proper_path_tools/path/fn.normalize.html +// https://docs.rs/pth/latest/pth/path/fn.normalize.html // added path normalization // unitore .query.execute \'SELECT \* FROM feed\' diff --git a/module/move/unitore/tests/config_add.rs b/module/move/unitore/tests/config_add.rs index 7f080622b8..6673b0f608 100644 --- a/module/move/unitore/tests/config_add.rs +++ b/module/move/unitore/tests/config_add.rs @@ -12,7 +12,7 @@ use error_tools::untyped::Result; async fn config_add() -> Result< () > { let path = PathBuf::from( "./tests/fixtures/test_config.toml" ); - let temp_path = proper_path_tools::path::unique_folder_name().unwrap(); + let temp_path = pth::path::unique_folder_name().unwrap(); let config = Config::default() .path( format!( "./{}", temp_path ) ) diff --git a/module/move/unitore/tests/config_delete.rs b/module/move/unitore/tests/config_delete.rs index 9a7ffdf10a..c3393702cc 100644 --- a/module/move/unitore/tests/config_delete.rs +++ b/module/move/unitore/tests/config_delete.rs @@ -16,7 +16,7 @@ async fn config_delete() -> Result< () > { let path = std::path::PathBuf::from( "./tests/fixtures/test_config.toml" ); - let temp_path = proper_path_tools::path::unique_folder_name().unwrap(); + let temp_path = pth::path::unique_folder_name().unwrap(); let config = Config::default() .path( format!( "./{}", temp_path ) ) diff --git a/module/move/unitore/tests/frames_download.rs b/module/move/unitore/tests/frames_download.rs index 11494838f9..0b78b077d0 100644 --- a/module/move/unitore/tests/frames_download.rs +++ b/module/move/unitore/tests/frames_download.rs @@ -2,7 +2,7 @@ use feed_rs::parser as feed_parser; use gluesql:: { core:: - { + { chrono::{ DateTime, Utc }, data::Value }, @@ -20,7 +20,7 @@ use error_tools::untyped::Result; #[ tokio::test ] async fn test_save() -> Result< () > { - let temp_path = proper_path_tools::path::unique_folder_name().unwrap(); + let temp_path = pth::path::unique_folder_name().unwrap(); let config = Config::default() .path( format!( "./{}", temp_path ) ) @@ -52,7 +52,7 @@ async fn test_save() -> Result< () > #[ tokio::test ] async fn test_update() -> Result< () > { - let temp_path = proper_path_tools::path::unique_folder_name().unwrap(); + let temp_path = pth::path::unique_folder_name().unwrap(); let config = Config::default() .path( format!( "./{}", temp_path ) ) diff --git a/module/move/unitore/tests/query_execute.rs b/module/move/unitore/tests/query_execute.rs index 4215971781..0e47c9e576 100644 --- a/module/move/unitore/tests/query_execute.rs +++ b/module/move/unitore/tests/query_execute.rs @@ -41,14 +41,14 @@ fn query_execute() -> Result< () > assert!( res.is_ok() ); // test action - let rt = tokio::runtime::Runtime::new()?; + let rt = tokio::runtime::Runtime::new()?; let ca = CommandsAggregator::former() .command( "query.execute" ) .hint( "hint" ) .long_hint( "long_hint" ) .subject().hint( "SQL query" ).kind( Type::String ).optional( false ).end() .routine( move | o : VerifiedCommand | - { + { let mut f_store = MockStore::new(); f_store .expect_query_execute() @@ -62,19 +62,19 @@ fn query_execute() -> Result< () > ] ) ) ) - ; + ; _ = rt.block_on( async move { let query_arg = o.args .get_owned::< String >( 0 ) ; - + let query_str = query_arg.unwrap(); query::query_execute( f_store, query_str ).await - } ); + } ); } ) .end() - .perform(); + .perform(); let entries = ca.perform( vec![ ".query.execute".to_string(), "SELECT title FROM frame".into() ] ); assert!( entries.is_ok() ); Ok( () ) @@ -84,7 +84,7 @@ fn query_execute() -> Result< () > async fn query_feeds() -> Result< () > { let path = PathBuf::from( "./tests/fixtures/test_config.toml" ); - let temp_path = proper_path_tools::path::unique_folder_name().unwrap(); + let temp_path = pth::path::unique_folder_name().unwrap(); let config = sled::Config::default() .path( format!( "./{}", temp_path ) ) @@ -114,7 +114,7 @@ async fn query_feeds() -> Result< () > #[ tokio::test ] async fn query_frames() -> Result< () > { - let temp_path = proper_path_tools::path::unique_folder_name().unwrap(); + let temp_path = pth::path::unique_folder_name().unwrap(); let config = sled::Config::default() .path( format!( "./{}", temp_path ) ) @@ -160,7 +160,7 @@ async fn query_frames() -> Result< () > async fn query_configs() -> Result< () > { let path = PathBuf::from( "./tests/fixtures/test_config.toml" ); - let temp_path = proper_path_tools::path::unique_folder_name().unwrap(); + let temp_path = pth::path::unique_folder_name().unwrap(); let config = sled::Config::default() .path( format!( "./{}", temp_path ) ) @@ -182,6 +182,6 @@ async fn query_configs() -> Result< () > { assert!( false ); } - + Ok( () ) } diff --git a/module/move/unitore/tests/table_list.rs b/module/move/unitore/tests/table_list.rs index ff06deae00..88b16d519e 100644 --- a/module/move/unitore/tests/table_list.rs +++ b/module/move/unitore/tests/table_list.rs @@ -13,7 +13,7 @@ use error_tools::untyped::Result; #[ tokio::test ] async fn table_list() -> Result< () > { - let temp_path = proper_path_tools::path::unique_folder_name().unwrap(); + let temp_path = pth::path::unique_folder_name().unwrap(); let config = Config::default() .path( format!( "./{}", temp_path ) ) @@ -30,7 +30,7 @@ async fn table_list() -> Result< () > .map( | row | row[ 1 ].clone() ) .collect::< Vec< _ > >() ; - + assert_eq!( column_names.len(), 9 ); assert!( column_names.contains( &Str( String::from( "published") ) ) ); assert!( column_names.contains( &Str( String::from( "authors") ) ) ); diff --git a/module/move/unitore/tests/tables_list.rs b/module/move/unitore/tests/tables_list.rs index f740e94b08..7f5fbb57f7 100644 --- a/module/move/unitore/tests/tables_list.rs +++ b/module/move/unitore/tests/tables_list.rs @@ -9,7 +9,7 @@ use error_tools::untyped::Result; #[ tokio::test ] async fn tables_list() -> Result< () > { - let temp_path = proper_path_tools::path::unique_folder_name().unwrap(); + let temp_path = pth::path::unique_folder_name().unwrap(); let config = Config::default() .path( format!( "./{}", temp_path ) ) diff --git a/module/move/wca/Cargo.toml b/module/move/wca/Cargo.toml index 2d69a8aaff..662ecd9c71 100644 --- a/module/move/wca/Cargo.toml +++ b/module/move/wca/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wca" -version = "0.20.0" +version = "0.24.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -40,12 +40,12 @@ harness = false [dependencies] ## internal -error_tools = { workspace = true, features = [ "default" ] } -strs_tools = { workspace = true, features = [ "default" ] } -mod_interface = { workspace = true, features = [ "default" ] } -iter_tools = { workspace = true, features = [ "default" ] } -former = { workspace = true, features = [ "default" ] } -# xxx : qqq : optimize set of features +error_tools = { workspace = true, features = [ "enabled", "error_typed", "error_untyped" ] } +mod_interface = { workspace = true, features = [ "enabled" ] } +iter_tools = { workspace = true, features = [ "enabled" ] } +former = { workspace = true, features = [ "enabled", "derive_former" ] } +# xxx : aaa : optimize set of features +# aaa : done. ## external log = "0.4" diff --git a/module/move/wca/License b/module/move/wca/License index 6d5ef8559f..72c80c1308 100644 --- a/module/move/wca/License +++ b/module/move/wca/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/move/wca/Readme.md index b808fce2bc..2e5ffafd27 100644 --- a/module/move/wca/Readme.md +++ b/module/move/wca/Readme.md @@ -2,7 +2,7 @@ # Module :: wca - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_wca_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_wca_push.yml) [![docs.rs](https://img.shields.io/docsrs/wca?color=e3e8f0&logo=docs.rs)](https://docs.rs/wca) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fmove%2Fwca%2Fexamples%2Fwca_trivial.rs,RUN_POSTFIX=--example%20wca_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_wca_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_wca_push.yml) [![docs.rs](https://img.shields.io/docsrs/wca?color=e3e8f0&logo=docs.rs)](https://docs.rs/wca) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fmove%2Fwca%2Fexamples%2Fwca_trivial.rs,RUN_POSTFIX=--example%20module%2Fmove%2Fwca%2Fexamples%2Fwca_trivial.rs/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) The tool to make CLI ( commands user interface ). It is able to aggregate external binary applications, as well as functions, which are written in your language. @@ -14,7 +14,7 @@ The tool to make CLI ( commands user interface ). It is able to aggregate extern ```rust #[ cfg( not( feature = "no_std" ) ) ] { - use wca::{ VerifiedCommand, Context, Type }; + use wca::{ VerifiedCommand, Type }; fn main() { @@ -37,7 +37,7 @@ The tool to make CLI ( commands user interface ). It is able to aggregate extern .end() .perform(); - let args = std::env::args().skip( 1 ).collect::< Vec< String > >(); + let args: Vec< String > = std::env::args().skip( 1 ).collect(); ca.perform( args ).unwrap(); } diff --git a/module/move/wca/benches/bench.rs b/module/move/wca/benches/bench.rs index a1dfbf1b0e..8b05ccf91d 100644 --- a/module/move/wca/benches/bench.rs +++ b/module/move/wca/benches/bench.rs @@ -1,14 +1,17 @@ #![ allow( missing_debug_implementations ) ] #![ allow( missing_docs ) ] -use std::collections::HashMap; + use criterion::{ criterion_group, criterion_main, Criterion }; -use wca::{ CommandsAggregator, Routine, Type }; +use wca::grammar::Dictionary; +use wca::{ CommandsAggregator, Type }; + -fn init( count : usize, command : wca::Command ) -> CommandsAggregator + +fn init( count : usize, command : wca::grammar::Command ) -> CommandsAggregator { - let mut commands = Vec::with_capacity( count ); - let mut routines = HashMap::with_capacity( count ); + + let mut dic_former = Dictionary::former(); for i in 0 .. count { let name = format!( "command_{i}" ); @@ -16,19 +19,15 @@ fn init( count : usize, command : wca::Command ) -> CommandsAggregator let mut command = command.clone(); command.phrase = name.clone(); - commands.push( command ); - routines.insert - ( - name, Routine::new( | _ | { assert_eq!( 1 + 1, 2 ); Ok( () ) } ), - ); + dic_former = dic_former.command( command ); + } - - assert_eq!( count, commands.len() ); - assert_eq!( count, routines.len() ); - + let dictionary = dic_former.form(); + + // The CommandsAggregator has changed and there are no more grammar fields and the executor no longer stores routines. + // Accordingly, I made changes and write commands through DictionaryFormer and pass it to CommandsAggregator CommandsAggregator::former() - .grammar( commands ) - .executor( routines ) + .dictionary( dictionary ) .perform() } @@ -37,7 +36,7 @@ fn initialize_commands_without_args( count : usize ) -> CommandsAggregator init ( count, - wca::Command::former() + wca::grammar::Command::former() .hint( "hint" ) .long_hint( "long_hint" ) .phrase( "{placeholder}" ) @@ -45,36 +44,40 @@ fn initialize_commands_without_args( count : usize ) -> CommandsAggregator ) } -fn initialize_commands_with_subjects( count : usize ) -> CommandsAggregator { +fn initialize_commands_with_subjects( count : usize ) -> CommandsAggregator +{ + // The way commands are initialized has changed, now the ComandFormer from the grammar module is used and the subject() and property methods are called differently init ( count, - wca::Command::former() + wca::grammar::Command::former() .hint( "hint" ) .long_hint( "long_hint" ) .phrase( "{placeholder}" ) - .subject( "hint", Type::String, true ) - .subject( "hint", Type::String, true ) + .subject().hint( "hint" ).kind( Type::String ).optional( true ).end() + .subject().hint( "hint" ).kind( Type::String ).optional( true ).end() .form(), ) } -fn initialize_commands_with_properties( count : usize ) -> CommandsAggregator { +fn initialize_commands_with_properties( count : usize ) -> CommandsAggregator +{ init ( count, - wca::Command::former() + wca::grammar::Command::former() .hint( "hint" ) .long_hint( "long_hint" ) .phrase( "{placeholder}" ) - .property( "prop", "hint", Type::String, true ) - .property( "prop2", "hint", Type::String, true ) + .property( "prop" ).hint( "hint" ).kind( Type::String ).optional( true ).end() + .property( "prop2" ).hint( "hint" ).kind( Type::String ).optional( true ).end() .form(), ) } -fn run_commands< S : AsRef< str > >( ca : CommandsAggregator, command : S ) { - ca.perform( command.as_ref() ).unwrap() +fn run_commands< S : AsRef< str > >( ca : CommandsAggregator, command : S ) +{ + ca.perform( command.as_ref() ).unwrap(); } fn benchmark_initialize_thousand_commands( c : &mut Criterion ) diff --git a/module/move/wca/examples/wca_custom_error.rs b/module/move/wca/examples/wca_custom_error.rs new file mode 100644 index 0000000000..7b3862b77c --- /dev/null +++ b/module/move/wca/examples/wca_custom_error.rs @@ -0,0 +1,43 @@ +//! +//! # Handling Errors with `CommandsAggregator` +//! +//! This module provides an example of how to use `wca::CommandsAggregator` to manage error handling in a command-line interface. The `CommandsAggregator` offers a fluent interface for defining commands and associating them with various error types, making it straightforward to handle and present errors in a structured way. +//! +//! ## Purpose +//! +//! The primary goal of this example is to showcase how `CommandsAggregator` facilitates error handling, whether errors are simple strings, custom typed errors, untyped errors, or errors with additional context. This approach ensures that error management is both consistent and extensible. +//! + +#[ derive( Debug, error_tools::typed::Error )] +enum CustomError +{ + #[ error( "this is typed error" ) ] + TheError, +} + +fn main() -> error_tools::error::untyped::Result< () > +{ + let ca = wca::CommandsAggregator::former() + .command( "error.string" ) + .hint( "Returns error as a string" ) + .routine( || { Err( "this is string error" ) } ) + .end() + .command( "error.typed" ) + .hint( "Returns error as a custom error" ) + .routine( || { Err( CustomError::TheError ) } ) + .end() + .command( "error.untyped" ) + .hint( "Returns error as untyped error" ) + .routine( || { Err( error_tools::error::untyped::format_err!( "this is untyped error" ) ) } ) + .end() + .command( "error.with_context" ) + .hint( "Returns error as untyped error with context" ) + .routine( || { Err( error_tools::error::untyped::format_err!( "this is untyped error" ).context( "with context" ) ) } ) + .end() + .perform(); + + let args: Vec< String > = std::env::args().skip( 1 ).collect(); + () = ca.perform( args )?; + + Ok( () ) +} \ No newline at end of file diff --git a/module/move/wca/examples/wca_fluent.rs b/module/move/wca/examples/wca_fluent.rs index 487d6ee97d..6b2f4adf61 100644 --- a/module/move/wca/examples/wca_fluent.rs +++ b/module/move/wca/examples/wca_fluent.rs @@ -7,45 +7,53 @@ //! -use wca::{ Context, Handler, Type, VerifiedCommand }; +use wca::{ executor::{ Context, Handler }, Type, VerifiedCommand }; use std::sync::{ Arc, Mutex }; -fn main() +fn main() -> error_tools::error::untyped::Result< () > { let ca = wca::CommandsAggregator::former() .with_context( Mutex::new( 0 ) ) .command( "echo" ) - .hint( "prints all subjects and properties" ) - .subject().kind( Type::String ).optional( true ).end() - .property( "property" ).hint( "simple property" ).kind( Type::String ).optional( true ).end() - .routine( | o : VerifiedCommand | { println!( "= Args\n{:?}\n\n= Properties\n{:?}\n", o.args, o.props ) } ) - .end() + .hint( "prints all subjects and properties" ) + .subject().kind( Type::String ).optional( true ).end() + .property( "property" ).hint( "simple property" ).kind( Type::String ).optional( true ).end() + .routine( | o : VerifiedCommand | + { + println!( "= Args\n{:?}\n\n= Properties\n{:?}\n", o.args, o.props ) + }) + .end() .command( "inc" ) - .hint( "This command increments a state number each time it is called consecutively. (E.g. `.inc .inc`)" ) - .routine( | ctx : Context | - { - let i : Arc< Mutex< i32 > > = ctx.get().unwrap(); - let mut i = i.lock().unwrap(); - println!( "i = {}", i ); - *i += 1; - } ) - .end() + .hint( "This command increments a state number each time it is called consecutively. (E.g. `.inc .inc`)" ) + .routine( | ctx : Context | + { + let i : Arc< Mutex< i32 > > = ctx.get().unwrap(); + let mut i = i.lock().unwrap(); + println!( "i = {}", i ); + *i += 1; + }) + .end() .command( "error" ) - .hint( "prints all subjects and properties" ) - .subject().kind( Type::String ).optional( true ).end() - .routine( | o : VerifiedCommand | { println!( "Returns an error" ); Err( format!( "{}", o.args.get_owned::< String >( 0 ).unwrap_or_default() ) ) } ) - .end() + .hint( "prints all subjects and properties" ) + .subject().kind( Type::String ).optional( true ).end() + .routine( | o : VerifiedCommand | + { + println!( "Returns an error" ); + Err( format!( "{}", o.args.get_owned::< String >( 0 ).unwrap_or_default() ) ) + }) + .end() .command( "exit" ) - .hint( "just exit" ) - .routine( Handler::< _, std::convert::Infallible >::from - ( - || { println!( "exit" ); std::process::exit( 0 ) } - ) ) - .end() + .hint( "just exit" ) + .routine( Handler::< _, std::convert::Infallible >::from( || + { + println!( "exit" ); std::process::exit( 0 ) + })) + .end() .perform(); - let args = std::env::args().skip( 1 ).collect::< Vec< String > >(); - ca.perform( args ).unwrap(); + let args: Vec< String > = std::env::args().skip( 1 ).collect(); + ca.perform( args )?; + Ok( () ) } diff --git a/module/move/wca/examples/wca_suggest.rs b/module/move/wca/examples/wca_suggest.rs index 2bb73fa111..f57c589de6 100644 --- a/module/move/wca/examples/wca_suggest.rs +++ b/module/move/wca/examples/wca_suggest.rs @@ -22,26 +22,23 @@ use wca::{ CommandsAggregator, Type, VerifiedCommand }; -fn main() +fn main() -> error_tools::error::untyped::Result< () > { let ca = CommandsAggregator::former() .command( "echo" ) - .hint( "prints all subjects and properties" ) - .subject().kind( Type::String ).optional( true ).end() - .property( "property" ).hint( "simple property" ).kind( Type::String ).optional( true ).end() - .routine( | o : VerifiedCommand | - { - println!( "= Args\n{:?}\n\n= Properties\n{:?}\n", o.args, o.props ); - }) - .end() + .hint( "prints all subjects and properties" ) + .subject().kind( Type::String ).optional( true ).end() + .property( "property" ).hint( "simple property" ).kind( Type::String ).optional( true ).end() + .routine( | o : VerifiedCommand | + { + println!( "= Args\n{:?}\n\n= Properties\n{:?}\n", o.args, o.props ); + }) + .end() .perform(); - let args = std::env::args().skip( 1 ).collect::< Vec< String > >(); - match ca.perform( args.join( " " ) ) - { - Ok( _ ) => {} - Err( err ) => println!( "{err}" ), - }; + let args: Vec< String > = std::env::args().skip( 1 ).collect(); + ca.perform( args.join( " " ) )?; + Ok( () ) } diff --git a/module/move/wca/examples/wca_trivial.rs b/module/move/wca/examples/wca_trivial.rs index c228e6e20a..443742cc49 100644 --- a/module/move/wca/examples/wca_trivial.rs +++ b/module/move/wca/examples/wca_trivial.rs @@ -16,24 +16,24 @@ fn exit() std::process::exit( 0 ) } -fn main() +fn main() -> error_tools::error::untyped::Result< () > { let ca = CommandsAggregator::former() .command( "exit" ) - .hint( "just exit" ) - .routine( || exit() ) - .end() + .hint( "just exit" ) + // fix clippy + .routine( exit ) + .end() .command( "echo" ) - .hint( "prints all subjects and properties" ) - .subject().hint( "Subject" ).kind( Type::String ).optional( true ).end() - .property( "property" ).hint( "simple property" ).kind( Type::String ).optional( true ).end() - .routine( f1 ) - .end() + .hint( "prints all subjects and properties" ) + .subject().hint( "Subject" ).kind( Type::String ).optional( true ).end() + .property( "property" ).hint( "simple property" ).kind( Type::String ).optional( true ).end() + .routine( f1 ) + .end() .order( Order::Lexicography ) - .perform() - ; + .perform(); - // aaa : qqq2 : for Bohdan : that should work + // aaa : aaa2 : for Bohdan : that should work // let ca = wca::CommandsAggregator::former() // .command( "echo" ) // .hint( "prints all subjects and properties" ) @@ -50,6 +50,8 @@ fn main() // ca.execute( input ).unwrap(); //aaa: works - let input = std::env::args().skip( 1 ).collect::< Vec< String > >(); - ca.perform( input ).unwrap(); + let input: Vec< String > = std::env::args().skip( 1 ).collect(); + ca.perform( input )?; + + Ok( () ) } diff --git a/module/move/wca/src/ca/aggregator.rs b/module/move/wca/src/ca/aggregator.rs index 05575b9136..8c46b6c1c2 100644 --- a/module/move/wca/src/ca/aggregator.rs +++ b/module/move/wca/src/ca/aggregator.rs @@ -1,11 +1,12 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use ca:: { - Verifier, Executor, - grammar::command::private:: + grammar::command:: { CommandFormer, CommandAsSubformer, @@ -14,20 +15,23 @@ mod private }, help::{ HelpGeneratorFn, HelpGeneratorOptions, HelpVariants }, }; + use verifier::{ Verifier, VerificationError, VerifiedCommand }; + use parser::{ Program, Parser, ParserError }; + use grammar::Dictionary; + use executor::Context; - // qqq : group uses - use std::collections::HashSet; - use std::fmt; + use std:: + { + fmt, + collections::HashSet + }; use former::StoragePreform; - // use wtools:: - // { - // }; - // use wtools::thiserror; use error:: { // Result, untyped::Error as wError, // xxx - for_lib::*, + // only importing Error from this module is used + for_lib::Error, }; use iter_tools::Itertools; @@ -54,11 +58,11 @@ mod private /// source of the program input : String, /// original error - error : wError, + error : ParserError, }, /// This variant represents errors that occur during grammar conversion. #[ error( "Can not identify a command.\nDetails: {0}" ) ] - Verifier( wError ), + Verifier( VerificationError ), /// This variant is used to represent errors that occur during executor conversion. #[ error( "Can not find a routine for a command.\nDetails: {0}" ) ] ExecutorConverter( wError ), @@ -70,16 +74,17 @@ mod private { /// This variant is used to represent validation errors. /// It carries a `ValidationError` payload that provides additional information about the error. - #[ error( "Validation error. {0}" ) ] + #[ error( "Validation error\n{0}" ) ] Validation( ValidationError ), /// This variant represents execution errors. - #[ error( "Execution failed. {0:?}" ) ] + #[ error( "Execution failed\n{0:?}" ) ] Execution( wError ), } - // xxx : qqq : qqq2 : for Bohdan : one level is obviously redundant + // xxx : aaa : aaa2 : for Bohdan : one level is obviously redundant // Program< Namespace< ExecutableCommand_ > > -> Program< ExecutableCommand_ > // aaa : done. The concept of `Namespace` has been removed + #[ allow( clippy::type_complexity ) ] struct CommandsAggregatorCallback( Box< dyn Fn( &str, &Program< VerifiedCommand > ) > ); impl fmt::Debug for CommandsAggregatorCallback @@ -93,7 +98,7 @@ mod private /// The `CommandsAggregator` struct is responsible for aggregating all commands that the user defines, /// and for parsing and executing them. It is the main entry point of the library. /// - /// CommandsAggregator component brings everything together. This component is responsible for configuring the `Parser`, `Grammar`, and `Executor` components based on the user’s needs. It also manages the entire pipeline of processing, from parsing the raw text input to executing the final command(parse -> validate -> execute). + /// `CommandsAggregator` component brings everything together. This component is responsible for configuring the `Parser`, `Grammar`, and `Executor` components based on the user’s needs. It also manages the entire pipeline of processing, from parsing the raw text input to executing the final command(parse -> validate -> execute). /// /// # Example: /// @@ -144,8 +149,8 @@ mod private let dictionary = ca.dictionary.get_or_insert_with( Dictionary::default ); dictionary.order = ca.order.unwrap_or_default(); - let help_generator = std::mem::take( &mut ca.help_generator ).unwrap_or_default(); - let help_variants = std::mem::take( &mut ca.help_variants ).unwrap_or_else( || HashSet::from([ HelpVariants::All ]) ); + let help_generator = core::mem::take( &mut ca.help_generator ).unwrap_or_default(); + let help_variants = core::mem::take( &mut ca.help_variants ).unwrap_or_else( || HashSet::from( [ HelpVariants::All ] ) ); if help_variants.contains( &HelpVariants::All ) { @@ -170,6 +175,8 @@ mod private /// # Arguments /// /// * `name` - The name of the command. + /// # Panics + /// qqq: doc pub fn command< IntoName >( self, name : IntoName ) -> CommandAsSubformer< Self, impl CommandAsSubformerEnd< Self > > where IntoName : Into< String >, @@ -203,6 +210,7 @@ mod private /// /// The modified instance of `Self`. // `'static` means that the value must be owned or live at least as a `Context' + #[ must_use ] pub fn with_context< T >( mut self, value : T ) -> Self where T : Sync + Send + 'static, @@ -230,6 +238,7 @@ mod private /// ca.perform( ".help" )?; /// # Ok( () ) } /// ``` + #[ must_use ] pub fn help< HelpFunction >( mut self, func : HelpFunction ) -> Self where HelpFunction : Fn( &Dictionary, HelpGeneratorOptions< '_ > ) -> String + 'static @@ -255,6 +264,7 @@ mod private /// ca.perform( ".help" )?; /// # Ok( () ) } /// ``` + #[ must_use ] pub fn callback< Callback >( mut self, callback : Callback ) -> Self where Callback : Fn( &str, &Program< VerifiedCommand > ) + 'static, @@ -269,21 +279,29 @@ mod private /// Parse, converts and executes a program /// /// Takes a string with program and executes it + /// # Errors + /// qqq: doc pub fn perform< S >( &self, program : S ) -> Result< (), Error > where S : IntoInput { let Input( ref program ) = program.into_input(); - let raw_program = self.parser.parse( program ).map_err( | e | Error::Validation( ValidationError::Parser { input : format!( "{:?}", program ), error : e } ) )?; - let grammar_program = self.verifier.to_program( &self.dictionary, raw_program ).map_err( | e | Error::Validation( ValidationError::Verifier( e ) ) )?; + let raw_program = self.parser.parse( program ).map_err( | e | + { + Error::Validation( ValidationError::Parser { input : format!( "{program:?}" ), error : e } ) + })?; + let grammar_program = self.verifier.to_program( &self.dictionary, raw_program ).map_err( | e | + { + Error::Validation( ValidationError::Verifier( e ) ) + })?; if let Some( callback ) = &self.callback_fn { - callback.0( &program.join( " " ), &grammar_program ) + callback.0( &program.join( " " ), &grammar_program ); } - self.executor.program( &self.dictionary, grammar_program ).map_err( | e | Error::Execution( e ) ) + self.executor.program( &self.dictionary, grammar_program ).map_err( | e | Error::Execution( e.into() ) ) } } } @@ -293,8 +311,8 @@ mod private crate::mod_interface! { exposed use CommandsAggregator; - exposed use CommandsAggregatorFormer; - exposed use Error; - exposed use ValidationError; + orphan use CommandsAggregatorFormer; + orphan use Error; + orphan use ValidationError; exposed use Order; } diff --git a/module/move/wca/src/ca/executor/context.rs b/module/move/wca/src/ca/executor/context.rs index df60994a23..4189550df5 100644 --- a/module/move/wca/src/ca/executor/context.rs +++ b/module/move/wca/src/ca/executor/context.rs @@ -1,3 +1,4 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { use std::sync::Arc; @@ -7,7 +8,7 @@ mod private /// # Examples: /// /// ``` - /// # use wca::{ Routine, Handler, Context, Value, Args, Props, VerifiedCommand }; + /// # use wca::{ executor::{ Routine, Handler, Args, Props, Context }, Value, VerifiedCommand }; /// # use std::sync::{ Arc, Mutex }; /// let routine = Routine::from( Handler::from /// ( @@ -33,11 +34,11 @@ mod private /// } /// assert_eq!( 1, *ctx.get::< Mutex< i32 > >().unwrap().lock().unwrap() ); /// ``` - // qqq : ? + // xxx clarification is needed qqq : поточнити #[ derive( Debug, Clone ) ] pub struct Context { - inner : Arc< dyn std::any::Any + Send + Sync >, + inner : Arc< dyn core::any::Any + Send + Sync >, } impl Default for Context @@ -55,7 +56,6 @@ mod private /// # Arguments /// /// * `value` - The value to be stored in the `Context`. The value must implement the `Send` and `Sync` traits. - /// ``` // `'static` means that the object must be owned or live at least as a `Context' pub fn new< T : Send + Sync + 'static >( value : T ) -> Self { @@ -80,6 +80,7 @@ mod private /// An `Option` containing a reference-counted smart pointer (`Arc`) to the object of type `T` if it exists in the context. /// `None` is returned if the object does not exist or if it cannot be downcasted to type `T`. // `'static` means that the object must be owned or live at least as a `Context' + #[ must_use ] pub fn get< T : Send + Sync + 'static >( &self ) -> Option< Arc< T > > { self.inner.clone().downcast::< T >().ok() @@ -91,5 +92,5 @@ mod private crate::mod_interface! { - exposed use Context; + orphan use Context; } diff --git a/module/move/wca/src/ca/executor/executor.rs b/module/move/wca/src/ca/executor/executor.rs index fe1cbff998..f76cf5e4ab 100644 --- a/module/move/wca/src/ca/executor/executor.rs +++ b/module/move/wca/src/ca/executor/executor.rs @@ -1,14 +1,24 @@ mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; - - // use wtools::error::Result; - use error::return_err; - use ca::help::private::{ HelpGeneratorOptions, LevelOfDetail, generate_help_content }; + use ca::help::{ HelpGeneratorOptions, generate_help_content, LevelOfDetail }; + use verifier::VerifiedCommand; + use parser::Program; + use grammar::Dictionary; + use executor::{ Routine, Context }; // aaa : for Bohdan : how is it useful? where is it used? // aaa : `ExecutorType` has been removed + #[ derive( Debug, error::typed::Error ) ] + pub enum CommandError + { + #[ error( "Internal command: `.{}` failed with: {}", command.phrase, error ) ] + Internal { command: VerifiedCommand, error: InternalCommandError }, + #[ error( "Command: `.{}` failed with: {}", command.phrase, error ) ] + User { command: VerifiedCommand, error: error::untyped::Error }, + } /// Executor that is responsible for executing the program's commands. /// It uses the given `Context` to store and retrieve values during runtime. @@ -35,10 +45,12 @@ mod private /// # Returns /// /// A `Result` with `Ok( () )` if the execution was successful, or an `Err` containing an error message if an error occurred. - /// - // qqq : use typed error + /// # Errors + /// qqq: doc + // aaa : use typed error + // aaa : done pub fn program( &self, dictionary : &Dictionary, program : Program< VerifiedCommand > ) - -> error::untyped::Result< () > + -> Result< (), Box< CommandError > > { for command in program.commands { @@ -60,18 +72,26 @@ mod private /// # Returns /// /// Returns a Result indicating success or failure. If successful, returns `Ok(())`, otherwise returns an error. - // qqq : use typed error + /// # Errors + /// qqq: doc + /// # Panics + /// qqq: doc + // aaa : use typed error + // aaa : done pub fn command( &self, dictionary : &Dictionary, command : VerifiedCommand ) - -> error::untyped::Result< () > + // fix clippy error + -> Result< (), Box< CommandError > > { if command.internal_command { - _exec_internal_command( dictionary, command ) + exec_internal_command( dictionary, command.clone() ) + .map_err( | error | Box::new( CommandError::Internal { command, error } ) ) } else { let routine = dictionary.command( &command.phrase ).unwrap().routine.clone(); - _exec_command( command, routine, self.context.clone() ) + exec_command( command.clone(), routine, self.context.clone() ) + .map_err( | error | Box::new( CommandError::User { command, error } ) ) } } @@ -80,7 +100,9 @@ mod private } // qqq : use typed error - fn _exec_command( command : VerifiedCommand, routine : Routine, ctx : Context ) + // aaa : should it be typed? it is user command with unknown error type + // fix clippy error + fn exec_command( command : VerifiedCommand, routine : Routine, ctx : Context ) -> error::untyped::Result< () > { match routine @@ -90,9 +112,21 @@ mod private } } - // qqq : use typed error - fn _exec_internal_command( dictionary : &Dictionary, command : VerifiedCommand ) - -> error::untyped::Result< () > + #[ derive( Debug, error::typed::Error ) ] + pub enum InternalCommandError + { + #[ error( "Encountered an unrecognized internal command: `.{user_input}`." ) ] + UnknownInternalCommand { user_input: String }, + #[ error( "Not found command that starts with `.{user_input}`." ) ] + CommandNotFound { user_input: String }, + } + + // aaa : use typed error + // aaa : done + #[ allow( clippy::needless_pass_by_value ) ] + // fix clippy error + fn exec_internal_command( dictionary : &Dictionary, command : VerifiedCommand ) + -> Result< (), InternalCommandError > { match command.phrase.as_str() { @@ -122,7 +156,7 @@ mod private let commands = dictionary.search( name.strip_prefix( '.' ).unwrap_or( name ) ); if commands.is_empty() { - return_err!( "Not found command that starts with `.{}`.", name ); + return Err( InternalCommandError::CommandNotFound { user_input : name.into() } ); } let generator_args = HelpGeneratorOptions::former() .command_prefix( "." ) @@ -151,10 +185,10 @@ mod private } else { - return_err!( "Not found command that starts with `.{}`.", name ); + return Err( InternalCommandError::CommandNotFound { user_input : name.into() } ); } } - unexpected => return_err!( "Encountered an unrecognized internal command: `.{}`.", unexpected ), + unexpected => return Err( InternalCommandError::UnknownInternalCommand { user_input: unexpected.into() }), } Ok( () ) diff --git a/module/move/wca/src/ca/executor/mod.rs b/module/move/wca/src/ca/executor/mod.rs index 77789544d0..1793a9d23f 100644 --- a/module/move/wca/src/ca/executor/mod.rs +++ b/module/move/wca/src/ca/executor/mod.rs @@ -1,3 +1,5 @@ +mod private {} + crate::mod_interface! { diff --git a/module/move/wca/src/ca/executor/routine.rs b/module/move/wca/src/ca/executor/routine.rs index 45fc96bed1..13d1fd0d8b 100644 --- a/module/move/wca/src/ca/executor/routine.rs +++ b/module/move/wca/src/ca/executor/routine.rs @@ -1,14 +1,20 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; - // qqq : group + // aaa : group + // aaa : done - use std::collections::HashMap; - // use wtools::error::Result; - - use std::{ fmt::Formatter, rc::Rc }; - // use wtools::anyhow::anyhow; + use std:: + { + collections::HashMap, + fmt::Formatter, + rc::Rc, + }; + use verifier::VerifiedCommand; + use executor::Context; /// Command Args /// @@ -17,7 +23,7 @@ mod private /// # Example: /// /// ``` - /// use wca::{ Args, Value }; + /// use wca::{ executor::Args, Value }; /// /// let args = Args( vec![ Value::String( "Hello, World!".to_string() ) ] ); /// @@ -30,7 +36,7 @@ mod private /// /// ## Use case /// ``` - /// # use wca::{ Routine, Handler, VerifiedCommand }; + /// # use wca::{ executor::{ Routine, Handler }, VerifiedCommand }; /// let routine = Routine::from( Handler::from /// ( /// | o : VerifiedCommand | @@ -47,7 +53,7 @@ mod private /// Returns owned casted value by its index /// /// ``` - /// # use wca::{ Args, Value }; + /// # use wca::{ executor::Args, Value }; /// /// let args = Args( vec![ Value::String( "Hello, World!".to_string() ) ] ); /// @@ -57,6 +63,7 @@ mod private /// let first_arg : &str = args[ 0 ].clone().into(); /// assert_eq!( "Hello, World!", first_arg ); /// ``` + #[ must_use ] pub fn get_owned< T : From< Value > >( &self, index : usize ) -> Option< T > { self.0.get( index ).map( | arg | arg.to_owned().into() ) @@ -79,7 +86,7 @@ mod private /// # Example: /// /// ``` - /// use wca::{ Props, Value }; + /// use wca::{ executor::Props, Value }; /// /// let props = Props( [ ( "hello".to_string(), Value::String( "World!".to_string() ) ) ].into() ); /// let hello_prop : &str = props.get_owned( "hello" ).unwrap(); @@ -89,7 +96,7 @@ mod private /// /// ## Use case /// ``` - /// # use wca::{ Routine, Handler, Props, VerifiedCommand }; + /// # use wca::{ executor::{ Routine, Handler, Props }, VerifiedCommand }; /// let routine = Routine::from( Handler::from /// ( /// | o : VerifiedCommand | @@ -106,7 +113,7 @@ mod private /// Returns owned casted value by its key /// /// ``` - /// # use wca::{ Props, Value }; + /// # use wca::{ executor::Props, Value }; /// /// let props = Props( [ ( "hello".to_string(), Value::String( "World!".to_string() ) ) ].into() ); /// let hello_prop : &str = props.get_owned( "hello" ).unwrap(); @@ -132,7 +139,10 @@ mod private // aaa : done. now it works with the following variants: // fn(), fn(args), fn(props), fn(args, props), fn(context), fn(context, args), fn(context, props), fn(context, args, props) - // qqq : why not public? + // aaa : why not public? // aaa : described + + // These type aliases are kept private to hide implementation details and prevent misuse. + // Exposing them would risk complicating the API and limit future refactoring flexibility. type RoutineWithoutContextFn = dyn Fn( VerifiedCommand ) -> error::untyped::Result< () >; type RoutineWithContextFn = dyn Fn( Context, VerifiedCommand ) -> error::untyped::Result< () >; @@ -140,7 +150,7 @@ mod private /// Routine handle. /// /// ``` - /// # use wca::{ Handler, Routine }; + /// # use wca::executor::{ Handler, Routine }; /// let routine = Routine::from( Handler::from /// ( /// || @@ -151,7 +161,7 @@ mod private /// ``` /// /// ``` - /// # use wca::{ Handler, Routine, VerifiedCommand }; + /// # use wca::{ executor::{ Handler, Routine }, VerifiedCommand }; /// let routine = Routine::from( Handler::from /// ( /// | o : VerifiedCommand | @@ -162,7 +172,7 @@ mod private /// ``` /// /// ``` - /// # use wca::{ Handler, Routine }; + /// # use wca::executor::{ Handler, Routine }; /// let routine = Routine::from( Handler::from /// ( /// | ctx, o | @@ -170,12 +180,11 @@ mod private /// // Do what you need to do /// } /// ) ); - pub struct Handler< I, O >( Box< dyn Fn( I ) -> O > ); - impl< I, O > std::fmt::Debug for Handler< I, O > + impl< I, O > core::fmt::Debug for Handler< I, O > { - fn fmt( &self, f : &mut Formatter< '_ > ) -> std::fmt::Result + fn fmt( &self, f : &mut Formatter< '_ > ) -> core::fmt::Result { f.debug_struct( "Handler" ).finish_non_exhaustive() } @@ -194,9 +203,9 @@ mod private } impl< F, R > From< F > for Handler< VerifiedCommand, R > - where - R : IntoResult + 'static, - F : Fn( VerifiedCommand ) -> R + 'static, + where + R : IntoResult + 'static, + F : Fn( VerifiedCommand ) -> R + 'static, { fn from( value : F ) -> Self { @@ -243,7 +252,7 @@ mod private /// /// - `WithoutContext`: A routine that does not require any context. /// - `WithContext`: A routine that requires a context. -// qqq : for Bohdan : instead of array of Enums, lets better have 5 different arrays of different Routine and no enum +// xxx clarification is needed : for Bohdan : instead of array of Enums, lets better have 5 different arrays of different Routine and no enum // to use statical dispatch #[ derive( Clone ) ] pub enum Routine @@ -254,9 +263,9 @@ mod private WithContext( Rc< RoutineWithContextFn > ), } - impl std::fmt::Debug for Routine + impl core::fmt::Debug for Routine { - fn fmt( &self, f : &mut Formatter< '_ > ) -> std::fmt::Result + fn fmt( &self, f : &mut Formatter< '_ > ) -> core::fmt::Result { match self { @@ -309,7 +318,7 @@ mod private { // We can't compare closures. Because every closure has a separate type, even if they're identical. // Therefore, we check that the two Rc's point to the same closure (allocation). - #[ allow( clippy::vtable_address_comparisons ) ] + #[ allow( ambiguous_wide_pointer_comparisons ) ] match ( self, other ) { ( Routine::WithContext( this ), Routine::WithContext( other ) ) => Rc::ptr_eq( this, other ), @@ -327,15 +336,25 @@ mod private } // xxx - impl IntoResult for std::convert::Infallible { fn into_result( self ) -> error::untyped::Result< () > { Ok( () ) } } + // aaa : This is an untyped error because we want to provide a common interface for all commands, while also allowing users to propagate their own specific custom errors. + impl IntoResult for core::convert::Infallible { fn into_result( self ) -> error::untyped::Result< () > { Ok( () ) } } impl IntoResult for () { fn into_result( self ) -> error::untyped::Result< () > { Ok( () ) } } - impl< E : std::fmt::Debug > IntoResult + impl< E : core::fmt::Debug + std::fmt::Display + 'static > IntoResult for error::untyped::Result< (), E > { fn into_result( self ) -> error::untyped::Result< () > { - self.map_err( | e | error::untyped::format_err!( "{e:?}" )) - // xxx : qqq : ? + use std::any::TypeId; + // if it's anyhow error we want to have full context(debug), and if it's not(this error) we want to display + if TypeId::of::< error::untyped::Error >() == TypeId::of::< E >() + { + self.map_err( | e | error::untyped::format_err!( "{e:?}" )) + } + else + { + self.map_err( | e | error::untyped::format_err!( "{e}" )) + } + // xxx : aaa : ? } } } @@ -344,8 +363,8 @@ mod private crate::mod_interface! { - exposed use Routine; - exposed use Handler; - exposed use Args; - exposed use Props; + orphan use Routine; + orphan use Handler; + orphan use Args; + orphan use Props; } diff --git a/module/move/wca/src/ca/facade.rs b/module/move/wca/src/ca/facade.rs deleted file mode 100644 index 80fca20afc..0000000000 --- a/module/move/wca/src/ca/facade.rs +++ /dev/null @@ -1,345 +0,0 @@ -// mod private -// { -// use crate::*; -// use core::fmt; -// use ca::grammar; -// -// /// Macro for parsing WCA arguments. -// /// -// /// # Examples -// /// ```rust -// /// use wca::Value; -// /// -// /// let mut args = vec![ Value::Number( 42. ), Value::String( "Rust".into() ) ].into_iter(); -// /// wca::parse_args!( args, n : f64, name : String ); -// /// -// /// assert_eq!( n, 42. ); -// /// assert_eq!( name, "Rust" ); -// /// ``` -// #[macro_export] -// macro_rules! parse_args -// { -// ( $args : ident, mut $b : ident : $ty : ident $( $rest : tt )* ) => -// { -// let mut $b : $ty = std::convert::TryFrom::try_from( $args.next().unwrap() ).unwrap(); -// $crate::parse_args!( $args $( $rest )* ) -// }; -// ( $args : ident, $b : ident : $ty : ident $( $rest : tt )* ) => -// { -// let $b : $ty = std::convert::TryFrom::try_from( $args.next().unwrap() ).unwrap(); -// $crate::parse_args!( $args $( $rest )* ) -// }; -// ( $args : ident, $b : ident $( $rest : tt )* ) => -// { -// let $b = $args.next().unwrap(); -// $crate::parse_args!( $args $( $rest )* ) -// }; -// ( $args : ident, mut $b : ident $( $rest : tt )* ) => -// { -// let mut $b = $args.next().unwrap(); -// $crate::parse_args!( $args $( $rest )* ) -// }; -// ( $args : ident ) => -// { -// assert!( $args.next().is_none() ); -// }; -// ( $args : ident, ) => -// { -// $crate::parse_args!( $args ) -// }; -// } -// -// /// Creates a command-line interface (CLI) builder with the given initial state. -// /// -// /// This function initializes a `CommandBuilder` with the provided `state` and -// /// returns it for further configuration of the CLI. -// pub fn cui< T >( state : T ) -> CommandBuilder< T > -// { -// CommandBuilder::with_state( state ) -// } -// -// /// A struct representing a property. -// #[ derive( Debug, Clone ) ] -// pub struct Property< 'a > -// { -// /// The name of the property. -// pub name : &'a str, -// /// The hint for the property. -// pub debug : &'a str, -// /// The tag representing the property's type. -// pub tag : Type, -// } -// -// impl< 'a > Property< 'a > -// { -// /// Constructor of a property. -// pub fn new( name : &'a str, hint : &'a str, tag : Type ) -> Self { Self { name, hint, tag } } -// } -// -// /// A builder struct for constructing commands. -// #[ derive( Debug ) ] -// pub struct CommandBuilder< T > -// { -// state : T, -// commands : Vec< Command >, -// handlers : std::collections::HashMap< String, Routine >, -// } -// -// impl< T > CommandBuilder< T > -// { -// /// Constructs a `CommandBuilder` with the given state. -// pub fn with_state( state : T ) -> Self -// { -// Self { state, handlers : < _ >::default(), commands : vec![] } -// } -// } -// -// #[ derive( Debug ) ] -// pub struct Builder< F > -// { -// handler : F, -// command : Command, -// } -// -// impl< F > Builder< F > -// { -// /// Creates a new instance of the command with the provided handler function. -// /// -// /// This method takes in a handler function `handler` and creates a new instance of the command. -// /// The `handler` function is used to handle the execution logic associated with the command. -// /// -// /// # Arguments -// /// -// /// * `handler` - The handler function that will be invoked when the command is executed. -// /// -// /// # Returns -// /// -// /// A new instance of the command with the specified `handler`. -// /// -// #[ inline ] -// pub fn new( handler: F ) -> Self -// { -// let name = -// { -// use iter_tools::Itertools as _; -// -// let name = std::any::type_name::< F >(); -// let name = name.split("::").last().unwrap(); -// name.split( '_' ).join( "." ) -// }; -// -// Self { handler, command : Command::former().phrase( name ).form() } -// } -// -// /// Adds an argument to the command. -// /// -// /// This method takes in the `hint` and `tag` parameters to create a `ValueDescription` object -// /// representing an argument. The `ValueDescription` object is then appended to the command's -// /// `subjects` collection. -// /// -// /// # Arguments -// /// -// /// * `hint` - The hint for the argument, represented as a string slice (`&str`). -// /// * `tag` - The type of the argument, represented by a `Type` object from the `Type` module. -// /// -// /// # Returns -// /// -// /// The modified command instance with the argument added. -// /// -// #[ inline ] -// pub fn arg( mut self, hint : &str, tag : Type ) -> Self -// { -// self.command.subjects.push( grammar::command::ValueDescription -// { -// hint : hint.into(), -// kind : tag, -// optional : false, -// }); -// -// self -// } -// -// /// Adds a property to the command. -// /// -// /// This method takes in the `name`, `hint`, and `kind` parameters to create a `ValueDescription` -// /// object representing a property. The `ValueDescription` object is then inserted into the -// /// command's properties collection using the `name` as the key. -// /// -// /// # Example -// /// ```no_rust -// /// let ca = cui(()) -// /// .command(user.property("name", "Name property", Type::String)) -// /// .build(); -// /// ``` -// /// -// /// # Arguments -// /// -// /// * `name` - The name of the property. It should implement the `ToString` trait. -// /// * `hint` - The hint for the property. It should implement the `ToString` trait. -// /// * `kind` - The type of the property, represented by a `Type` object from the `Type` module. -// /// -// /// # Returns -// /// -// /// The modified command instance with the property added. -// /// -// #[ inline ] -// pub fn property( mut self, name : impl ToString , hint : impl ToString, kind : Type ) -> Self -// { -// self.command.properties.insert -// ( -// name.to_string(), -// grammar::command::ValueDescription -// { -// hint : hint.to_string(), -// kind, -// optional : false, -// } -// ); -// -// self -// } -// -// /// Adds multiple properties to the command. -// /// -// /// This method takes in an array of `Property` objects and adds them to the command's properties. -// /// The properties are provided in the `properties` parameter as an array of length `N`. -// /// -// /// ```without_std -// /// let ca = cui(()) -// /// .properties([ -// /// Property::new("name", "Name property", Type::String), -// /// Property::new("age", "Age property", Type::Integer), -// /// ]).build(); -// /// ``` -// /// -// /// # Arguments -// /// -// /// * `properties` - An array of `Property` objects representing the properties to be added. -// /// -// /// # Returns -// /// -// /// The modified command instance with the properties added. -// /// -// #[ inline ] -// pub fn properties< const N: usize >( mut self, properties : [ Property< '_ >; N ] ) -> Self -// { -// self.command.properties.reserve( properties.len() ); -// -// for Property { name, hint, tag } in properties -// { -// self = self.property(name, hint, tag); -// } -// -// self -// } -// } -// -// impl< T: Clone + 'static > CommandBuilder< T > -// { -// /// Adds a command to the `CommandBuilder`. -// /// ```no_rust -// /// let ca = cui( () ) // Add commands using the builder pattern -// /// .command( command ) -// /// .command( command2 ) -// /// .command( echo.arg("string", Type::String ) ) // Customize your commands by chaining methods such as properties -// /// // property, and arg to add properties and arguments. -// /// .build(); -// /// -// /// ``` -// pub fn command< F, E > -// ( -// mut self, -// command : impl IntoBuilder< F, T >, -// ) -> Self -// where -// F : Fn( T, Args, Props ) -> Result< (), E > + 'static + Copy, -// E : fmt::Debug, -// { -// let Builder { handler, command } = command.into_builder(); -// let state = self.state.clone(); -// -// let closure = closure::closure!( | ( args, props ) | -// { -// handler( state.clone(), args, props ) -// .map_err( | report | BasicError::new( format!( "{report:?}" ) ).into() ) -// }); -// -// let handler = Routine::new( closure ); -// -// self.handlers.insert( command.phrase.clone(), handler ); -// self.commands.push( command ); -// -// self -// } -// -// /// Builds and returns a `wca::CommandsAggregator` instance. -// /// -// /// This method finalizes the construction of the `CommandBuilder` by -// /// creating a `wca::CommandsAggregator` instance with the accumulated -// /// commands and handlers. -// pub fn build( self ) -> CommandsAggregator -// { -// CommandsAggregator::former().grammar( self.commands ).executor( self.handlers ).perform() -// } -// } -// -// /// An extension trait for commands. -// /// -// /// This trait provides additional methods for enhancing commands, such as -// /// adding arguments and properties. -// pub trait CommandExt< T > : Sized -// { -// /// Adds an argument to the command. -// fn arg( self, hint : &str, tag : Type ) -> Builder< Self > -// { -// Builder::new( self ).arg( hint, tag ) -// } -// -// /// Adds property to the command. -// fn property< const N: usize >( self, name : impl ToString , hint : impl ToString, kind : Type ) -> Builder< Self > -// { -// Builder::new( self ).property( name, hint, kind ) -// } -// -// /// Adds properties to the command. -// fn properties< const N: usize >( self, properties: [ Property< '_ >; N ] ) -> Builder< Self > -// { -// Builder::new( self ).properties( properties ) -// } -// } -// -// impl< F: Fn( T, Args, Props ) -> Result< (), E>, T, E > CommandExt< T > for F {} -// -// /// A trait for converting a type into a `Builder`. -// pub trait IntoBuilder< F, T > : Sized -// { -// /// Converts the type into a `Builder` instance. -// fn into_builder( self ) -> Builder< F >; -// } -// -// impl< F, T > IntoBuilder< F, T > for Builder< F > -// { -// fn into_builder( self ) -> Self -// { -// self -// } -// } -// -// impl< F: Fn( T, Args, Props ) -> Result< (), E >, T, E > IntoBuilder< F, T > for F -// { -// fn into_builder( self ) -> Builder< F > -// { -// Builder::new( self ) -// } -// } -// -// } -// -// crate::mod_interface! -// { -// exposed use cui; -// exposed use CommandBuilder; -// exposed use Property; -// prelude use IntoBuilder; -// prelude use CommandExt; -// } diff --git a/module/move/wca/src/ca/formatter.rs b/module/move/wca/src/ca/formatter.rs index 59fb4b31ff..e3cc4d69fe 100644 --- a/module/move/wca/src/ca/formatter.rs +++ b/module/move/wca/src/ca/formatter.rs @@ -1,25 +1,40 @@ mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use iter_tools::Itertools; - use ca::aggregator::private::Order; + use ca::aggregator::Order; + use grammar::Dictionary; - /// - + /// Enum representing the format options for generating help content. + /// + /// `HelpFormat` defines the output format of help content, enabling the choice + /// between different styles, such as `Markdown` for structured text, or other + /// custom formats. #[ derive( Debug, Clone, PartialEq ) ] pub enum HelpFormat { + /// Generates help content in Markdown format, suitable for environments + /// that support Markdown rendering (e.g., documentation platforms, text editors). Markdown, + /// Represents an alternative format, customizable for different needs. Another, } + /// Generates Markdown-formatted help content based on a dictionary of terms and a specified order. + /// + /// The `md_generator` function takes a reference to a `Dictionary` and an `Order` to produce + /// a help document in Markdown format. This function is useful for generating structured, + /// readable help documentation suitable for Markdown-compatible platforms. + #[ must_use ] pub fn md_generator( grammar : &Dictionary, order: Order ) -> String { let text = grammar.commands() .into_iter() - .map( |( name, cmd )| + .map( | ( name, cmd ) | { - let subjects = cmd.subjects.iter().fold( String::new(), | _, _ | format!( " `[argument]`" ) ); + let subjects = cmd.subjects.iter().fold( String::new(), | _, _ | " `[argument]`".to_string() ); let properties = if cmd.properties.is_empty() { " " } else { " `[properties]` " }; format! ( @@ -35,18 +50,16 @@ mod private format!( "{acc}\n- {cmd}" ) }); - let list_of_commands = format!( "## Commands\n\n{}", text ); + let list_of_commands = format!( "## Commands\n\n{text}" ); let about_each_command = grammar.commands() .into_iter() - .map( |( name, cmd )| + .map( | ( name, cmd ) | { - let subjects = cmd.subjects.iter().fold( String::new(), | _, _ | format!( " `[Subject]`" ) ); + let subjects = cmd.subjects.iter().fold( String::new(), | _, _ | " `[Subject]`".to_string() ); let properties = if cmd.properties.is_empty() { " " } else { " `[properties]` " }; let hint = if cmd.hint.is_empty() { &cmd.long_hint } else { &cmd.hint }; - - let heading = format!( "## .{}{subjects}{properties}\n__{}__\n", name, hint ); - + let heading = format!( "## .{name}{subjects}{properties}\n__{hint}__\n" ); let hint = if cmd.long_hint.is_empty() { &cmd.hint } else { &cmd.long_hint }; let full_subjects = cmd .subjects @@ -54,7 +67,7 @@ mod private .enumerate() .map ( - |( number, subj )| + | ( number, subj ) | format!( "\n- {}subject_{number} - {} `[{:?}]`", if subj.optional { "`< optional >` " } else { "" }, subj.hint, subj.kind ) ) .join( "\n" ); @@ -63,7 +76,7 @@ mod private .into_iter() .map ( - |( name, value )| + | ( name, value ) | format!( "\n- {}{} - {} `[{:?}]`", if value.optional { "`< optional >` " } else { "" }, value.hint, name, value.kind ) ) .join( "\n" ); @@ -73,8 +86,8 @@ mod private format! ( "{heading}\n{}{}\n\n{hint}\n", - if cmd.subjects.is_empty() { "".to_string() } else { format!( "\n\nSubjects:{}", &full_subjects ) }, - if cmd.properties.is_empty() { "".to_string() } else { format!( "\n\nProperties:{}",&full_properties ) }, + if cmd.subjects.is_empty() { String::new() } else { format!( "\n\nSubjects:{}", &full_subjects ) }, + if cmd.properties.is_empty() { String::new() } else { format!( "\n\nProperties:{}",&full_properties ) }, ) }) @@ -91,5 +104,6 @@ mod private crate::mod_interface! { - + own use HelpFormat; + own use md_generator; } \ No newline at end of file diff --git a/module/move/wca/src/ca/grammar/command.rs b/module/move/wca/src/ca/grammar/command.rs index 3bfbd7a695..31b4568112 100644 --- a/module/move/wca/src/ca/grammar/command.rs +++ b/module/move/wca/src/ca/grammar/command.rs @@ -1,11 +1,14 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; - use std::collections::{ HashMap }; + use std::collections::HashMap; use indexmap::IndexMap; use former::{ Former, StoragePreform }; use iter_tools::Itertools; + use executor::{ Routine, Handler }; /// A description of a Value in a command. Used to specify the expected type and provide a hint for the Value. /// @@ -35,7 +38,7 @@ mod private pub struct PropertyDescription { name : String, - // qqq : how to re-use ValueDescriptionFormer without additional end? + // xxx : how to re-use ValueDescriptionFormer without additional end? // #[subform_scalar] // value : ValueDescription, /// providing guidance to the user for entering a valid value @@ -74,7 +77,7 @@ mod private /// # Example: /// /// ``` - /// # use wca::{ Command, Type }; + /// # use wca::{ grammar::Command, Type }; /// let command = Command::former() /// .hint( "hint" ) /// .long_hint( "long_hint" ) @@ -103,10 +106,14 @@ mod private /// Map of aliases. // Aliased key -> Original key pub properties_aliases : HashMap< String, String >, - // qqq : make it usable and remove default(?) + // aaa : make it usable and remove default(?) + // aaa : it is usable /// The type `Routine` represents the specific implementation of the routine. #[ scalar( setter = false ) ] - #[ former( default = Routine::from( Handler::< _, std::convert::Infallible >::from( || { panic!( "No routine available: A handler function for the command is missing" ) } ) ) ) ] + #[ former( default = Routine::from( Handler::< _, std::convert::Infallible >::from( || + { + panic!( "No routine available: A handler function for the command is missing" ) + })))] pub routine : Routine, } @@ -118,11 +125,11 @@ mod private { Order::Nature => { - self.properties.iter().map( | ( key, value ) | ( key, value ) ).collect() + self.properties.iter().collect() } Order::Lexicography => { - self.properties.iter().map( | ( key, value ) | ( key, value ) ).sorted_by_key( | ( k, _ ) | *k ).collect() + self.properties.iter().sorted_by_key( | ( k, _ ) | *k ).collect() } } } @@ -133,6 +140,7 @@ mod private Definition : former::FormerDefinition< Storage = < Command as former::EntityToStorage >::Storage >, { /// Setter for separate properties aliases. + #[ must_use ] pub fn property_alias< S : Into< String > >( mut self, key : S, alias : S ) -> Self { let key = key.into(); @@ -175,6 +183,7 @@ mod private /// # Returns /// /// Returns the `CommandFormer` instance with the new command routine set. + #[ must_use ] pub fn routine< I, R, F : Into< Handler< I, R > > >( mut self, f : F ) -> Self where Routine: From< Handler< I, R > >, @@ -207,7 +216,10 @@ mod private /// # Arguments /// /// * `name` - The name of the property. It should implement the `Into< String >` trait. - pub fn property< IntoName >( self, name : IntoName ) -> PropertyDescriptionAsSubformer< Self, impl PropertyDescriptionAsSubformerEnd< Self > > + /// # Panics + /// qqq: doc + pub fn property< IntoName >( self, name : IntoName ) + -> PropertyDescriptionAsSubformer< Self, impl PropertyDescriptionAsSubformerEnd< Self > > where IntoName : Into< String >, { @@ -246,9 +258,15 @@ mod private crate::mod_interface! { - exposed use Command; - exposed use CommandFormer; + orphan use Command; + orphan use CommandFormer; own use ValueDescription; + + own use CommandAsSubformer; + own use CommandAsSubformerEnd; + own use CommandFormerStorage; + } -// qqq : use orphan instead of exposed for ALL files in the folder, dont use prelude for structs \ No newline at end of file +// aaa : use orphan instead of exposed for ALL files in the folder, dont use prelude for structs +// aaa : done. \ No newline at end of file diff --git a/module/move/wca/src/ca/grammar/dictionary.rs b/module/move/wca/src/ca/grammar/dictionary.rs index e6887aef26..3e8e0389a5 100644 --- a/module/move/wca/src/ca/grammar/dictionary.rs +++ b/module/move/wca/src/ca/grammar/dictionary.rs @@ -1,11 +1,14 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use former::Former; use indexmap::IndexMap; use iter_tools::Itertools; + use grammar::Command; - // qqq : `Former` does not handle this situation well + // xxx : `Former` does not handle this situation well // /// A collection of commands. // /// @@ -25,8 +28,6 @@ mod private pub( crate ) order : Order, } - // qqq : IDK how to integrate it into the `CommandsAggregatorFormer` - // impl DictionaryFormer { pub fn command( mut self, command : Command ) -> Self @@ -88,17 +89,18 @@ mod private } /// asd + #[ must_use ] pub fn commands( &self ) -> Vec< ( &String, &Command ) > { match self.order { Order::Nature => { - self.commands.iter().map( | ( key, value ) | ( key, value ) ).collect() + self.commands.iter().collect() } Order::Lexicography => { - self.commands.iter().map( | ( key, value ) | ( key, value ) ).sorted_by_key( | ( key, _ ) | *key ).collect() + self.commands.iter().sorted_by_key( | ( key, _ ) | *key ).collect() } } } @@ -109,5 +111,5 @@ mod private crate::mod_interface! { - exposed use Dictionary; + orphan use Dictionary; } diff --git a/module/move/wca/src/ca/grammar/mod.rs b/module/move/wca/src/ca/grammar/mod.rs index f31e992b38..28a87f9e2b 100644 --- a/module/move/wca/src/ca/grammar/mod.rs +++ b/module/move/wca/src/ca/grammar/mod.rs @@ -1,3 +1,5 @@ +mod private {} + crate::mod_interface! { /// User grammar settings. diff --git a/module/move/wca/src/ca/grammar/types.rs b/module/move/wca/src/ca/grammar/types.rs index d5c6e971df..55bda746fe 100644 --- a/module/move/wca/src/ca/grammar/types.rs +++ b/module/move/wca/src/ca/grammar/types.rs @@ -1,14 +1,13 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use std::fmt:: { Display, Formatter }; - // use wtools; - // use wtools::{ error::Result, err }; - use error::err; use iter_tools::Itertools; /// Available types that can be converted to a `Value` @@ -47,6 +46,8 @@ mod private pub trait TryCast< T > { /// return casted value + /// # Errors + /// qqq: doc fn try_cast( &self, value : String ) -> error::untyped::Result< T >; } @@ -59,7 +60,7 @@ mod private /// # Example: /// /// ``` - /// # use wca::{ VerifiedCommand, Value, Args, Props }; + /// # use wca::{ VerifiedCommand, Value, executor::{ Args, Props } }; /// # use std::collections::HashMap; /// let command = VerifiedCommand /// { @@ -97,7 +98,7 @@ mod private impl Display for Value { - fn fmt( &self, f : &mut Formatter< '_ >) -> std::fmt::Result + fn fmt( &self, f : &mut Formatter< '_ > ) -> std::fmt::Result { match self { @@ -119,7 +120,7 @@ mod private } Value::List( list ) => { - let list = list.iter().map( | element | element.to_string() ).join( "," ); // qqq : don't hardcode ", " find way to get original separator + let list = list.iter().map( std::string::ToString::to_string ).join( "," ); write!( f, "{list}" )?; } } @@ -129,7 +130,7 @@ mod private macro_rules! value_into_impl { - ( $( $value_kind : path => $( $kind : ty => $cast : expr ),+ );+ ) => + ( $( $value_kind : path => $( $kind : ty => $cast : expr ), + ); + ) => { $( $( impl From< Value > for $kind @@ -138,7 +139,7 @@ mod private { match value { - #[ allow( clippy::redundant_closure_call ) ] // ok because of it improve understanding what is `value` at macro call + #[ allow( clippy::redundant_closure_call, clippy::cast_possible_truncation, clippy::cast_sign_loss ) ] // ok because of it improve understanding what is `value` at macro call $value_kind( value ) => ( $cast )( value ), _ => panic!( "Unknown cast variant. Got `{value:?}` and try to cast to `{}`", stringify!( $kind ) ) } @@ -173,8 +174,8 @@ mod private { match value { - Value::List( value ) => value.into_iter().map( | x | x.into() ).collect(), - _ => panic!( "Unknown cast variant. Got `{value:?}` and try to cast to `Vec<{}>`", std::any::type_name::< T >() ) + Value::List( value ) => value.into_iter().map( std::convert::Into::into ).collect(), + _ => panic!( "Unknown cast variant. Got `{value:?}` and try to cast to `Vec<{}>`", core::any::type_name::< T >() ) } } } @@ -186,16 +187,27 @@ mod private match self { Self::String => Ok( Value::String( value ) ), - Self::Number => value.parse().map_err( | _ | err!( "Can not parse number from `{}`", value ) ).map( Value::Number ), + Self::Number => value.parse().map_err( | _ | + { + error::untyped::format_err!( "Can not parse number from `{}`", value ) + }).map( Value::Number ), Self::Path => Ok( Value::Path( value.into() ) ), - Self::Bool => Ok( Value::Bool( match value.as_str() { "1" | "true" => true, "0" | "false" => false, _ => return Err( err!( "Can not parse bool from `{}`", value ) ) } ) ), + Self::Bool => Ok( Value::Bool( match value.as_str() + { + "1" | "true" => true, "0" | "false" => false, _ => + { + return Err( error::untyped::format_err!( "Can not parse bool from `{}`", value ) ) + } + })), Self::List( kind, delimeter ) => { - let values = value + let values: error::untyped::Result< Vec< Value > > = value .split( *delimeter ) .map( | val | kind.try_cast( val.into() ) ) - .collect::< error::untyped::Result< Vec< Value > > >()?; - // qqq : avoid using fish notation whenever possible. review whole crate + .collect(); + let values = values?; + // aaa : avoid using fish notation whenever possible. review whole crate + // aaa : done Ok( Value::List( values ) ) }, } diff --git a/module/move/wca/src/ca/help.rs b/module/move/wca/src/ca/help.rs index b7d0593634..73fcbed05b 100644 --- a/module/move/wca/src/ca/help.rs +++ b/module/move/wca/src/ca/help.rs @@ -1,32 +1,40 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use ca:: { - Command, - Routine, Type, - formatter::private:: + formatter:: { HelpFormat, md_generator }, tool::table::format_table, }; + use verifier::VerifiedCommand; + use grammar::{ Command, Dictionary }; + use executor::Routine; use iter_tools::Itertools; use std::rc::Rc; use error::untyped::format_err; use former::Former; - // qqq : for Bohdan : it should transparent mechanist which patch list of commands, not a stand-alone mechanism + // aaa : for Bohdan : it should transparent mechanist which patch list of commands, not a stand-alone mechanism + // aaa : it is + /// Enum `LevelOfDetail` specifies the granularity of detail for rendering or processing: #[ derive( Debug, Default, Copy, Clone, PartialEq, Eq ) ] pub enum LevelOfDetail { + /// No detail (default). #[ default ] None, + /// Basic level of detail. Simple, + /// High level of detail. Detailed, } @@ -42,13 +50,13 @@ mod private /// Reresents how much information to display for the subjects /// /// - `None` - nothing - /// - `Simple` - + /// - `Simple` - < subjects > /// - `Detailed` - each subject with information about it. E.g. `` pub subject_detailing : LevelOfDetail, /// Reresents how much information to display for the properties /// /// - `None` - nothing - /// - `Simple` - + /// - `Simple` - < properties > /// - `Detailed` - each property with information about it. E.g. `` pub property_detailing : LevelOfDetail, /// Reresents how much information to display for the properties @@ -63,8 +71,19 @@ mod private pub order : Order, } - // qqq : for Barsik : make possible to change properties order - pub( crate ) fn generate_help_content( dictionary : &Dictionary, o : HelpGeneratorOptions< '_ > ) -> String + // aaa : for Barsik : make possible to change properties order + // aaa : order option + + /// Generates help content as a formatted string based on a given dictionary and options. + /// + /// This function takes a `Dictionary` of terms or commands and a `HelpGeneratorOptions` + /// struct to customize the help output, generating a user-friendly help message + /// or guide in `String` format. + /// # Panics + /// qqq: doc + #[ must_use ] + #[ allow( clippy::match_same_arms ) ] + pub fn generate_help_content( dictionary : &Dictionary, o : HelpGeneratorOptions< '_ > ) -> String { struct Row { @@ -88,31 +107,43 @@ mod private }; let subjects = match o.subject_detailing { - LevelOfDetail::None => "".into(), - _ if command.subjects.is_empty() => "".into(), + LevelOfDetail::None => String::new(), + _ if command.subjects.is_empty() => String::new(), LevelOfDetail::Simple => "< subjects >".into(), - LevelOfDetail::Detailed => command.subjects.iter().map( | v | format!( "< {}{:?} >", if v.optional { "?" } else { "" }, v.kind ) ).collect::< Vec< _ > >().join( " " ), + LevelOfDetail::Detailed => command.subjects.iter().map( | v | + { + format!( "< {}{:?} >", if v.optional { "?" } else { "" }, v.kind ) + }).collect::< Vec< _ > >().join( " " ), }; let properties = match o.property_detailing { - LevelOfDetail::None => "".into(), - _ if command.subjects.is_empty() => "".into(), + LevelOfDetail::None => String::new(), + _ if command.subjects.is_empty() => String::new(), LevelOfDetail::Simple => "< properties >".into(), - LevelOfDetail::Detailed => command.properties( dictionary.order ).iter().map( |( n, v )| format!( "< {}:{}{:?} >", if v.optional { "?" } else { "" }, n, v.kind ) ).collect::< Vec< _ > >().join( " " ), + LevelOfDetail::Detailed => command.properties( dictionary.order ).iter().map( | ( n, v ) | + { + format!( "< {}:{}{:?} >", if v.optional { "?" } else { "" }, n, v.kind ) + }).collect::< Vec< _ > >().join( " " ), }; let footer = if o.with_footer { - let full_subjects = command.subjects.iter().map( | subj | format!( "- {} [{}{:?}]", subj.hint, if subj.optional { "?" } else { "" }, subj.kind ) ).join( "\n\t" ); - let full_properties = format_table( command.properties( dictionary.order ).into_iter().map( | ( name, value ) | [ name.clone(), format!( "- {} [{}{:?}]", value.hint, if value.optional { "?" } else { "" }, value.kind ) ] ) ).unwrap().replace( '\n', "\n\t" ); + let full_subjects = command.subjects.iter().map( | subj | + { + format!( "- {} [{}{:?}]", subj.hint, if subj.optional { "?" } else { "" }, subj.kind ) + }).join( "\n\t" ); + let full_properties = format_table( command.properties( dictionary.order ).into_iter().map( | ( name, value ) | + { + [ name.clone(), format!( "- {} [{}{:?}]", value.hint, if value.optional { "?" } else { "" }, value.kind ) ] + })).unwrap().replace( '\n', "\n\t" ); format! ( "{}{}", - if command.subjects.is_empty() { "".to_string() } else { format!( "\nSubjects:\n\t{}", &full_subjects ) }, - if command.properties.is_empty() { "".to_string() } else { format!( "\nProperties:\n\t{}",&full_properties ) } + if command.subjects.is_empty() { String::new() } else { format!( "\nSubjects:\n\t{}", &full_subjects ) }, + if command.properties.is_empty() { String::new() } else { format!( "\nProperties:\n\t{}",&full_properties ) } ) - } else { "".into() }; + } else { String::new() }; Row { @@ -130,7 +161,7 @@ mod private format! ( "{}{}{}", - format_table([[ row.name, row.args, row.hint ]]).unwrap(), + format_table( [ [ row.name, row.args, row.hint ] ] ).unwrap(), if row.footer.is_empty() { "" } else { "\n" }, row.footer ) @@ -141,7 +172,7 @@ mod private { let rows = dictionary.commands() .into_iter() - .map( |( _, cmd )| cmd ) + .map( | ( _, cmd ) | cmd ) .map( for_single_command ) .map( | row | [ row.name, row.args, row.hint ] ); format_table( rows ).unwrap() @@ -165,6 +196,7 @@ mod private impl HelpVariants { /// Generates help commands + #[ allow( clippy::match_wildcard_for_single_variants ) ] pub fn generate( &self, helper : &HelpGeneratorFn, dictionary : &mut Dictionary, order : Order ) { match self @@ -183,6 +215,7 @@ mod private } // .help + #[ allow( clippy::unused_self ) ] fn general_help( &self, helper : &HelpGeneratorFn, dictionary : &mut Dictionary, order : Order ) { let phrase = "help".to_string(); @@ -235,10 +268,10 @@ mod private let help = Command::former() .hint( "prints information about existing commands" ) .property( "format" ) - .hint( "help generates in format witch you write" ) - .kind( Type::String ) - .optional( true ) - .end() + .hint( "help generates in format witch you write" ) + .kind( Type::String ) + .optional( true ) + .end() .phrase( &phrase ) .routine( routine ) .form(); @@ -247,6 +280,7 @@ mod private } // .help command_name + #[ allow( clippy::unused_self ) ] fn subject_command_help( &self, helper : &HelpGeneratorFn, dictionary : &mut Dictionary ) { let phrase = "help".to_string(); @@ -271,7 +305,7 @@ mod private let args = HelpGeneratorOptions::former() .command_prefix( "." ) - .for_commands([ cmd ]) + .for_commands( [ cmd ] ) .description_detailing( LevelOfDetail::Detailed ) .subject_detailing( LevelOfDetail::Simple ) .property_detailing( LevelOfDetail::Simple ) @@ -281,15 +315,23 @@ mod private println!( "Help command\n\n{text}" ); } - }; + } Ok::< _, error_tools::untyped::Error >( () ) }; let help = Command::former() .hint( "prints full information about a specified command" ) - .subject().hint( "command name" ).kind( Type::String ).optional( true ).end() - .property( "format" ).hint( "help generates in format witch you write" ).kind( Type::String ).optional( true ).end() + .subject() + .hint( "command name" ) + .kind( Type::String ) + .optional( true ) + .end() + .property( "format" ) + .hint( "help generates in format witch you write" ) + .kind( Type::String ) + .optional( true ) + .end() .phrase( &phrase ) .routine( routine ) .form(); @@ -357,7 +399,7 @@ mod private /// /// ``` /// # use wca::ca::help::{ HelpGeneratorOptions, HelpGeneratorFn }; - /// use wca::{ Command, Dictionary }; + /// use wca::grammar::{ Command, Dictionary }; /// /// fn my_help_generator( dictionary : &Dictionary, args : HelpGeneratorOptions< '_ > ) -> String /// { @@ -390,22 +432,23 @@ mod private where HelpFunction : Fn( &Dictionary, HelpGeneratorOptions< '_ > ) -> String + 'static { - Self( Rc::new( func ) ) + Self( Rc::new( func ) ) } } impl HelpGeneratorFn { /// Executes the function to generate help content + #[ must_use ] pub fn exec( &self, dictionary : &Dictionary, args : HelpGeneratorOptions< '_ > ) -> String { self.0( dictionary, args ) } } - impl std::fmt::Debug for HelpGeneratorFn + impl core::fmt::Debug for HelpGeneratorFn { - fn fmt( &self, f : &mut std::fmt::Formatter< '_ > ) -> std::fmt::Result + fn fmt( &self, f : &mut core::fmt::Formatter< '_ > ) -> core::fmt::Result { f.write_str( "HelpGenerator" ) } @@ -418,5 +461,9 @@ crate::mod_interface! { own use HelpGeneratorFn; own use HelpGeneratorOptions; + own use LevelOfDetail; + own use generate_help_content; + prelude use HelpVariants; + } diff --git a/module/move/wca/src/ca/input.rs b/module/move/wca/src/ca/input.rs index c2826f99ef..34d57ba2c9 100644 --- a/module/move/wca/src/ca/input.rs +++ b/module/move/wca/src/ca/input.rs @@ -1,13 +1,13 @@ mod private { - use std::io; - use std::io::Write; + use std::io::{ self, Write }; /// Ask use input from standard input. + #[ must_use ] pub fn ask( request : &str ) -> String { let mut response = String::new(); - print!( "{} : ", request ); + print!( "{request} : " ); io::stdout().flush().ok(); io::stdin().read_line( &mut response ).ok(); response.trim().to_string() @@ -78,6 +78,6 @@ mod private crate::mod_interface! { exposed use ask; - exposed use Input; - exposed use IntoInput; + orphan use Input; + orphan use IntoInput; } diff --git a/module/move/wca/src/ca/mod.rs b/module/move/wca/src/ca/mod.rs index 3526d2c8fa..66c6832f28 100644 --- a/module/move/wca/src/ca/mod.rs +++ b/module/move/wca/src/ca/mod.rs @@ -2,6 +2,8 @@ //! Commands aggregator library. //! +mod private {} + crate::mod_interface! { diff --git a/module/move/wca/src/ca/parser/command.rs b/module/move/wca/src/ca/parser/command.rs index 332c9e71f6..9d75b11655 100644 --- a/module/move/wca/src/ca/parser/command.rs +++ b/module/move/wca/src/ca/parser/command.rs @@ -25,7 +25,7 @@ mod private /// # Example: /// /// ``` - /// # use wca::ParsedCommand; + /// # use wca::parser::ParsedCommand; /// # use std::collections::HashMap; /// ParsedCommand /// { @@ -39,7 +39,7 @@ mod private /// }; /// ``` /// - /// In the above example, a `ParsedCommand` instance is created with the name "command", a single subject "subject_value", and one property "prop_name" with a raw value of "raw_prop_value". + /// In the above example, a `ParsedCommand` instance is created with the name "command", a single subject "`subject_value`", and one property "`prop_name`" with a raw value of "`raw_prop_value`". /// #[ derive( Default, Debug, Clone, PartialEq, Eq ) ] pub struct ParsedCommand @@ -57,6 +57,6 @@ mod private crate::mod_interface! { - exposed use Program; - exposed use ParsedCommand; + orphan use Program; + orphan use ParsedCommand; } diff --git a/module/move/wca/src/ca/parser/mod.rs b/module/move/wca/src/ca/parser/mod.rs index 6d21385d36..50322eee12 100644 --- a/module/move/wca/src/ca/parser/mod.rs +++ b/module/move/wca/src/ca/parser/mod.rs @@ -1,3 +1,5 @@ +mod private {} + crate::mod_interface! { /// This module defines a raw representation of parsed commands, providing a foundation for further processing and @@ -5,7 +7,7 @@ crate::mod_interface! /// a straightforward and easy-to-work-with format, allowing for efficient manipulation and subsequent conversion to /// other representations. layer command; - + /// This module is responsible for processing command-line arguments and parsing them into a raw representation of a /// program containing multiple parsed commands. The input list of arguments is transformed into a structured format, /// allowing the program to efficiently handle and manipulate the parsed commands. diff --git a/module/move/wca/src/ca/parser/parser.rs b/module/move/wca/src/ca/parser/parser.rs index 1efe959495..2d6327691b 100644 --- a/module/move/wca/src/ca/parser/parser.rs +++ b/module/move/wca/src/ca/parser/parser.rs @@ -1,15 +1,30 @@ mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use std::collections::HashMap; + use parser::{ Program, ParsedCommand }; - use error::{ return_err }; + // use error::{ return_err }; + + #[ allow( missing_docs ) ] + #[ derive( Debug, error::typed::Error ) ] + pub enum ParserError + { + #[ error( "Internal Error: {details}" ) ] + InternalError { details: String }, + #[ error( "Unexpected input. Expected: {expected}, found {input}" ) ] + UnexpectedInput { expected: String, input: String }, + } /// `Parser` is a struct used for parsing data. #[ derive( Debug ) ] pub struct Parser; + // fix clippy error too large return type + type ParsedArgs = ( Vec< String >, HashMap< String, String >, usize ); + impl Parser { /// Parses a vector of command line arguments and returns a `Program` containing the parsed commands. @@ -21,13 +36,16 @@ mod private /// # Returns /// /// Returns a `Result` with a `Program` containing the parsed commands if successful, or an error if parsing fails. - // qqq : use typed error - pub fn parse< As, A >( &self, args : As ) -> error::untyped::Result< Program< ParsedCommand > > + /// # Errors + /// qqq: doc + // aaa : use typed error + // aaa : done. + pub fn parse< As, A >( &self, args : As ) -> Result< Program< ParsedCommand >, ParserError > where As : IntoIterator< Item = A >, A : Into< String >, { - let args = args.into_iter().map( Into::into ).collect::< Vec< _ > >(); + let args : Vec< _ > = args.into_iter().map( Into::into ).collect(); let mut commands = vec![]; let mut i = 0; while i < args.len() @@ -45,7 +63,7 @@ mod private { if let Some( name ) = input.strip_prefix( '.' ) { - name.is_empty() || name.starts_with( '?' ) || name.chars().next().is_some_and( | c | c.is_alphanumeric() ) + name.is_empty() || name.starts_with( '?' ) || name.chars().next().is_some_and( char::is_alphanumeric ) } else { @@ -54,18 +72,19 @@ mod private } // returns ParsedCommand and relative position of the last parsed item - // qqq : use typed error - fn parse_command( args : &[ String ] ) -> error::untyped::Result< ( ParsedCommand, usize ) > + // aaa : use typed error + fn parse_command( args : &[ String ] ) -> Result< ( ParsedCommand, usize ), ParserError > { - if args.is_empty() { - return_err!( "Unexpected behaviour: Try to parse command without input" ); + if args.is_empty() + { + return Err( ParserError::InternalError { details: "Try to parse command without input".into() } ); } let mut i = 0; if !Self::valid_command_name( &args[ i ] ) { - return_err!( "Unexpected input: Expected a command, found: `{}`", args[ i ] ); + return Err( ParserError::UnexpectedInput { expected: "command".into(), input: args[ i ].clone() } ); } let name = match args[ i ].strip_prefix( '.' ).unwrap() { @@ -75,10 +94,9 @@ mod private }; i += 1; let ( subjects, properties, relative_pos ) = Self::parse_command_args( &args[ i .. ] )?; - i += relative_pos; - return Ok( + Ok( ( ParsedCommand { @@ -90,9 +108,13 @@ mod private )) } + + + // returns ( subjects, properties, relative_end_pos ) - // qqq : use typed error - fn parse_command_args( args : &[ String ] ) -> error::untyped::Result< ( Vec< String >, HashMap< String, String >, usize ) > + // aaa : use typed error + // aaa : done + fn parse_command_args( args : &[ String ] ) -> Result< ParsedArgs, ParserError > { let mut i = 0; @@ -125,7 +147,7 @@ mod private // prop: else { - return_err!( "Unexpected input '{}': Detected a possible property key preceding the ':' character. However, no corresponding value was found.", item ); + return Err( ParserError::UnexpectedInput { expected: "property value".into(), input: "end of input".into() } ); } } // prop : value | prop :value @@ -146,17 +168,22 @@ mod private // : else { - return_err!( "Unexpected input '{} :': Detected a possible property key preceding the ':' character. However, no corresponding value was found.", item ); + return Err( ParserError::UnexpectedInput { expected: "property value".into(), input: "end of input".into() } ); } } - else if !properties_turn { subjects.push( item.to_string() ); } - - else { return_err!( "Unexpected input: Expected `command` or `property`, found: `{}`", item ); } + else if !properties_turn + { + subjects.push( item.to_string() ); + } + else + { + return Err( ParserError::UnexpectedInput { expected: "`command` or `property`".into(), input: item.into() } ); + } i += 1; } - Ok(( subjects, properties, i )) + Ok( ( subjects, properties, i ) ) } } } @@ -165,5 +192,6 @@ mod private crate::mod_interface! { - exposed use Parser; + orphan use Parser; + orphan use ParserError; } diff --git a/module/move/wca/src/ca/tool/mod.rs b/module/move/wca/src/ca/tool/mod.rs index 116804b97d..3f400c96a9 100644 --- a/module/move/wca/src/ca/tool/mod.rs +++ b/module/move/wca/src/ca/tool/mod.rs @@ -1,3 +1,5 @@ +mod private {} + crate::mod_interface! { @@ -5,7 +7,10 @@ crate::mod_interface! layer table; orphan use super::super::tool; - orphan use ::error_tools as error; + + // orphan use ::error_tools as error; + use ::error_tools; + orphan use ::iter_tools; // use ::strs_tools as string; // xxx : check diff --git a/module/move/wca/src/ca/tool/table.rs b/module/move/wca/src/ca/tool/table.rs index 192caa0396..c8a2e9374f 100644 --- a/module/move/wca/src/ca/tool/table.rs +++ b/module/move/wca/src/ca/tool/table.rs @@ -1,9 +1,11 @@ mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; + use core::fmt::Write; // use wtools::error::{ Result, err }; - use error::err; + // use error::err; /// Represents a table composed of multiple rows. /// @@ -69,7 +71,7 @@ mod private fn max_column_lengths( table : &Table ) -> Vec< usize > { - let num_columns = table.0.get( 0 ).map_or( 0, | row | row.0.len() ); + let num_columns = table.0.first().map_or( 0, | row | row.0.len() ); ( 0 .. num_columns ) .map( | column_index | { @@ -81,6 +83,10 @@ mod private .collect() } + #[ derive( Debug, error::typed::Error ) ] + #[ error( "Invalid table" ) ] + pub struct FormatTableError; + /// Formats a table into a readable string representation. /// /// # Arguments @@ -90,15 +96,18 @@ mod private /// # Returns /// /// * `error::untyped::Result` - A `error::untyped::Result` containing the formatted table as a `String`, or an `Error` if the table is invalid. - // qqq : use typed error - pub fn format_table< IntoTable >( table : IntoTable ) -> error::untyped::Result< String > + /// # Errors + /// qqq: doc + // aaa : use typed error + // aaa : done + pub fn format_table< IntoTable >( table : IntoTable ) -> Result< String, FormatTableError > where IntoTable : Into< Table >, { let table = table.into(); if !table.validate() { - return Err( err!( "Invalid table" ) ); + return Err( FormatTableError ); } let max_lengths = max_column_lengths( &table ); @@ -108,7 +117,7 @@ mod private { for ( i, cell ) in row.0.iter().enumerate() { - formatted_table.push_str( &format!( "{:width$}", cell, width = max_lengths[ i ] ) ); + write!( formatted_table, "{:width$}", cell, width = max_lengths[ i ] ).expect( "Writing to String shouldn't fail" ); formatted_table.push( ' ' ); } formatted_table.pop(); // trailing space diff --git a/module/move/wca/src/ca/verifier/command.rs b/module/move/wca/src/ca/verifier/command.rs index ef8c2824b9..f52d54c897 100644 --- a/module/move/wca/src/ca/verifier/command.rs +++ b/module/move/wca/src/ca/verifier/command.rs @@ -1,13 +1,15 @@ mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; + use executor::{ Args, Props }; /// Represents a grammatically correct command with a phrase descriptor, a list of command subjects, and a set of command options. /// /// # Example: /// /// ``` - /// # use wca::{ VerifiedCommand, Value, Args, Props }; + /// # use wca::{ VerifiedCommand, Value, executor::{ Args, Props } }; /// # use std::collections::HashMap; /// VerifiedCommand /// { @@ -22,7 +24,7 @@ mod private /// }; /// ``` /// - /// In the above example, a `VerifiedCommand` instance is created with the name "command", a single subject "subject_value", and one property "prop_name" with a typed values. + /// In the above example, a `VerifiedCommand` instance is created with the name "command", a single subject "`subject_value`", and one property "`prop_name`" with a typed values. /// #[ derive( Debug, Clone ) ] pub struct VerifiedCommand @@ -46,4 +48,5 @@ crate::mod_interface! exposed use VerifiedCommand; } -// qqq : use orphan instead of exposed for ALL files in the folder, dont use prelude for structs \ No newline at end of file +// aaa : use orphan instead of exposed for ALL files in the folder, dont use prelude for structs +// aaa : done. \ No newline at end of file diff --git a/module/move/wca/src/ca/verifier/mod.rs b/module/move/wca/src/ca/verifier/mod.rs index 7ed35ae7b9..4723d0bdcc 100644 --- a/module/move/wca/src/ca/verifier/mod.rs +++ b/module/move/wca/src/ca/verifier/mod.rs @@ -1,3 +1,5 @@ +mod private {} + crate::mod_interface! { /// Represents a grammatically correct command with a phrase descriptor, a list of command subjects, and a set of command options.. diff --git a/module/move/wca/src/ca/verifier/verifier.rs b/module/move/wca/src/ca/verifier/verifier.rs index 6fc13fc28e..404433a130 100644 --- a/module/move/wca/src/ca/verifier/verifier.rs +++ b/module/move/wca/src/ca/verifier/verifier.rs @@ -1,20 +1,64 @@ mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; - - use ca::grammar::command::ValueDescription; - // use former::Former; + use help::{ HelpGeneratorOptions, LevelOfDetail, generate_help_content }; + use grammar::{ Dictionary, Command, command::ValueDescription }; + use executor::{ Args, Props }; use std::collections::HashMap; use indexmap::IndexMap; - // use wtools::{ error, error::Result, err }; - use error::err; - use ca::help::private::{ HelpGeneratorOptions, LevelOfDetail, generate_help_content }; + use verifier::VerifiedCommand; + use parser::{ Program, ParsedCommand }; + + #[ allow( missing_docs ) ] + #[ derive( Debug, error::typed::Error ) ] + pub enum VerificationError + { + #[ error + ( + "Command not found. {} {}", + if let Some( phrase ) = name_suggestion + { + format!( "Maybe you mean `.{phrase}`?" ) + } + else + { + "Please use `.` command to see the list of available commands.".into() + }, + // fix clippy + if let Some( info ) = command_info { format!( "Command info: `{info}`" ) } else { String::new() } + )] + CommandNotFound { name_suggestion: Option< String >, command_info: Option< String > }, + #[ error( "Fail in command `.{command_name}` while processing subjects. {error}" ) ] + Subject { command_name: String, error: SubjectError }, + #[ error( "Fail in command `.{command_name}` while processing properties. {error}" ) ] + Property { command_name: String, error: PropertyError }, + } + + #[ allow( missing_docs ) ] + #[ derive( Debug, error::typed::Error ) ] + pub enum SubjectError + { + #[ error( "Missing not optional subject" ) ] + MissingNotOptional, + #[ error( "Can not identify a subject: `{value}`" ) ] + CanNotIdentify { value: String }, + } + + #[ allow( missing_docs ) ] + #[ derive( Debug, error::typed::Error ) ] + pub enum PropertyError + { + #[ error( "Expected: {description:?}. Found: {input}" ) ] + Cast { description: ValueDescription, input: String }, + } + // xxx /// Converts a `ParsedCommand` to a `VerifiedCommand` by performing validation and type casting on values. /// /// ``` - /// # use wca::{ Command, Type, Verifier, Dictionary, ParsedCommand }; + /// # use wca::{ Type, verifier::Verifier, grammar::{ Dictionary, Command }, parser::ParsedCommand }; /// # use std::collections::HashMap; /// # fn main() -> Result< (), Box< dyn std::error::Error > > /// # { @@ -42,19 +86,23 @@ mod private /// Converts raw program to grammatically correct /// /// Converts all namespaces into it with `to_namespace` method. + /// # Errors + /// qqq: doc pub fn to_program ( &self, dictionary : &Dictionary, raw_program : Program< ParsedCommand > ) - -> error::untyped::Result< Program< VerifiedCommand > > - // qqq : use typed error + -> Result< Program< VerifiedCommand >, VerificationError > + // aaa : use typed error + // aaa : done { - let commands = raw_program.commands + let commands: Result< Vec< VerifiedCommand >, VerificationError > = raw_program.commands .into_iter() .map( | n | self.to_command( dictionary, n ) ) - .collect::< error::untyped::Result< Vec< VerifiedCommand > > >()?; + .collect(); + let commands = commands?; Ok( Program { commands } ) } @@ -89,8 +137,12 @@ mod private ) -> usize { raw_properties.iter() - .filter( |( k, _ )| !( properties.contains_key( *k ) || properties_aliases.get( *k ).map_or( false, | key | properties.contains_key( key ) ) ) ) - .count() + .filter( | ( k, _ ) | + { + // fix clippy + !( properties.contains_key( *k ) || properties_aliases.get( *k ).is_some_and( | key | properties.contains_key( key ) ) ) + }) + .count() } fn is_valid_command_variant( subjects_count : usize, raw_count : usize, possible_count : usize ) -> bool @@ -109,14 +161,15 @@ mod private if Self::is_valid_command_variant( expected_subjects_count, raw_subjects_count, possible_subjects_count ) { Some( variant ) } else { None } } - // qqq : use typed error + // aaa : use typed error + // aaa : done. fn extract_subjects( command : &Command, raw_command : &ParsedCommand, used_properties : &[ &String ] ) -> - error::untyped::Result< Vec< Value > > + Result< Vec< Value >, SubjectError > { let mut subjects = vec![]; - let all_subjects = raw_command + let all_subjects: Vec< _ > = raw_command .subjects.clone().into_iter() .chain ( @@ -124,7 +177,7 @@ mod private .filter( |( key, _ )| !used_properties.contains( key ) ) .map( |( key, value )| format!( "{key}:{value}" ) ) ) - .collect::< Vec< _ > >(); + .collect(); let mut rc_subjects_iter = all_subjects.iter(); let mut current = rc_subjects_iter.next(); @@ -134,20 +187,22 @@ mod private { Some( v ) => v, None if *optional => continue, - _ => return Err( err!( "Missing not optional subject" ) ), + _ => return Err( SubjectError::MissingNotOptional ), }; subjects.push( value ); current = rc_subjects_iter.next(); } - if let Some( value ) = current { return Err( err!( "Can not identify a subject: `{}`", value ) ) } + if let Some( value ) = current { return Err( SubjectError::CanNotIdentify { value: value.clone() } ) } Ok( subjects ) } - // qqq : use typed error + // aaa : use typed error + // aaa : done. + #[ allow( clippy::manual_map ) ] fn extract_properties( command: &Command, raw_command : HashMap< String, String > ) -> - error::untyped::Result< HashMap< String, Value > > + Result< HashMap< String, Value >, PropertyError > { raw_command.into_iter() .filter_map @@ -163,12 +218,12 @@ mod private .map ( |( value_description, key, value )| - value_description.kind.try_cast( value ).map( | v | ( key.clone(), v ) ) + value_description.kind.try_cast( value.clone() ).map( | v | ( key.clone(), v ) ).map_err( | _ | PropertyError::Cast { description: value_description.clone(), input: format!( "{key}: {value}" ) } ) ) - .collect::< error::untyped::Result< HashMap< _, _ > > >() + .collect() } - - fn group_properties_and_their_aliases< 'a, Ks >( aliases : &'a HashMap< String, String >, used_keys : Ks ) -> Vec< &String > + // fix clippy + fn group_properties_and_their_aliases< 'a, Ks >( aliases : &'a HashMap< String, String >, used_keys : Ks ) -> Vec<&'a String > where Ks : Iterator< Item = &'a String > { @@ -184,18 +239,23 @@ mod private used_keys.flat_map( | key | { - reverse_aliases.get( key ).into_iter().flatten().map( | k | *k ).chain( Some( key ) ) + reverse_aliases.get( key ).into_iter().flatten().copied().chain( Some( key ) ) }) - .collect::< Vec< _ > >() + .collect() } /// Converts raw command to grammatically correct /// /// Make sure that this command is described in the grammar and matches it(command itself and all it options too). - // qqq : use typed error + /// # Errors + /// qqq: doc + /// # Panics + /// qqq: doc + // aaa : use typed error + // aaa : done. pub fn to_command( &self, dictionary : &Dictionary, raw_command : ParsedCommand ) -> - error::untyped::Result< VerifiedCommand > + Result< VerifiedCommand, VerificationError > { if raw_command.name.ends_with( '.' ) | raw_command.name.ends_with( ".?" ) { @@ -207,35 +267,33 @@ mod private props : Props( HashMap::new() ), }); } + // fix clippy let command = dictionary.command( &raw_command.name ) - .ok_or_else::< error::untyped::Error, _ > - ( - || - { - #[ cfg( feature = "on_unknown_suggest" ) ] - if let Some( phrase ) = Self::suggest_command( dictionary, &raw_command.name ) - { return err!( "Command not found. Maybe you mean `.{}`?", phrase ) } - err!( "Command not found. Please use `.` command to see the list of available commands." ) + .ok_or( + { + #[ cfg( feature = "on_unknown_suggest" ) ] + if let Some( phrase ) = Self::suggest_command( dictionary, &raw_command.name ) { + return Err( VerificationError::CommandNotFound { name_suggestion: Some( phrase.to_string() ), command_info: None } ); } - )?; + VerificationError::CommandNotFound { name_suggestion: None, command_info: None } + })?; let Some( cmd ) = Self::check_command( command, &raw_command ) else { - error::untyped::bail! - ( - "`{}` command with specified subjects not found. Command info: `{}`", - &raw_command.name, - generate_help_content( dictionary, HelpGeneratorOptions::former().for_commands([ dictionary.command( &raw_command.name ).unwrap() ]).command_prefix( "." ).subject_detailing( LevelOfDetail::Detailed ).form() ).strip_suffix( " " ).unwrap() - ); + return Err( VerificationError::CommandNotFound + { + name_suggestion: Some( command.phrase.clone() ), + command_info: Some( generate_help_content( dictionary, HelpGeneratorOptions::former().for_commands([ dictionary.command( &raw_command.name ).unwrap() ]).command_prefix( "." ).subject_detailing( LevelOfDetail::Detailed ).form() ).strip_suffix( " " ).unwrap().into() ), + } ); }; - let properties = Self::extract_properties( cmd, raw_command.properties.clone() )?; + let properties = Self::extract_properties( cmd, raw_command.properties.clone() ).map_err( | e | VerificationError::Property { command_name: cmd.phrase.clone(), error: e } )?; let used_properties_with_their_aliases = Self::group_properties_and_their_aliases( &cmd.properties_aliases, properties.keys() ); - let subjects = Self::extract_subjects( cmd, &raw_command, &used_properties_with_their_aliases )?; + let subjects = Self::extract_subjects( cmd, &raw_command, &used_properties_with_their_aliases ).map_err( | e | VerificationError::Subject { command_name: cmd.phrase.clone(), error: e } )?; Ok( VerifiedCommand { - phrase : cmd.phrase.to_owned(), + phrase : cmd.phrase.clone(), internal_command : false, args : Args( subjects ), props : Props( properties ), @@ -248,5 +306,10 @@ mod private crate::mod_interface! { - exposed use Verifier; + orphan use Verifier; + orphan use VerificationError; + + // own use LevelOfDetail; + // own use generate_help_content; + } diff --git a/module/move/wca/src/lib.rs b/module/move/wca/src/lib.rs index c318aa58fd..2fcb2a8409 100644 --- a/module/move/wca/src/lib.rs +++ b/module/move/wca/src/lib.rs @@ -4,13 +4,12 @@ #![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] #![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "doc/", "wca.md" ) ) ] -#![ allow( where_clauses_object_safety ) ] // https://github.com/chris-morgan/anymap/issues/31 -// qqq : xxx : is it neccessary? - use mod_interface::mod_interface; pub mod ca; +mod private {} + crate::mod_interface! { use super::ca; diff --git a/module/move/wca/tests/inc/adapter.rs b/module/move/wca/tests/inc/adapter.rs deleted file mode 100644 index 33d5cd7e61..0000000000 --- a/module/move/wca/tests/inc/adapter.rs +++ /dev/null @@ -1,44 +0,0 @@ -use super::*; -use the_module::exposed::*; - -tests_impls! -{ - fn simple() - { - fn command( () : (), args : Args, props : Props) -> Result< (), () > - { - Ok( () ) - } - - fn command2( () : (), args : Args, props : Props ) -> Result< (), () > - { - Ok( () ) - } - - fn echo( () : (), args : Args, props : Props ) -> Result< (), () > - { - Ok( () ) - } - - let ca = the_module::cui( () ).command( command ).command( command2 ).command( echo.arg( "string", Type::String ) ).build(); - - a_id!( (), ca.perform( ".command2 .help" ).unwrap() ); - - a_id!( (), ca.perform( ".help command" ).unwrap() ); - a_id!( (), ca.perform( ".help command2" ).unwrap() ); - a_id!( (), ca.perform( ".help help" ).unwrap() ); - - a_id!( (), ca.perform( ".help.command" ).unwrap() ); - a_id!( (), ca.perform( ".help.command2" ).unwrap() ); - a_id!( (), ca.perform( ".help.help" ).unwrap() ); - - a_true!( ca.perform( ".help.help.help" ).is_err() ); - a_true!( ca.perform( ".echo 34" ).is_ok() ); - a_true!( ca.perform( ".echo" ).is_err() ); - } -} - -tests_index! -{ - simple -} diff --git a/module/move/wca/tests/inc/commands_aggregator/basic.rs b/module/move/wca/tests/inc/commands_aggregator/basic.rs index f7019bebf6..5f8464ff9b 100644 --- a/module/move/wca/tests/inc/commands_aggregator/basic.rs +++ b/module/move/wca/tests/inc/commands_aggregator/basic.rs @@ -1,7 +1,15 @@ use super::*; -use the_module::VerifiedCommand; +use the_module:: +{ + parser::Parser, + VerifiedCommand, + CommandsAggregator, + HelpVariants, + Type, + Error, + ValidationError, +}; -// tests_impls! { @@ -9,10 +17,10 @@ tests_impls! { let ca = CommandsAggregator::former() .command( "command" ) - .hint( "hint" ) - .long_hint( "long_hint" ) - .routine( || println!( "Command" ) ) - .end() + .hint( "hint" ) + .long_hint( "long_hint" ) + .routine( || println!( "Command" ) ) + .end() .perform(); a_id!( (), ca.perform( ".command" ).unwrap() ); // Parse -> Validate -> Execute @@ -22,11 +30,11 @@ tests_impls! { let ca = CommandsAggregator::former() .command( "command" ) - .hint( "hint" ) - .long_hint( "long_hint" ) - .routine( || println!( "Command" ) ) - .end() - .help_variants([ HelpVariants::General ]) + .hint( "hint" ) + .long_hint( "long_hint" ) + .routine( || println!( "Command" ) ) + .end() + .help_variants( [ HelpVariants::General ] ) .perform(); a_id!( (), ca.perform( ".help" ).unwrap() ); // raw string -> GrammarProgram -> ExecutableProgram -> execute @@ -40,35 +48,34 @@ tests_impls! { let ca = CommandsAggregator::former() .command( "cmd.first" ) - .hint( "hint" ) - .long_hint( "long_hint" ) - .routine( || println!( "Command" ) ) - .end() + .hint( "hint" ) + .long_hint( "long_hint" ) + .routine( || println!( "Command" ) ) + .end() .command( "cmd.second" ) - .hint( "hint" ) - .long_hint( "long_hint" ) - .routine( || println!( "Command2" ) ) - .end() + .hint( "hint" ) + .long_hint( "long_hint" ) + .routine( || println!( "Command2" ) ) + .end() .perform(); a_id!( (), ca.perform( "." ).unwrap() ); - // qqq : this use case is disabled - // a_id!( (), ca.perform( ".cmd." ).unwrap() ); + a_id!( (), ca.perform( ".cmd." ).unwrap() ); } fn error_types() { let ca = CommandsAggregator::former() .command( "command" ) - .hint( "hint" ) - .long_hint( "long_hint" ) - .routine( || println!( "command" ) ) - .end() + .hint( "hint" ) + .long_hint( "long_hint" ) + .routine( || println!( "command" ) ) + .end() .command( "command_with_execution_error" ) - .hint( "hint" ) - .long_hint( "long_hint" ) - .routine( || { println!( "command" ); Err( "runtime error" ) } ) - .end() + .hint( "hint" ) + .long_hint( "long_hint" ) + .routine( || { println!( "command" ); Err( "runtime error" ) } ) + .end() .perform(); a_true!( ca.perform( ".command" ).is_ok() ); @@ -110,11 +117,11 @@ tests_impls! { let ca = CommandsAggregator::former() .command( "command" ) - .hint( "hint" ) - .long_hint( "long_hint" ) - .subject().hint( "A path to directory." ).kind( Type::Path ).optional( true ).end() - .routine( || println!( "hello" ) ) - .end() + .hint( "hint" ) + .long_hint( "long_hint" ) + .subject().hint( "A path to directory." ).kind( Type::Path ).optional( true ).end() + .routine( || println!( "hello" ) ) + .end() .perform(); let command = vec![ ".command".into(), "./path:to_dir".into() ]; @@ -136,10 +143,10 @@ tests_impls! fn string_subject_with_colon() { - let dictionary = &the_module::Dictionary::former() + let dictionary = &the_module::grammar::Dictionary::former() .command ( - wca::Command::former() + wca::grammar::Command::former() .hint( "hint" ) .long_hint( "long_hint" ) .phrase( "command" ) @@ -150,7 +157,7 @@ tests_impls! ) .perform(); let parser = Parser; - let grammar = the_module::Verifier; + let grammar = the_module::verifier::Verifier; let executor = the_module::Executor::former().form(); let raw_command = parser.parse( [ ".command", "qwe:rty", "nightly:true" ] ).unwrap().commands.remove( 0 ); @@ -163,10 +170,10 @@ tests_impls! fn no_prop_subject_with_colon() { - let dictionary = &the_module::Dictionary::former() + let dictionary = &the_module::grammar::Dictionary::former() .command ( - the_module::Command::former() + the_module::grammar::Command::former() .hint( "hint" ) .long_hint( "long_hint" ) .phrase( "command" ) @@ -177,7 +184,7 @@ tests_impls! .form(); let parser = Parser; - let grammar = the_module::Verifier; + let grammar = the_module::verifier::Verifier; let executor = the_module::Executor::former().form(); let raw_command = parser.parse( [ ".command", "qwe:rty" ] ).unwrap().commands.remove( 0 ); @@ -190,10 +197,10 @@ tests_impls! fn optional_prop_subject_with_colon() { - let dictionary = &the_module::Dictionary::former() + let dictionary = &the_module::grammar::Dictionary::former() .command ( - the_module::Command::former() + the_module::grammar::Command::former() .hint( "hint" ) .long_hint( "long_hint" ) .phrase( "command" ) @@ -205,29 +212,30 @@ tests_impls! .form(); let parser = Parser; - let grammar = the_module::Verifier; + let grammar = the_module::verifier::Verifier; let executor = the_module::Executor::former().form(); let raw_command = parser.parse( [ ".command", "qwe:rty" ] ).unwrap().commands.remove( 0 ); let grammar_command = grammar.to_command( dictionary, raw_command ).unwrap(); - a_id!( grammar_command.args.0, vec![ the_module::Value::String("qwe:rty".into()) ] ); + a_id!( grammar_command.args.0, vec![ the_module::Value::String( "qwe:rty".into() ) ] ); a_id!( (), executor.command( dictionary, grammar_command ).unwrap() ); } - // qqq : make the following test work + // aaa : make the following test work + // aaa : works fn subject_with_spaces() { let query = "SELECT title, links, MIN( published ) FROM Frames"; let ca = CommandsAggregator::former() .command( "query.execute" ) - .hint( "hint" ) - .long_hint( "long_hint" ) - .subject().hint( "SQL query" ).kind( Type::String ).optional( false ).end() - .routine( move | o : VerifiedCommand | assert_eq!( query, o.args.get_owned::< &str >( 0 ).unwrap() ) ) - .end() + .hint( "hint" ) + .long_hint( "long_hint" ) + .subject().hint( "SQL query" ).kind( Type::String ).optional( false ).end() + .routine( move | o : VerifiedCommand | assert_eq!( query, o.args.get_owned::< &str >( 0 ).unwrap() ) ) + .end() .perform(); a_id!( (), ca.perform( vec![ ".query.execute".to_string(), query.into() ] ).unwrap() ); diff --git a/module/move/wca/tests/inc/commands_aggregator/callback.rs b/module/move/wca/tests/inc/commands_aggregator/callback.rs index 834426c32d..03f696263d 100644 --- a/module/move/wca/tests/inc/commands_aggregator/callback.rs +++ b/module/move/wca/tests/inc/commands_aggregator/callback.rs @@ -1,5 +1,6 @@ use super::*; use std::sync::{ Arc, Mutex }; +use the_module::CommandsAggregator; #[ test ] fn changes_state_of_local_variable_on_perform() @@ -9,15 +10,15 @@ fn changes_state_of_local_variable_on_perform() let ca_history = Arc::clone( &history ); let ca = CommandsAggregator::former() .command( "command" ) - .hint( "hint" ) - .long_hint( "long_hint" ) - .routine( || println!( "command" ) ) - .end() + .hint( "hint" ) + .long_hint( "long_hint" ) + .routine( || println!( "command" ) ) + .end() .command( "command2" ) - .hint( "hint" ) - .long_hint( "long_hint" ) - .routine( || println!( "command2" ) ) - .end() + .hint( "hint" ) + .long_hint( "long_hint" ) + .routine( || println!( "command2" ) ) + .end() .callback ( move | input, program | @@ -25,8 +26,8 @@ fn changes_state_of_local_variable_on_perform() .push( ( input.to_string(), - program.commands.clone() ) - )) + program.commands.clone() + ))) .perform(); { @@ -36,14 +37,14 @@ fn changes_state_of_local_variable_on_perform() { ca.perform( ".command" ).unwrap(); let current_history = history.lock().unwrap(); - assert_eq!( [ ".command" ], current_history.iter().map( |( input, _ )| input ).collect::< Vec< _ > >().as_slice() ); + assert_eq!( [ ".command" ], current_history.iter().map( | ( input, _ ) | input ).collect::< Vec< _ > >().as_slice() ); assert_eq!( 1, current_history.len() ); } { ca.perform( ".command2" ).unwrap(); let current_history = history.lock().unwrap(); - assert_eq!( [ ".command", ".command2" ], current_history.iter().map( |( input, _ )| input ).collect::< Vec< _ > >().as_slice() ); + assert_eq!( [ ".command", ".command2" ], current_history.iter().map( | ( input, _ ) | input ).collect::< Vec< _ > >().as_slice() ); assert_eq!( 2, current_history.len() ); } } diff --git a/module/move/wca/tests/inc/commands_aggregator/help.rs b/module/move/wca/tests/inc/commands_aggregator/help.rs index 1df2be062e..db97e118b3 100644 --- a/module/move/wca/tests/inc/commands_aggregator/help.rs +++ b/module/move/wca/tests/inc/commands_aggregator/help.rs @@ -1,19 +1,38 @@ -use std::fs::{DirBuilder, File}; -use std::io::Write; -use std::path::Path; -use std::process::{Command, Stdio}; +use std:: +{ + io::Write, + path::Path, + fs::{ DirBuilder, File }, + process::{ Command, Stdio }, +}; pub fn start_sync< AP, Args, Arg, P > ( application : AP, args: Args, path : P, -) -> String where AP : AsRef< Path >, Args : IntoIterator< Item = Arg >, Arg : AsRef< std::ffi::OsStr >, P : AsRef< Path >, +) -> String +where + AP : AsRef< Path >, + Args : IntoIterator< Item = Arg >, + Arg : AsRef< std::ffi::OsStr >, + P : AsRef< Path >, { let ( application, path ) = ( application.as_ref(), path.as_ref() ); - let args = args.into_iter().map( | a | a.as_ref().into() ).collect::< Vec< std::ffi::OsString > >(); - let child = Command::new( application ).args( &args ).stdout( Stdio::piped() ).stderr( Stdio::piped() ).current_dir( path ).spawn().unwrap(); + let args: Vec< std::ffi::OsString > = args.into_iter().map( | a | a.as_ref().into() ).collect(); + let child = Command::new( application ) + .args( &args ) + .stdout( Stdio::piped() ) + .stderr( Stdio::piped() ) + .current_dir( path ) + .spawn() + .unwrap(); let output = child.wait_with_output().unwrap(); + + if !output.status.success() + { + println!( "{}", String::from_utf8( output.stderr ).unwrap() ); + } String::from_utf8( output.stdout ).unwrap() } @@ -38,11 +57,11 @@ wca = {{path = "{}"}}"#, fn main(){ let ca = wca::CommandsAggregator::former() .command( "echo" ) - .hint( "prints all subjects and properties" ) - .subject().hint( "Subject" ).kind( Type::String ).optional( true ).end() - .property( "property" ).hint( "simple property" ).kind( Type::String ).optional( true ).end() - .routine( | o : VerifiedCommand | { println!( "= Args\n{:?}\n\n= Properties\n{:?}\n", o.args, o.props ) } ) - .end() + .hint( "prints all subjects and properties" ) + .subject().hint( "Subject" ).kind( Type::String ).optional( true ).end() + .property( "property" ).hint( "simple property" ).kind( Type::String ).optional( true ).end() + .routine( | o : VerifiedCommand | { println!( "= Args\n{:?}\n\n= Properties\n{:?}\n", o.args, o.props ) } ) + .end() .perform(); let args = std::env::args().skip( 1 ).collect::< Vec< String > >(); @@ -82,22 +101,22 @@ wca = {{path = "{}"}}"#, let ca = wca::CommandsAggregator::former() .command( "c" ) - .hint( "c" ) - .property( "c-property" ).kind( Type::String ).optional( true ).end() - .property( "b-property" ).kind( Type::String ).optional( true ).end() - .property( "a-property" ).kind( Type::String ).optional( true ).end() - .routine( | o : VerifiedCommand | { println!("c") } ) - .end() + .hint( "c" ) + .property( "c-property" ).kind( Type::String ).optional( true ).end() + .property( "b-property" ).kind( Type::String ).optional( true ).end() + .property( "a-property" ).kind( Type::String ).optional( true ).end() + .routine( | o : VerifiedCommand | { println!("c") } ) + .end() .command( "b" ) - .hint( "b" ) - .property( "b-property" ).kind( Type::String ).optional( true ).end() - .routine( | o : VerifiedCommand | { println!("b") } ) - .end() + .hint( "b" ) + .property( "b-property" ).kind( Type::String ).optional( true ).end() + .routine( | o : VerifiedCommand | { println!("b") } ) + .end() .command( "a" ) - .hint( "a" ) - .property( "a-property" ).kind( Type::String ).optional( true ).end() - .routine( | o : VerifiedCommand | { println!("a") } ) - .end() + .hint( "a" ) + .property( "a-property" ).kind( Type::String ).optional( true ).end() + .routine( | o : VerifiedCommand | { println!("a") } ) + .end() .order( Order::Nature ) .perform(); @@ -151,23 +170,23 @@ wca = {{path = "{}"}}"#, let ca = wca::CommandsAggregator::former() .command( "c" ) - .hint( "c" ) - .property( "c-property" ).kind( Type::String ).optional( true ).end() - .property( "b-property" ).kind( Type::String ).optional( true ).end() - .property( "a-property" ).kind( Type::String ).optional( true ).end() - .routine( | o : VerifiedCommand | { println!("c") } ) - .end() + .hint( "c" ) + .property( "c-property" ).kind( Type::String ).optional( true ).end() + .property( "b-property" ).kind( Type::String ).optional( true ).end() + .property( "a-property" ).kind( Type::String ).optional( true ).end() + .routine( | o : VerifiedCommand | { println!("c") } ) + .end() .command( "b" ) - .hint( "b" ) - .property( "b-property" ).kind( Type::String ).optional( true ).end() - .routine( | o : VerifiedCommand | { println!("b") } ) - .end() + .hint( "b" ) + .property( "b-property" ).kind( Type::String ).optional( true ).end() + .routine( | o : VerifiedCommand | { println!("b") } ) + .end() .command( "a" ) - .hint( "a" ) - .property( "a-property" ).kind( Type::String ).optional( true ).end() - .routine( | o : VerifiedCommand | { println!("a") } ) - .end() - .order( Order::Lexicography ) + .hint( "a" ) + .property( "a-property" ).kind( Type::String ).optional( true ).end() + .routine( | o : VerifiedCommand | { println!("a") } ) + .end() + .order( Order::Lexicography ) .perform(); let args = std::env::args().skip( 1 ).collect::< Vec< String > >(); diff --git a/module/move/wca/tests/inc/commands_aggregator/mod.rs b/module/move/wca/tests/inc/commands_aggregator/mod.rs index ca0cdc4b5a..fedda3d681 100644 --- a/module/move/wca/tests/inc/commands_aggregator/mod.rs +++ b/module/move/wca/tests/inc/commands_aggregator/mod.rs @@ -1,16 +1,5 @@ use super::*; -use the_module:: -{ - Parser, - - CommandsAggregator, - HelpVariants, - Type, - Error, - ValidationError, -}; - mod basic; mod callback; mod help; diff --git a/module/move/wca/tests/inc/executor/command.rs b/module/move/wca/tests/inc/executor/command.rs index b1dcf7ac12..32c92425b1 100644 --- a/module/move/wca/tests/inc/executor/command.rs +++ b/module/move/wca/tests/inc/executor/command.rs @@ -1,5 +1,15 @@ use super::*; -use the_module::VerifiedCommand; +use the_module:: +{ + parser::Parser, + VerifiedCommand, + executor::Context, Type, + grammar::Dictionary, + verifier::Verifier, + + Executor, + // wtools +}; // @@ -14,7 +24,7 @@ tests_impls! let dictionary = &Dictionary::former() .command ( - wca::Command::former() + wca::grammar::Command::former() .hint( "hint" ) .long_hint( "long_hint" ) .phrase( "command" ) @@ -42,12 +52,12 @@ tests_impls! let dictionary = &Dictionary::former() .command ( - wca::Command::former() + wca::grammar::Command::former() .hint( "hint" ) .long_hint( "long_hint" ) .phrase( "command" ) .subject().hint( "hint" ).kind( Type::String ).optional( false ).end() - .routine( | o : VerifiedCommand | o.args.get( 0 ).map( | a | println!( "{a:?}" )).ok_or_else( || "Subject not found" ) ) + .routine( | o : VerifiedCommand | o.args.get( 0 ).map( | a | println!( "{a:?}" ) ).ok_or_else( || "Subject not found" ) ) .form() ) .form(); @@ -78,12 +88,12 @@ tests_impls! let dictionary = &Dictionary::former() .command ( - wca::Command::former() + wca::grammar::Command::former() .hint( "hint" ) .long_hint( "long_hint" ) .phrase( "command" ) .property( "prop" ).hint( "about prop" ).kind( Type::String ).optional( true ).end() - .routine( | o : VerifiedCommand | o.props.get( "prop" ).map( | a | println!( "{a:?}" )).ok_or_else( || "Prop not found" ) ) + .routine( | o : VerifiedCommand | o.props.get( "prop" ).map( | a | println!( "{a:?}" ) ).ok_or_else( || "Prop not found" ) ) .form() ) .form(); @@ -121,7 +131,7 @@ tests_impls! let dictionary = &Dictionary::former() .command ( - wca::Command::former() + wca::grammar::Command::former() .hint( "hint" ) .long_hint( "long_hint" ) .phrase( "check" ) @@ -137,7 +147,7 @@ tests_impls! ) .form(); let verifier = Verifier; - let mut ctx = wca::Context::new( Mutex::new( 1 ) ); + let mut ctx = wca::executor::Context::new( Mutex::new( 1 ) ); // init executor let executor = Executor::former() .context( ctx ) @@ -160,7 +170,7 @@ tests_impls! let dictionary = &Dictionary::former() .command ( - wca::Command::former() + wca::grammar::Command::former() .hint( "hint" ) .long_hint( "long_hint" ) .phrase( "command" ) diff --git a/module/move/wca/tests/inc/executor/mod.rs b/module/move/wca/tests/inc/executor/mod.rs index 7c84cbf8a3..617cf69b75 100644 --- a/module/move/wca/tests/inc/executor/mod.rs +++ b/module/move/wca/tests/inc/executor/mod.rs @@ -1,17 +1,4 @@ use super::*; -// qqq : rid of global uses in tests -use the_module:: -{ - Parser, - - Context, Type, - Dictionary, - Verifier, - - Executor, - // wtools -}; - mod command; mod program; diff --git a/module/move/wca/tests/inc/executor/program.rs b/module/move/wca/tests/inc/executor/program.rs index de33330259..ef0f63940a 100644 --- a/module/move/wca/tests/inc/executor/program.rs +++ b/module/move/wca/tests/inc/executor/program.rs @@ -1,5 +1,15 @@ use super::*; -use the_module::VerifiedCommand; +use the_module:: +{ + parser::Parser, + VerifiedCommand, + executor::Context, Type, + grammar::Dictionary, + verifier::Verifier, + + Executor, + // wtools +}; // @@ -14,7 +24,7 @@ tests_impls! let dictionary = &Dictionary::former() .command ( - wca::Command::former() + wca::grammar::Command::former() .hint( "hint" ) .long_hint( "long_hint" ) .phrase( "command" ) @@ -47,7 +57,7 @@ tests_impls! let dictionary = &Dictionary::former() .command ( - wca::Command::former() + wca::grammar::Command::former() .hint( "hint" ) .long_hint( "long_hint" ) .phrase( "inc" ) @@ -63,7 +73,7 @@ tests_impls! ) .command ( - wca::Command::former() + wca::grammar::Command::former() .hint( "hint" ) .long_hint( "long_hint" ) .phrase( "eq" ) @@ -91,7 +101,7 @@ tests_impls! let verifier = Verifier; // starts with 0 - let ctx = wca::Context::new( Mutex::new( 0 ) ); + let ctx = wca::executor::Context::new( Mutex::new( 0 ) ); // init simple executor let executor = Executor::former() .context( ctx ) diff --git a/module/move/wca/tests/inc/grammar/from_command.rs b/module/move/wca/tests/inc/grammar/from_command.rs index 9823236c0c..1776539288 100644 --- a/module/move/wca/tests/inc/grammar/from_command.rs +++ b/module/move/wca/tests/inc/grammar/from_command.rs @@ -1,5 +1,14 @@ use super::*; +use the_module:: +{ + parser::Parser, + + Type, Value, + grammar::Dictionary, + verifier::Verifier, +}; + // tests_impls! @@ -13,7 +22,7 @@ tests_impls! let dictionary = &Dictionary::former() .command ( - wca::Command::former() + wca::grammar::Command::former() .hint( "hint" ) .long_hint( "long_hint" ) .phrase( "command" ) @@ -45,7 +54,7 @@ tests_impls! let dictionary = &Dictionary::former() .command ( - wca::Command::former() + wca::grammar::Command::former() .hint( "hint" ) .long_hint( "long_hint" ) .phrase( "command" ) @@ -92,7 +101,7 @@ tests_impls! let dictionary = &Dictionary::former() .command ( - wca::Command::former() + wca::grammar::Command::former() .hint( "hint" ) .long_hint( "long_hint" ) .phrase( "command" ) @@ -121,7 +130,7 @@ tests_impls! let dictionary = &Dictionary::former() .command ( - wca::Command::former() + wca::grammar::Command::former() .hint( "hint" ) .long_hint( "long_hint" ) .phrase( "command" ) @@ -156,7 +165,7 @@ tests_impls! let dictionary = &Dictionary::former() .command ( - wca::Command::former() + wca::grammar::Command::former() .hint( "hint" ) .long_hint( "long_hint" ) .phrase( "command" ) @@ -184,7 +193,7 @@ tests_impls! let dictionary = &Dictionary::former() .command ( - wca::Command::former() + wca::grammar::Command::former() .hint( "hint" ) .long_hint( "long_hint" ) .phrase( "command" ) @@ -223,7 +232,7 @@ tests_impls! let dictionary = &Dictionary::former() .command ( - wca::Command::former() + wca::grammar::Command::former() .hint( "hint" ) .long_hint( "long_hint" ) .phrase( "command" ) @@ -238,14 +247,14 @@ tests_impls! let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); a_true!( grammar_command.args.0.is_empty() ); - a_id!( HashMap::from_iter([ ( "prop1".to_string(), Value::String( "value1".to_string() ) ) ]), grammar_command.props.0 ); + a_id!( HashMap::from_iter( [ ( "prop1".to_string(), Value::String( "value1".to_string() ) ) ] ), grammar_command.props.0 ); // with property re-write let raw_command = parser.parse( [ ".command", "prop1:value", "prop1:another_value" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); a_true!( grammar_command.args.0.is_empty() ); - a_id!( HashMap::from_iter([ ( "prop1".to_string(), Value::String( "another_value".to_string() ) ) ]), grammar_command.props.0 ); + a_id!( HashMap::from_iter( [ ( "prop1".to_string(), Value::String( "another_value".to_string() ) ) ] ), grammar_command.props.0 ); // with undeclareted property let raw_command = parser.parse( [ ".command", "undeclareted_prop:value" ] ).unwrap().commands.remove( 0 ); @@ -268,7 +277,7 @@ tests_impls! let dictionary = &Dictionary::former() .command ( - wca::Command::former() + wca::grammar::Command::former() .hint( "hint" ) .long_hint( "long_hint" ) .phrase( "command" ) @@ -297,7 +306,7 @@ tests_impls! let dictionary = &Dictionary::former() .command ( - wca::Command::former() + wca::grammar::Command::former() .hint( "hint" ) .long_hint( "long_hint" ) .phrase( "command" ) @@ -328,17 +337,17 @@ tests_impls! let dictionary = &Dictionary::former() .command ( - wca::Command::former() + wca::grammar::Command::former() .hint( "hint" ) .long_hint( "long_hint" ) .phrase( "command" ) .property( "property" ) - .hint( "string property" ) - .kind( Type::String ) - .optional( true ) - .alias( "prop" ) - .alias( "p" ) - .end() + .hint( "string property" ) + .kind( Type::String ) + .optional( true ) + .alias( "prop" ) + .alias( "p" ) + .end() .form() ) .form(); @@ -349,27 +358,27 @@ tests_impls! let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); a_true!( grammar_command.args.0.is_empty() ); - a_id!( HashMap::from_iter([ ( "property".to_string(), Value::String( "value".to_string() ) ) ]), grammar_command.props.0 ); + a_id!( HashMap::from_iter( [ ( "property".to_string(), Value::String( "value".to_string() ) ) ] ), grammar_command.props.0 ); // first alias let raw_command = parser.parse( [ ".command", "prop:value" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); a_true!( grammar_command.args.0.is_empty() ); - a_id!( HashMap::from_iter([ ( "property".to_string(), Value::String( "value".to_string() ) ) ]), grammar_command.props.0 ); + a_id!( HashMap::from_iter( [ ( "property".to_string(), Value::String( "value".to_string() ) ) ] ), grammar_command.props.0 ); // second alias let raw_command = parser.parse( [ ".command", "p:value" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); a_true!( grammar_command.args.0.is_empty() ); - a_id!( HashMap::from_iter([ ( "property".to_string(), Value::String( "value".to_string() ) ) ]), grammar_command.props.0 ); + a_id!( HashMap::from_iter( [ ( "property".to_string(), Value::String( "value".to_string() ) ) ] ), grammar_command.props.0 ); // init converter with layered properties let dictionary = &Dictionary::former() .command ( - wca::Command::former() + wca::grammar::Command::former() .hint( "hint" ) .long_hint( "long_hint" ) .phrase( "command" ) @@ -384,7 +393,7 @@ tests_impls! let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); a_true!( grammar_command.args.0.is_empty() ); - a_id!( HashMap::from_iter([ ( "property".to_string(), Value::String( "value".to_string() ) ) ]), grammar_command.props.0 ); + a_id!( HashMap::from_iter( [ ( "property".to_string(), Value::String( "value".to_string() ) ) ] ), grammar_command.props.0 ); } } diff --git a/module/move/wca/tests/inc/grammar/from_program.rs b/module/move/wca/tests/inc/grammar/from_program.rs index 670eaf178c..256fd6dcd9 100644 --- a/module/move/wca/tests/inc/grammar/from_program.rs +++ b/module/move/wca/tests/inc/grammar/from_program.rs @@ -1,5 +1,14 @@ use super::*; +use the_module:: +{ + parser::Parser, + + Type, Value, + grammar::Dictionary, + verifier::Verifier, +}; + // tests_impls! @@ -12,7 +21,7 @@ tests_impls! let dictionary = &Dictionary::former() .command ( - wca::Command::former() + wca::grammar::Command::former() .hint( "hint" ) .long_hint( "long_hint" ) .phrase( "command1" ) @@ -21,7 +30,7 @@ tests_impls! ) .command ( - wca::Command::former() + wca::grammar::Command::former() .hint( "hint" ) .long_hint( "long_hint" ) .phrase( "command2" ) diff --git a/module/move/wca/tests/inc/grammar/mod.rs b/module/move/wca/tests/inc/grammar/mod.rs index 38c94dc114..454495c496 100644 --- a/module/move/wca/tests/inc/grammar/mod.rs +++ b/module/move/wca/tests/inc/grammar/mod.rs @@ -1,12 +1,4 @@ use super::*; -use the_module:: -{ - Parser, - - Type, Value, - Dictionary, - Verifier, -}; mod from_command; mod from_program; diff --git a/module/move/wca/tests/inc/grammar/types.rs b/module/move/wca/tests/inc/grammar/types.rs index 7421fce48f..037cdb3177 100644 --- a/module/move/wca/tests/inc/grammar/types.rs +++ b/module/move/wca/tests/inc/grammar/types.rs @@ -1,5 +1,5 @@ use super::*; -use wca::TryCast; +use the_module::{ TryCast, Type, Value }; // @@ -116,9 +116,10 @@ tests_impls! // numbers let numbers = Type::List( Type::Number.into(), ';' ).try_cast( "100;3.14".into() ); let numbers = numbers.unwrap(); - a_id!( - Value::List( vec![ Value::Number( 100.0 ), Value::Number( 3.14 ) ] ) - , numbers ); + a_id! + ( + Value::List( vec![ Value::Number( 100.0 ), Value::Number( 3.14 ) ] ), numbers + ); let inner_numbers : Vec< i32 > = numbers.clone().into(); a_id!( vec![ 100, 3 ], inner_numbers ); @@ -134,7 +135,7 @@ tests_impls! let string = Type::List( Type::String.into(), ',' ).try_cast( origin_string.into() ).unwrap(); a_id!( origin_string, string.to_string() ); - // xxx : qqq : that fails now. suggest solution + // xxx clarification is needed : qqq : that fails now. suggest solution // let origin_string = "100;3.14"; // let string = Type::List( Type::Number.into(), ';' ).try_cast( origin_string.into() ).unwrap(); // a_id!( origin_string, string.to_string() ); diff --git a/module/move/wca/tests/inc/mod.rs b/module/move/wca/tests/inc/mod.rs index c2617e9035..c805473908 100644 --- a/module/move/wca/tests/inc/mod.rs +++ b/module/move/wca/tests/inc/mod.rs @@ -1,20 +1,7 @@ -#[ allow( unused_imports ) ] use super::*; +use test_tools::exposed::*; -#[ allow( unused_imports ) ] -use the_module::tool::*; -#[ allow( unused_imports ) ] -use std::collections::HashMap; - -#[ cfg( not( feature = "no_std" ) ) ] mod parser; -#[ cfg( not( feature = "no_std" ) ) ] mod grammar; -#[ cfg( not( feature = "no_std" ) ) ] mod executor; -#[ cfg( not( feature = "no_std" ) ) ] mod commands_aggregator; - -// qqq : for Bohdan : why commented out? resolve -// #[ cfg( not( feature = "no_std" ) ) ] -// mod adapter; diff --git a/module/move/wca/tests/inc/parser/command.rs b/module/move/wca/tests/inc/parser/command.rs index 986ab1d0c0..e11f427695 100644 --- a/module/move/wca/tests/inc/parser/command.rs +++ b/module/move/wca/tests/inc/parser/command.rs @@ -1,4 +1,5 @@ use super::*; +use the_module::parser::{ ParsedCommand, Parser }; // @@ -51,7 +52,7 @@ tests_impls! { name : "command".into(), subjects : vec![], - properties : HashMap::from_iter([ ( "prop".into(), "value".into() ) ]), + properties : HashMap::from_iter( [ ( "prop".into(), "value".into() ) ] ), }, parser.parse( [ ".command", "prop:value" ] ).unwrap().commands[ 0 ] ); @@ -80,7 +81,7 @@ tests_impls! { name : "command".into(), subjects : vec![ "subject".into() ], - properties : HashMap::from_iter([ ( "prop".into(), "value".into() ) ]), + properties : HashMap::from_iter( [ ( "prop".into(), "value".into() ) ] ), }, parser.parse( [ ".command", "subject", "prop:value" ] ).unwrap().commands[ 0 ] ); @@ -131,7 +132,7 @@ tests_impls! { name : "command".into(), subjects : vec![], - properties : HashMap::from_iter([ ( "prop".into(), "value with spaces".into() ) ]), + properties : HashMap::from_iter( [ ( "prop".into(), "value with spaces".into() ) ] ), }, parser.parse( [ ".command", "prop:value with spaces" ] ).unwrap().commands[ 0 ] ); @@ -142,7 +143,7 @@ tests_impls! { name : "command".into(), subjects : vec![], - properties : HashMap::from_iter([ ( "prop".into(), "value with spaces".into() ) ]), + properties : HashMap::from_iter( [ ( "prop".into(), "value with spaces".into() ) ] ), }, parser.parse( [ ".command", "prop:", "value with spaces" ] ).unwrap().commands[ 0 ] ); @@ -153,7 +154,7 @@ tests_impls! { name : "command".into(), subjects : vec![], - properties : HashMap::from_iter([ ( "prop".into(), "value with spaces".into() ) ]), + properties : HashMap::from_iter( [ ( "prop".into(), "value with spaces".into() ) ] ), }, parser.parse( [ ".command", "prop", ":value with spaces" ] ).unwrap().commands[ 0 ] ); @@ -164,7 +165,7 @@ tests_impls! { name : "command".into(), subjects : vec![], - properties : HashMap::from_iter([ ( "prop".into(), "value with spaces".into() ) ]), + properties : HashMap::from_iter( [ ( "prop".into(), "value with spaces".into() ) ] ), }, parser.parse( [ ".command", "prop", ":", "value with spaces" ] ).unwrap().commands[ 0 ] ); @@ -202,7 +203,7 @@ tests_impls! { name : "command".into(), subjects : vec![], - properties : HashMap::from_iter([ ( "long_prop".into(), "some-value".into() ) ]), + properties : HashMap::from_iter( [ ( "long_prop".into(), "some-value".into() ) ] ), }, parser.parse( [ ".command", "long_prop:some-value" ] ).unwrap().commands[ 0 ] ); @@ -245,7 +246,7 @@ tests_impls! { name : "command".into(), subjects : vec![], - properties : HashMap::from_iter([ ( "path".into(), "/absolute/path/to/something".into() ) ]), + properties : HashMap::from_iter( [ ( "path".into(), "/absolute/path/to/something".into() ) ] ), }, parser.parse( [ ".command", "path:/absolute/path/to/something" ] ).unwrap().commands[ 0 ] ); @@ -256,7 +257,7 @@ tests_impls! { name : "command".into(), subjects : vec![], - properties : HashMap::from_iter([ ( "path".into(), "./path/to/something".into() ) ]), + properties : HashMap::from_iter( [ ( "path".into(), "./path/to/something".into() ) ] ), }, parser.parse( [ ".command", "path:./path/to/something" ] ).unwrap().commands[ 0 ] ); @@ -267,7 +268,7 @@ tests_impls! { name : "command".into(), subjects : vec![], - properties : HashMap::from_iter([ ( "path".into(), "../path/to/something".into() ) ]), + properties : HashMap::from_iter( [ ( "path".into(), "../path/to/something".into() ) ] ), }, parser.parse( [ ".command", "path:../path/to/something" ] ).unwrap().commands[ 0 ] ); @@ -283,7 +284,7 @@ tests_impls! { name : "command".into(), subjects : vec![], - properties : HashMap::from_iter([ ( "list".into(), "[1,2,3]".into() ) ]), + properties : HashMap::from_iter( [ ( "list".into(), "[1,2,3]".into() ) ] ), }, parser.parse( [ ".command", "list:[1,2,3]" ] ).unwrap().commands[ 0 ] ); @@ -299,7 +300,7 @@ tests_impls! { name : "command".into(), subjects : vec![ "subject with spaces".into() ], - properties : HashMap::from_iter([ ( "prop".into(), "property with spaces".into() ) ]), + properties : HashMap::from_iter( [ ( "prop".into(), "property with spaces".into() ) ] ), }, parser.parse( [ ".command", "subject with spaces", "prop:property with spaces" ] ).unwrap().commands[ 0 ] ); @@ -311,7 +312,7 @@ tests_impls! { name : "command".into(), subjects : vec![ "\\.command".into() ], - properties : HashMap::from_iter([ ( "prop".into(), ".command".into() ) ]), + properties : HashMap::from_iter( [ ( "prop".into(), ".command".into() ) ] ), }, parser.parse( [ ".command", "\\.command", "prop:.command" ] ).unwrap().commands[ 0 ] ); @@ -323,7 +324,7 @@ tests_impls! { name : "command".into(), subjects : vec![ "' queted ' \\ value".into() ], - properties : HashMap::from_iter([ ( "prop".into(), "some \"quetes\" ' \\ in string".into() ) ]), + properties : HashMap::from_iter( [ ( "prop".into(), "some \"quetes\" ' \\ in string".into() ) ] ), }, parser.parse( [ ".command", "\' queted \' \\ value", "prop:some \"quetes\" ' \\ in string" ] ).unwrap().commands[ 0 ] ); diff --git a/module/move/wca/tests/inc/parser/mod.rs b/module/move/wca/tests/inc/parser/mod.rs index 456679d11a..617cf69b75 100644 --- a/module/move/wca/tests/inc/parser/mod.rs +++ b/module/move/wca/tests/inc/parser/mod.rs @@ -1,10 +1,4 @@ use super::*; -use wca:: -{ - Program, ParsedCommand, - - Parser, -}; mod command; mod program; diff --git a/module/move/wca/tests/inc/parser/program.rs b/module/move/wca/tests/inc/parser/program.rs index 081f8cc3e8..04b07c322f 100644 --- a/module/move/wca/tests/inc/parser/program.rs +++ b/module/move/wca/tests/inc/parser/program.rs @@ -1,4 +1,5 @@ use super::*; +use the_module::parser::{ Program, ParsedCommand, Parser }; // diff --git a/module/move/wca/tests/smoke_test.rs b/module/move/wca/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/move/wca/tests/smoke_test.rs +++ b/module/move/wca/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/move/wca/tests/wca_tests.rs b/module/move/wca/tests/tests.rs similarity index 50% rename from module/move/wca/tests/wca_tests.rs rename to module/move/wca/tests/tests.rs index ac2fbf9612..cd66cd65aa 100644 --- a/module/move/wca/tests/wca_tests.rs +++ b/module/move/wca/tests/tests.rs @@ -1,12 +1,11 @@ +//! All tests. + // #![ deny( rust_2018_idioms ) ] // #![ deny( missing_debug_implementations ) ] // #![ deny( missing_docs ) ] +#![ allow( unused_imports ) ] -#[ allow( unused_imports ) ] +/// System under test. use wca as the_module; -#[ allow( unused_imports ) ] -use test_tools::exposed::*; -// #[ allow( unused_imports ) ] -// use wca::wtools::*; mod inc; diff --git a/module/move/willbe/Cargo.toml b/module/move/willbe/Cargo.toml index d87621762c..1dd34821d4 100644 --- a/module/move/willbe/Cargo.toml +++ b/module/move/willbe/Cargo.toml @@ -1,6 +1,7 @@ +# module/move/willbe/Cargo.toml [package] name = "willbe" -version = "0.14.0" +version = "0.21.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -41,7 +42,7 @@ enabled = [ "iter_tools/enabled", "mod_interface/enabled", "wca/enabled", - "proper_path_tools/enabled", + "pth/enabled", "process_tools/enabled", "derive_tools/enabled", "data_type/enabled", @@ -61,12 +62,12 @@ flate2 = "~1.0" globwalk = "~0.8" toml_edit = "~0.14" petgraph = "~0.6" -ptree = "~0.4" +#ptree = "~0.4" rayon = "1.8.0" semver = "~1.0.0" similar = "~2.4" regex = "1.10.2" -sha-1 = "~0.10" +#sha-1 = "~0.10" tar = "~0.4" handlebars = "4.5.0" ureq = "~2.9" @@ -80,6 +81,7 @@ serde_json = "1.0" # for CargoMetadata::Package::metadata (need serde_json::Valu serde = "1.0" # for CargoMetadata::Package parse-display = "0.9" # need because derive_tools don't reexport this correctly walkdir = "2.3" +rustdoc-md = "0.1.0" ## internal crates_tools = { workspace = true } @@ -88,7 +90,7 @@ former = { workspace = true, features = [ "default" ] } iter_tools = { workspace = true, features = [ "default" ] } mod_interface = { workspace = true, features = [ "default" ] } wca = { workspace = true, features = [ "default" ] } -proper_path_tools = { workspace = true, features = [ "default", "path_utf8" ] } +pth = { workspace = true, features = [ "default", "path_utf8" ] } process_tools = { workspace = true, features = [ "default" ] } derive_tools = { workspace = true, features = [ "derive_display", "derive_from_str", "derive_deref", "derive_from", "derive_as_ref" ] } data_type = { workspace = true, features = [ "either" ] } @@ -102,3 +104,4 @@ serde_yaml = "0.9" serde_json = "1.0.114" serde = "1.0" assert_cmd = "2.0" +predicates = "3.1.0" \ No newline at end of file diff --git a/module/move/willbe/License b/module/move/willbe/License index 6d5ef8559f..72c80c1308 100644 --- a/module/move/willbe/License +++ b/module/move/willbe/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/Readme.md b/module/move/willbe/Readme.md index b387b877c6..c7d2a441b9 100644 --- a/module/move/willbe/Readme.md +++ b/module/move/willbe/Readme.md @@ -1,6 +1,6 @@ -# Module:: willbe +# `Module`:: willbe [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_willbe_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_willbe_push.yml) [![docs.rs](https://img.shields.io/docsrs/willbe?color=e3e8f0&logo=docs.rs)](https://docs.rs/willbe) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) diff --git a/module/move/willbe/src/action/cicd_renew.rs b/module/move/willbe/src/action/cicd_renew.rs index fdf14a1216..cec1aae1c8 100644 --- a/module/move/willbe/src/action/cicd_renew.rs +++ b/module/move/willbe/src/action/cicd_renew.rs @@ -1,5 +1,6 @@ mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use std:: @@ -8,7 +9,7 @@ mod private io::{ Write, Read }, }; - use path::{ Path }; + use path::Path; use collection::BTreeMap; use convert_case::{ Casing, Case }; use handlebars::{ RenderError, TemplateError }; @@ -19,7 +20,7 @@ mod private use error:: { typed::Error, - err, + // err, }; #[ derive( Debug, Error ) ] @@ -42,7 +43,13 @@ mod private // qqq : for Petro : should return Report and typed error in Result /// Generate workflows for modules in .github/workflows directory. - pub fn cicd_renew( base_path : &Path ) -> Result< (), CiCdGenerateError > + /// # Errors + /// qqq: doc + /// + /// # Panics + /// qqq: doc + #[ allow( clippy::too_many_lines, clippy::result_large_err ) ] + pub fn action( base_path : &Path ) -> Result< (), CiCdGenerateError > { let workspace_cache = Workspace::try_from( CrateDir::try_from( base_path )? )?; let packages = workspace_cache.packages(); @@ -131,13 +138,13 @@ mod private data.insert( "name", name.as_str() ); data.insert( "username_and_repository", username_and_repository.0.as_str() ); data.insert( "branch", "alpha" ); - let manifest_file = manifest_file.to_string_lossy().replace( "\\", "/" ); + let manifest_file = manifest_file.to_string_lossy().replace( '\\', "/" ); let manifest_file = manifest_file.trim_start_matches( '/' ); data.insert( "manifest_path", manifest_file ); let content = handlebars.render( "module_push", &data )?; file_write( &workflow_file_name, &content )?; - println!( "file_write : {:?}", &workflow_file_name ) + println!( "file_write : {:?}", &workflow_file_name ); } dbg!( &workflow_root ); @@ -306,7 +313,7 @@ mod private Ok( () ) } - /// Prepare params for render appropriative_branch_for template. + /// Prepare params for render `appropriative_branch_for` template. fn map_prepare_for_appropriative_branch< 'a > ( branches : &'a str, @@ -333,7 +340,7 @@ mod private { match std::fs::create_dir_all( folder ) { - Ok( _ ) => {}, + Ok( () ) => {}, Err( e ) if e.kind() == std::io::ErrorKind::AlreadyExists => {}, Err( e ) => return Err( e.into() ), } @@ -372,10 +379,10 @@ mod private .map( String::from ); if let Some( url ) = url { - return url::repo_url_extract( &url ) + url::repo_url_extract( &url ) .and_then( | url | url::git_info_extract( &url ).ok() ) .map( UsernameAndRepository ) - .ok_or_else( || err!( "Fail to parse repository url from workspace Cargo.toml")) + .ok_or_else( || error::untyped::format_err!( "Fail to parse repository url from workspace Cargo.toml") ) } else { @@ -389,11 +396,11 @@ mod private break; } } - return url + url .and_then( | url | url::repo_url_extract( &url ) ) .and_then( | url | url::git_info_extract( &url ).ok() ) .map( UsernameAndRepository ) - .ok_or_else( || err!( "Fail to extract repository url") ) + .ok_or_else( || error::untyped::format_err!( "Fail to extract repository url") ) } } @@ -401,5 +408,5 @@ mod private crate::mod_interface! { - exposed use cicd_renew; + own use action; } diff --git a/module/move/willbe/src/action/crate_doc.rs b/module/move/willbe/src/action/crate_doc.rs new file mode 100644 index 0000000000..91f48874a1 --- /dev/null +++ b/module/move/willbe/src/action/crate_doc.rs @@ -0,0 +1,266 @@ +// module/move/willbe/src/action/crate_doc.rs +mod private +{ + #[ allow( clippy::wildcard_imports ) ] + use crate::*; + + use process_tools::process; + use error:: + { + untyped::Context, + typed::Error, + ErrWith, + }; + use core::fmt; + use std:: + { + ffi::OsString, + fs, + path::PathBuf, + }; + use collection_tools::HashMap; + use toml_edit::Document; + use rustdoc_md::rustdoc_json_types::Crate as RustdocCrate; + use rustdoc_md::rustdoc_json_to_markdown; + + /// Represents errors specific to the crate documentation generation process. + #[ derive( Debug, Error ) ] + pub enum CrateDocError + { + /// Error related to file system operations (reading/writing files). + #[ error( "I/O error: {0}" ) ] + Io( #[ from ] std::io::Error ), + /// Error encountered while parsing the Cargo.toml file. + #[ error( "Failed to parse Cargo.toml: {0}" ) ] + Toml( #[ from ] toml_edit::TomlError ), + /// Error occurred during the execution of the `cargo doc` command. + #[ error( "Failed to execute cargo doc command: {0}" ) ] + Command( String ), + /// Error encountered while deserializing the JSON output from `cargo doc`. + #[ error( "Failed to deserialize rustdoc JSON: {0}" ) ] + Json( #[ from ] serde_json::Error ), + /// Error occurred during the conversion from JSON to Markdown. + #[ error( "Failed to render Markdown: {0}" ) ] + MarkdownRender( String ), + /// The package name could not be found within the Cargo.toml file. + #[ error( "Missing package name in Cargo.toml at {0}" ) ] + MissingPackageName( PathBuf ), + /// The JSON documentation file generated by `cargo doc` was not found. + #[ error( "Generated JSON documentation file not found at {0}" ) ] + JsonFileNotFound( PathBuf ), + /// Error related to path manipulation or validation. + #[ error( "Path error: {0}" ) ] + Path( #[ from ] PathError ), + /// A general, untyped error occurred. + #[ error( "Untyped error: {0}" ) ] + Untyped( #[ from ] error::untyped::Error ), + } + + /// Report detailing the outcome of the documentation generation. + #[ derive( Debug, Default, Clone ) ] + pub struct CrateDocReport + { + /// The directory of the crate processed. + pub crate_dir : Option< CrateDir >, + /// The path where the Markdown file was (or was attempted to be) written. + pub output_path : Option< PathBuf >, + /// A summary status message of the operation. + pub status : String, + /// Output of the cargo doc command, if executed. + pub cargo_doc_report : Option< process::Report >, + } + + impl fmt::Display for CrateDocReport + { + fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result + { + // Status is the primary message + writeln!( f, "{}", self.status )?; + // Add crate and output path details for context + if let Some( crate_dir ) = &self.crate_dir + { + writeln!( f, " Crate: {}", crate_dir.as_ref().display() )?; + } + if let Some( output_path ) = &self.output_path + { + writeln!( f, " Output: {}", output_path.display() )?; + } + Ok( () ) + } + } + + /// + /// Generate documentation for a crate in a single Markdown file. + /// Executes `cargo doc` to generate JSON output, reads the JSON, + /// uses `rustdoc-md` to convert it to Markdown, and saves the result. + /// + /// # Arguments + /// * `workspace` - A reference to the workspace containing the crate. + /// * `crate_dir` - The directory of the crate for which to generate documentation. + /// * `output_path_req` - Optional path for the output Markdown file. + /// + /// # Returns + /// Returns `Ok(CrateDocReport)` if successful, otherwise returns `Err((CrateDocReport, CrateDocError))`. + /// + /// # Errors + /// Returns an error if the command arguments are invalid, the workspace cannot be loaded + #[allow(clippy::too_many_lines)] + pub fn doc + ( + workspace : &Workspace, + crate_dir : &CrateDir, + output_path_req : Option< PathBuf >, + ) -> ResultWithReport< CrateDocReport, CrateDocError > + { + let mut report = CrateDocReport + { + crate_dir : Some( crate_dir.clone() ), + status : format!( "Starting documentation generation for {}", crate_dir.as_ref().display() ), + ..Default::default() + }; + + + // --- Get crate name early for --package argument and file naming --- + let manifest_path_for_name = crate_dir.as_ref().join( "Cargo.toml" ); + let manifest_content_for_name = fs::read_to_string( &manifest_path_for_name ) + .map_err( CrateDocError::Io ) + .context( format!( "Failed to read Cargo.toml at {}", manifest_path_for_name.display() ) ) + .err_with_report( &report )?; + let manifest_toml_for_name = manifest_content_for_name.parse::< Document >() + .map_err( CrateDocError::Toml ) + .context( format!( "Failed to parse Cargo.toml at {}", manifest_path_for_name.display() ) ) + .err_with_report( &report )?; + let crate_name = manifest_toml_for_name[ "package" ][ "name" ] + .as_str() + .ok_or_else( || CrateDocError::MissingPackageName( manifest_path_for_name.clone() ) ) + .err_with_report( &report )?; + // --- End get crate name early --- + + // Define the arguments for `cargo doc` + let args: Vec< OsString > = vec! + [ + "doc".into(), + "--no-deps".into(), + "--package".into(), + crate_name.into(), + ]; + + // Define environment variables + let envs: HashMap< String, String > = + [ + ( "RUSTC_BOOTSTRAP".to_string(), "1".to_string() ), + ( "RUSTDOCFLAGS".to_string(), "-Z unstable-options --output-format json".to_string() ), + ].into(); + + // Execute the command from the workspace root + let cargo_report_result = process::Run::former() + .bin_path( "cargo" ) + .args( args ) + .current_path( workspace.workspace_root().absolute_path() ) + .env_variable( envs ) + .run(); + + // Store report regardless of outcome and update status if it failed + match &cargo_report_result + { + Ok( r ) => report.cargo_doc_report = Some( r.clone() ), + Err( r ) => + { + report.cargo_doc_report = Some( r.clone() ); + report.status = format!( "Failed during `cargo doc` execution for `{crate_name}`." ); + } + } + + // Handle potential command execution error using err_with_report + let _cargo_report = cargo_report_result + .map_err( | report | CrateDocError::Command( report.to_string() ) ) + .err_with_report( &report )?; + + // Construct path to the generated JSON file using workspace target dir + let json_path = workspace + .target_directory() + .join( "doc" ) + .join( format!( "{crate_name}.json" ) ); + + // Check if JSON file exists and read it + if !json_path.exists() + { + report.status = format!( "Generated JSON documentation file not found at {}", json_path.display() ); + return Err(( report, CrateDocError::JsonFileNotFound( json_path ) )); + } + let json_content = fs::read_to_string( &json_path ) + .map_err( CrateDocError::Io ) + .context( format!( "Failed to read JSON documentation file at {}", json_path.display() ) ) + .err_with_report( &report )?; + + // Deserialize JSON content into RustdocCrate struct + let rustdoc_crate: RustdocCrate = serde_json::from_str( &json_content ) + .map_err( CrateDocError::Json ) + .context( format!( "Failed to deserialize JSON from {}", json_path.display() ) ) + .err_with_report( &report )?; + + // Define output Markdown file path + let output_md_abs_path = match output_path_req + { + // If a path was provided + Some( req_path ) => + { + if req_path.is_absolute() + { + // Use it directly if absolute + req_path + } + else + { + // Resolve relative to CWD if relative + std::env::current_dir() + .map_err( CrateDocError::Io ) + .context( "Failed to get current directory to resolve output path" ) + .err_with_report( &report )? + .join( req_path ) + // Removed canonicalize call here + } + } + // If no path was provided, default to workspace target/doc directory + None => + { + workspace + .target_directory() + .join( "doc" ) + .join( format!( "{crate_name}_doc.md" ) ) + } + }; + + report.output_path = Some( output_md_abs_path.clone() ); + + // Use rustdoc_json_to_markdown to convert the Crate struct to Markdown string + let markdown_content = rustdoc_json_to_markdown( rustdoc_crate ); + + // Write the Markdown string to the output file + if let Some( parent_dir ) = output_md_abs_path.parent() + { + fs::create_dir_all( parent_dir ) + .map_err( CrateDocError::Io ) + .context( format!( "Failed to create output directory {}", parent_dir.display() ) ) + .err_with_report( &report )?; + } + fs::write( &output_md_abs_path, markdown_content ) + .map_err( CrateDocError::Io ) + .context( format!( "Failed to write Markdown documentation to {}", output_md_abs_path.display() ) ) + .err_with_report( &report )?; + + report.status = format!( "Markdown documentation generated successfully for `{crate_name}`" ); + + Ok( report ) + } +} + +crate::mod_interface! +{ + /// Generate documentation action. + orphan use doc; + /// Report for documentation generation. + orphan use CrateDocReport; + /// Error type for documentation generation. + orphan use CrateDocError; +} \ No newline at end of file diff --git a/module/move/willbe/src/action/deploy_renew.rs b/module/move/willbe/src/action/deploy_renew.rs index 2a6d3b52fd..12c923f575 100644 --- a/module/move/willbe/src/action/deploy_renew.rs +++ b/module/move/willbe/src/action/deploy_renew.rs @@ -1,160 +1,98 @@ mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use std::path::Path; - use error::{ untyped::Context }; + use error::untyped::Context; + #[ allow( clippy::wildcard_imports ) ] use tool::template::*; - // /// Template for creating deploy files. - // /// - // /// Includes terraform deploy options to GCP, and Hetzner, - // /// a Makefile for useful commands, and a key directory. - // #[ derive( Debug ) ] - // pub struct DeployTemplate - // { - // files : DeployTemplateFiles, - // parameters : TemplateParameters, - // values : TemplateValues, - // } + /// Template for creating deploy files. + /// + /// Includes terraform deploy options to GCP, and Hetzner, + /// a Makefile for useful commands, and a key directory. + #[ derive( Debug ) ] + pub struct DeployTemplate; - // // qqq : for Viktor : why DeployTemplate can't be part of template.rs? - - // impl Template< DeployTemplateFiles > for DeployTemplate - // { - // fn create_all( self, path : &Path ) -> error::untyped::Result< () > - // { - // self.files.create_all( path, &self.values ) - // } - - // fn parameters( &self ) -> &TemplateParameters - // { - // &self.parameters - // } - - // fn set_values( &mut self, values : TemplateValues ) - // { - // self.values = values - // } - - // fn get_values( &self ) -> &TemplateValues - // { - // &self.values - // } - - // fn get_values_mut( &mut self ) -> &mut TemplateValues - // { - // &mut self.values - // } - - // fn parameter_storage( &self ) -> &Path { - // "./.deploy_template.toml".as_ref() - // } - - // fn template_name( &self ) -> &'static str { - // "deploy" - // } - // } - - // impl Default for DeployTemplate - // { - // fn default() -> Self - // { - // let parameters = TemplateParameters::former() - // .parameter( "gcp_project_id" ).is_mandatory( true ).end() - // .parameter( "gcp_region" ).end() - // .parameter( "gcp_artifact_repo_name" ).end() - // .parameter( "docker_image_name" ).end() - // .form(); - - // Self - // { - // files : Default::default(), - // parameters, - // values : Default::default(), - // } - // } - // } - - // // qqq : for Viktor : is that structure required? - // /// Files for the deploy template. - // /// - // /// Default implementation contains all required files. - // #[ derive( Debug ) ] - // pub struct DeployTemplateFiles( Vec< TemplateFileDescriptor > ); - - // impl Default for DeployTemplateFiles - // { - // fn default() -> Self - // { - // let formed = TemplateFilesBuilder::former() - // // root - // .file().data( include_str!( "../../template/deploy/.deploy_template.toml.hbs" ) ).path( "./.deploy_template.toml" ).mode( WriteMode::TomlExtend ).is_template( true ).end() - // .file().data( include_str!( "../../template/deploy/Makefile.hbs" ) ).path( "./Makefile" ).is_template( true ).end() - // // /key - // .file().data( include_str!( "../../template/deploy/key/pack.sh" ) ).path( "./key/pack.sh" ).end() - // .file().data( include_str!( "../../template/deploy/key/Readme.md" ) ).path( "./key/Readme.md" ).end() - // // /deploy/ - // .file().data( include_str!( "../../template/deploy/deploy/Dockerfile" ) ).path( "./deploy/Dockerfile" ).end() - // .file().data( include_str!( "../../template/deploy/deploy/Readme.md" ) ).path( "./deploy/Readme.md" ).end() - // // /deploy/gar - // .file().data( include_str!( "../../template/deploy/deploy/gar/Readme.md" ) ).path( "./deploy/gar/Readme.md" ).end() - // .file().data( include_str!( "../../template/deploy/deploy/gar/main.tf" ) ).path( "./deploy/gar/main.tf" ).end() - // .file().data( include_str!( "../../template/deploy/deploy/gar/outputs.tf" ) ).path( "./deploy/gar/outputs.tf" ).end() - // .file().data( include_str!( "../../template/deploy/deploy/gar/variables.tf" ) ).path( "./deploy/gar/variables.tf" ).end() - // // /deploy/gce - // .file().data( include_str!( "../../template/deploy/deploy/gce/Readme.md" ) ).path( "./deploy/gce/Readme.md" ).end() - // .file().data( include_str!( "../../template/deploy/deploy/gce/main.tf" ) ).path( "./deploy/gce/main.tf" ).end() - // .file().data( include_str!( "../../template/deploy/deploy/gce/outputs.tf" ) ).path( "./deploy/gce/outputs.tf" ).end() - // .file().data( include_str!( "../../template/deploy/deploy/gce/variables.tf" ) ).path( "./deploy/gce/variables.tf" ).end() - // // /deploy/gce/templates - // .file().data( include_str!( "../../template/deploy/deploy/gce/templates/cloud-init.tpl" ) ).path( "./deploy/gce/templates/cloud-init.tpl" ).end() - // // /deploy/gcs - // .file().data( include_str!( "../../template/deploy/deploy/gcs/main.tf" ) ).path( "./deploy/gcs/main.tf" ).end() - // // /deploy/hetzner - // .file().data( include_str!( "../../template/deploy/deploy/hetzner/main.tf" ) ).path( "./deploy/hetzner/main.tf" ).end() - // .file().data( include_str!( "../../template/deploy/deploy/hetzner/outputs.tf" ) ).path( "./deploy/hetzner/outputs.tf" ).end() - // .file().data( include_str!( "../../template/deploy/deploy/hetzner/variables.tf" ) ).path( "./deploy/hetzner/variables.tf" ).end() - // // /deploy/hetzner/templates - // .file().data( include_str!( "../../template/deploy/deploy/hetzner/templates/cloud-init.tpl" ) ).path( "./deploy/hetzner/templates/cloud-init.tpl" ).end() - // // /deploy/aws - // .file().data( include_str!( "../../template/deploy/deploy/aws/main.tf" ) ).path( "./deploy/aws/main.tf" ).end() - // .file().data( include_str!( "../../template/deploy/deploy/aws/outputs.tf" ) ).path( "./deploy/aws/outputs.tf" ).end() - // .file().data( include_str!( "../../template/deploy/deploy/aws/variables.tf" ) ).path( "./deploy/aws/variables.tf" ).end() - // // /deploy/aws/templates - // .file().data( include_str!( "../../template/deploy/deploy/aws/templates/cloud-init.tpl" ) ).path( "./deploy/aws/templates/cloud-init.tpl" ).end() - // .form(); - - // Self( formed.files ) - // } - // } - - // // qqq : for Viktor : should not be required - // impl TemplateFiles for DeployTemplateFiles {} - // // qqq : for Viktor : should not be required - // impl IntoIterator for DeployTemplateFiles - // { - // type Item = TemplateFileDescriptor; - - // type IntoIter = std::vec::IntoIter< Self::Item >; - - // fn into_iter( self ) -> Self::IntoIter - // { - // self.0.into_iter() - // } - // } + impl DeployTemplate + { + /// Creates am instance of `[TemplateHolder]` for deployment template. + /// + /// Used for properly initializing a template + #[ must_use ] + #[ allow( clippy::should_implement_trait ) ] + pub fn default() -> TemplateHolder + { + let parameters = TemplateParameters::former() + .parameter( "gcp_project_id" ).is_mandatory( true ).end() + .parameter( "gcp_region" ).end() + .parameter( "gcp_artifact_repo_name" ).end() + .parameter( "docker_image_name" ).end() + .form(); + + TemplateHolder + { + files : get_deploy_template_files(), + parameters, + values : TemplateValues::default(), + parameter_storage : "./.deploy_template.toml".as_ref(), + template_name : "deploy", + } + } + } - // aaa : for Petro : redundant function - // aaa : this function not my, but ok I'll remove it. + fn get_deploy_template_files() -> Vec< TemplateFileDescriptor > + { + let formed = TemplateFilesBuilder::former() + // root + .file().data( include_str!( "../../template/deploy/.deploy_template.toml.hbs" ) ).path( "./.deploy_template.toml" ) + .mode( WriteMode::TomlExtend ) + .is_template( true ) + .end() + .file().data( include_str!( "../../template/deploy/Makefile.hbs" ) ).path( "./Makefile" ).is_template( true ).end() + // /key + .file().data( include_str!( "../../template/deploy/key/pack.sh" ) ).path( "./key/pack.sh" ).end() + .file().data( include_str!( "../../template/deploy/key/Readme.md" ) ).path( "./key/Readme.md" ).end() + // /deploy/ + .file().data( include_str!( "../../template/deploy/deploy/redeploy.sh" ) ).path( "./deploy/redeploy.sh" ).end() + .file().data( include_str!( "../../template/deploy/deploy/cloud-init.tpl.hbs" ) ).path( "./deploy/cloud-init.tpl" ).is_template( true ).end() + .file().data( include_str!( "../../template/deploy/deploy/Dockerfile" ) ).path( "./deploy/Dockerfile" ).end() + .file().data( include_str!( "../../template/deploy/deploy/Readme.md" ) ).path( "./deploy/Readme.md" ).end() + // /deploy/gar + .file().data( include_str!( "../../template/deploy/deploy/gar/Readme.md" ) ).path( "./deploy/gar/Readme.md" ).end() + .file().data( include_str!( "../../template/deploy/deploy/gar/main.tf.hbs" ) ).path( "./deploy/gar/main.tf" ).is_template( true ).end() + .file().data( include_str!( "../../template/deploy/deploy/gar/outputs.tf" ) ).path( "./deploy/gar/outputs.tf" ).end() + .file().data( include_str!( "../../template/deploy/deploy/gar/variables.tf" ) ).path( "./deploy/gar/variables.tf" ).end() + // /deploy/gce + .file().data( include_str!( "../../template/deploy/deploy/gce/Readme.md" ) ).path( "./deploy/gce/Readme.md" ).end() + .file().data( include_str!( "../../template/deploy/deploy/gce/main.tf.hbs" ) ).path( "./deploy/gce/main.tf" ).is_template( true ).end() + .file().data( include_str!( "../../template/deploy/deploy/gce/outputs.tf.hbs" ) ).path( "./deploy/gce/outputs.tf" ).is_template( true ).end() + .file().data( include_str!( "../../template/deploy/deploy/gce/variables.tf" ) ).path( "./deploy/gce/variables.tf" ).end() + // /deploy/gcs + .file().data( include_str!( "../../template/deploy/deploy/gcs/main.tf" ) ).path( "./deploy/gcs/main.tf" ).end() + // /deploy/hetzner + .file().data( include_str!( "../../template/deploy/deploy/hetzner/main.tf.hbs" ) ).path( "./deploy/hetzner/main.tf" ).is_template( true ).end() + .file().data( include_str!( "../../template/deploy/deploy/hetzner/outputs.tf.hbs" ) ).path( "./deploy/hetzner/outputs.tf" ).is_template( true ).end() + .file().data( include_str!( "../../template/deploy/deploy/hetzner/variables.tf" ) ).path( "./deploy/hetzner/variables.tf" ).end() + // /deploy/aws + .file().data( include_str!( "../../template/deploy/deploy/aws/main.tf" ) ).path( "./deploy/aws/main.tf" ).end() + .file().data( include_str!( "../../template/deploy/deploy/aws/outputs.tf" ) ).path( "./deploy/aws/outputs.tf" ).end() + .file().data( include_str!( "../../template/deploy/deploy/aws/variables.tf" ) ).path( "./deploy/aws/variables.tf" ).end() + .form(); + + formed.files + } fn dir_name_to_formatted( dir_name : &str, separator : &str ) -> String { dir_name - .replace( ' ', separator ) - .replace( '_', separator ) + .replace( [ ' ', '_' ], separator ) .to_lowercase() } /// Creates deploy template + /// # Errors + /// qqq: doc pub fn deploy_renew ( path : &Path, @@ -163,14 +101,14 @@ mod private -> error::untyped::Result< () > // qqq : typed error { - if let None = template.load_existing_params( path ) + if template.load_existing_params( path ).is_none() { let current_dir = std::env::current_dir()?; // qqq : for Petro : use file_name // qqq : for Kos : bad description let current_dir = current_dir .components() - .last() + .next_back() .context( "Invalid current directory" )?; let current_dir = current_dir.as_os_str().to_string_lossy(); @@ -179,7 +117,6 @@ mod private template .values .insert_if_empty( "gcp_artifact_repo_name", wca::Value::String( artifact_repo_name ) ); - template .values .insert_if_empty( "docker_image_name", wca::Value::String( docker_image_name ) ); @@ -187,7 +124,7 @@ mod private .values .insert_if_empty( "gcp_region", wca::Value::String( "europe-central2".into() ) ); } - template.create_all( path )?; + template.files.create_all( path, &template.values )?; Ok( () ) } @@ -196,5 +133,5 @@ mod private crate::mod_interface! { orphan use deploy_renew; - //orphan use DeployTemplate; + orphan use DeployTemplate; } diff --git a/module/move/willbe/src/action/features.rs b/module/move/willbe/src/action/features.rs index 26b8701cc2..cc52f93ff1 100644 --- a/module/move/willbe/src/action/features.rs +++ b/module/move/willbe/src/action/features.rs @@ -1,16 +1,15 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; - use std:: - { - fmt - }; + use std::fmt; use collection::{ BTreeMap, HashMap }; // // use path::AbsolutePath; use former::Former; - use error::{ untyped::Context }; + use error::untyped::Context; // use workspace::Workspace; /// Options available for the .features command @@ -39,25 +38,24 @@ mod private impl fmt::Display for FeaturesReport { - fn fmt( &self, f : &mut fmt::Formatter< '_ >) -> Result< (), fmt::Error > + #[ allow( clippy::match_bool ) ] + fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> Result< (), fmt::Error > { self.inner.iter().try_for_each ( | ( package, features ) | { - writeln!(f, "Package {}:", package)?; + writeln!( f, "Package {package}:" )?; features.iter().try_for_each ( | ( feature, dependencies ) | { - let feature = match self.with_features_deps + // fix clippy + let feature = if self.with_features_deps { - false => format!( "\t{feature}" ), - true - => - { - let deps = dependencies.join( ", " ); - format!( "\t{feature}: [{deps}]" ) - } - }; + let deps = dependencies.join( ", " ); + format!( "\t{feature}: [{deps}]" ) + } + else + { format!( "\t{feature}" ) }; writeln!( f, "{feature}" ) } ) @@ -67,6 +65,8 @@ mod private } /// List features + /// # Errors + /// qqq: doc pub fn features( FeaturesOptions { crate_dir, with_features_deps } : FeaturesOptions ) -> error::untyped::Result< FeaturesReport > // qqq : typed error @@ -78,7 +78,7 @@ mod private { if let Ok( manifest_file ) = package.manifest_file() { - manifest_file.inner().starts_with(crate_dir.clone().absolute_path()) + manifest_file.inner().starts_with( crate_dir.clone().absolute_path() ) } else { @@ -96,11 +96,12 @@ mod private packages // .iter() .for_each - ( | package | - { - let features = package.features(); - report.inner.insert(package.name().to_owned(), features.to_owned()); - } + ( + | package | + { + let features = package.features(); + report.inner.insert( package.name().to_owned(), features.to_owned() ); + } ); Ok( report ) } @@ -112,3 +113,4 @@ crate::mod_interface! orphan use FeaturesOptions; orphan use FeaturesReport; } +// qqq : don't use orphan here \ No newline at end of file diff --git a/module/move/willbe/src/action/list.rs b/module/move/willbe/src/action/list.rs index 6f2708217b..baa74feed0 100644 --- a/module/move/willbe/src/action/list.rs +++ b/module/move/willbe/src/action/list.rs @@ -1,6 +1,8 @@ -/// Internal namespace. +/// Define a private namespace for all its items. +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use std::{ fmt, str }; @@ -13,7 +15,7 @@ mod private }; use error:: { - ErrWith, err, + ErrWith, untyped::{ Context, format_err }, }; use tool::{ TreePrinter, ListNodeReport }; @@ -39,7 +41,7 @@ mod private { "tree" => ListFormat::Tree, "toposort" => ListFormat::Topological, - e => return Err( err!( "Unknown format '{}'. Available values : [tree, toposort]", e )) + e => return Err( error::untyped::format_err!( "Unknown format '{}'. Available values : [tree, toposort]", e ) ) }; Ok( value ) @@ -105,7 +107,7 @@ mod private { "nothing" => ListFilter::Nothing, "local" => ListFilter::Local, - e => return Err( err!( "Unknown filter '{}'. Available values : [nothing, local]", e ) ) + e => return Err( error::untyped::format_err!( "Unknown filter '{}'. Available values : [nothing, local]", e ) ) }; Ok( value ) @@ -285,7 +287,7 @@ mod private ( f, "{}", - v.iter().map( | l | l.to_string() ).collect::< Vec< _ > >().join( "\n" ) + v.iter().map( std::string::ToString::to_string ).collect::< Vec< _ > >().join( "\n" ) ), Self::List( v ) => @@ -293,7 +295,7 @@ mod private ( f, "{}", - v.iter().enumerate().map( |( i, v )| format!( "[{i}] {v}" ) ).collect::< Vec< _ > >().join( "\n" ) + v.iter().enumerate().map( | ( i, v ) | format!( "[{i}] {v}" ) ).collect::< Vec< _ > >().join( "\n" ) ), Self::Empty => write!( f, "Nothing" ), @@ -321,6 +323,7 @@ mod private pub path : Option< ManifestFile >, } + #[ allow( clippy::trivially_copy_pass_by_ref, clippy::needless_lifetimes ) ] fn process_package_dependency< 'a > ( workspace : &Workspace, @@ -347,7 +350,7 @@ mod private name : dependency.name(), // unwrap should be safe because of `semver::VersionReq` version : dependency.req(), - path : dependency.crate_dir().map( | p | p.manifest_file() ), + path : dependency.crate_dir().map( CrateDir::manifest_file ), }; // format!( "{}+{}+{}", dependency.name(), dependency.req(), dependency.crate_dir().unwrap().manifest_file() ); // let dep_id = format!( "{}+{}+{}", dependency.name(), dependency.req(), dependency.path().as_ref().map( | p | p.join( "Cargo.toml" ) ).unwrap_or_default() ); @@ -402,7 +405,7 @@ mod private name : dep.name(), // unwrap should be safe because of `semver::VersionReq` version : dep.req(), - path : dep.crate_dir().map( | p | p.manifest_file() ), + path : dep.crate_dir().map( CrateDir::manifest_file ), }; // if this is a cycle (we have visited this node before) if visited.contains( &dep_id ) @@ -436,9 +439,8 @@ mod private /// - `Result` - A result containing the list report if successful, /// or a tuple containing the list report and error if not successful. #[ cfg_attr( feature = "tracing", tracing::instrument ) ] - pub fn list( args : ListOptions ) - -> - ResultWithReport< ListReport, error::untyped::Error > // qqq : should be specific error + pub fn list_all( args : ListOptions ) + -> ResultWithReport< ListReport, error::untyped::Error > // qqq : should be specific error // qqq : use typed error { let mut report = ListReport::default(); @@ -462,17 +464,32 @@ mod private .package_find_by_manifest( manifest_file ) .ok_or_else( || format_err!( "Package not found in the workspace" ) ) .err_with_report( report )?; + let version = if args.info.contains( &PackageAdditionalInfo::Version ) + { + Some( package.version().to_string() ) + } + else + { + None + }; + let crate_dir = if args.info.contains( &PackageAdditionalInfo::Path ) + { + Some( package.crate_dir() ).transpose() + } + else + { + Ok( None ) + } + .err_with_report( report )?; let mut package_report = tool::ListNodeReport { name : package.name().to_string(), - // qqq : for Bohdan : too long lines - version : if args.info.contains( &PackageAdditionalInfo::Version ) { Some( package.version().to_string() ) } else { None }, - // qqq : for Bohdan : don't put multiline if into struct constructor - crate_dir : if args.info.contains( &PackageAdditionalInfo::Path ) - { Some( package.crate_dir() ).transpose() } - else - { Ok( None ) } - .err_with_report( report )?, + // aaa : for Bohdan : too long lines + // aaa : moved out + version, + // aaa : for Bohdan : don't put multiline if into struct constructor + // aaa : moved out + crate_dir, duplicate : false, normal_dependencies : vec![], dev_dependencies : vec![], @@ -485,7 +502,7 @@ mod private *report = match report { ListReport::Tree( ref mut v ) => ListReport::Tree - ( { v.extend([ printer ]); v.clone() } ), + ( { v.extend( [ printer ] ); v.clone() } ), ListReport::Empty => ListReport::Tree( vec![ printer ] ), ListReport::List( _ ) => unreachable!(), }; @@ -527,7 +544,7 @@ mod private .collect(); for package in packages { - tree_package_report( package.manifest_file().unwrap(), &mut report, &mut visited )? + tree_package_report( package.manifest_file().unwrap(), &mut report, &mut visited )?; } let ListReport::Tree( tree ) = report else { unreachable!() }; let printer = merge_build_dependencies( tree ); @@ -594,7 +611,7 @@ mod private ) .err_with_report( &report )?; let packages_info : collection::HashMap< String, WorkspacePackageRef< '_ > > = - packages.map( | p | ( p.name().to_string(), p ) ).collect(); + packages.map( | p | ( p.name().to_string(), p ) ).collect(); if root_crate.is_empty() { @@ -611,12 +628,12 @@ mod private { if args.info.contains( &PackageAdditionalInfo::Version ) { - name.push_str( " " ); + name.push( ' ' ); name.push_str( &p.version().to_string() ); } if args.info.contains( &PackageAdditionalInfo::Path ) { - name.push_str( " " ); + name.push( ' ' ); name.push_str( &p.manifest_file()?.to_string() ); // aaa : is it safe to use unwrap here? // aaa : should be safe, but now returns an error } @@ -624,7 +641,7 @@ mod private Ok::< String, PathError >( name ) } ) - .collect::< Result< _, _ >>() + .collect::< Result< _, _ > >() .err_with_report( &report )?; report = ListReport::List( names ); @@ -664,12 +681,12 @@ mod private { if args.info.contains( &PackageAdditionalInfo::Version ) { - name.push_str( " " ); + name.push( ' ' ); name.push_str( &p.version().to_string() ); } if args.info.contains( &PackageAdditionalInfo::Path ) { - name.push_str( " " ); + name.push( ' ' ); name.push_str( &p.manifest_file().unwrap().to_string() ); } } @@ -715,7 +732,7 @@ mod private .chain( report.dev_dependencies.iter_mut() ) .chain( report.build_dependencies.iter_mut() ) { - build_deps_acc = merge_build_dependencies_impl(dep, build_deps_acc ); + build_deps_acc = merge_build_dependencies_impl( dep, build_deps_acc ); } for dep in std::mem::take( &mut report.build_dependencies ) @@ -742,7 +759,7 @@ mod private } let printer : Vec< TreePrinter > = report .iter() - .map( | rep | TreePrinter::new( rep ) ) + .map( TreePrinter::new ) .collect(); printer } @@ -774,15 +791,15 @@ mod private fn rearrange_duplicates( mut report : Vec< tool::ListNodeReport > ) -> Vec< tool::TreePrinter > { let mut required_normal : collection::HashMap< usize, Vec< tool::ListNodeReport > > = collection::HashMap::new(); - for i in 0 .. report.len() + for ( i, report ) in report.iter_mut().enumerate() { let ( required, exist ) : ( Vec< _ >, Vec< _ > ) = std::mem::take ( - &mut report[ i ].normal_dependencies + &mut report.normal_dependencies ) .into_iter() .partition( | d | d.duplicate ); - report[ i ].normal_dependencies = exist; + report.normal_dependencies = exist; required_normal.insert( i, required ); } @@ -794,7 +811,7 @@ mod private let printer : Vec< TreePrinter > = report .iter() - .map( | rep | TreePrinter::new( rep ) ) + .map( TreePrinter::new ) .collect(); printer @@ -814,11 +831,10 @@ mod private if !node.duplicate { - if let Some( r ) = required.iter_mut().flat_map( |( _, v )| v ) + if let Some( r ) = required.iter_mut().flat_map( | ( _, v ) | v ) .find ( - | r | - r.name == node.name && r.version == node.version && r.crate_dir == node.crate_dir + | r | r.name == node.name && r.version == node.version && r.crate_dir == node.crate_dir ) { std::mem::swap( r, node ); @@ -849,5 +865,5 @@ crate::mod_interface! /// Contains output of a single node of the action. // own use ListNodeReport; /// List packages in workspace. - orphan use list; + orphan use list_all; } diff --git a/module/move/willbe/src/action/main_header.rs b/module/move/willbe/src/action/main_header.rs index 7c1b5af526..a84ee09d1b 100644 --- a/module/move/willbe/src/action/main_header.rs +++ b/module/move/willbe/src/action/main_header.rs @@ -1,11 +1,10 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use std::fmt::{ Display, Formatter }; - use std::fs:: - { - OpenOptions - }; + use std::fs::OpenOptions; use std::io:: { Read, @@ -16,10 +15,11 @@ mod private use std::path::PathBuf; use regex::Regex; use entity::{ PathError, WorkspaceInitError }; + #[ allow( unused_imports ) ] use error:: { - err, - untyped::Error, + // err, + // untyped::Error, }; use workspace_md_extension::WorkspaceMdExtension; @@ -48,6 +48,7 @@ mod private impl Display for MainHeaderRenewReport { + #[ allow( clippy::collapsible_else_if ) ] fn fmt( &self, f : &mut Formatter< '_ > ) -> std::fmt::Result { if self.success @@ -86,7 +87,7 @@ mod private { /// Represents a common error. #[ error( "Common error: {0}" ) ] - Common(#[ from ] Error ), + Common( #[ from ] error::untyped::Error ), // qqq : rid of /// Represents an I/O error. #[ error( "I/O error: {0}" ) ] IO( #[ from ] std::io::Error ), @@ -116,14 +117,14 @@ mod private // aaa : done let repository_url = workspace .repository_url() - .ok_or_else::< Error, _ > - ( || err!( "repo_url not found in workspace Cargo.toml" ) )?; + .ok_or_else::< error::untyped::Error, _ > + ( || error::untyped::format_err!( "repo_url not found in workspace Cargo.toml" ) )?; let master_branch = workspace.master_branch().unwrap_or( "master".into() ); let workspace_name = workspace .workspace_name() - .ok_or_else::< Error, _ > - ( || err!( "workspace_name not found in workspace Cargo.toml" ) )?; + .ok_or_else::< error::untyped::Error, _ > + ( || error::untyped::format_err!( "workspace_name not found in workspace Cargo.toml" ) )?; let discord_url = workspace.discord_url(); @@ -140,6 +141,7 @@ mod private } /// Convert `Self`to header. + #[ allow( clippy::uninlined_format_args, clippy::wrong_self_convention ) ] fn to_header( self ) -> Result< String, MainHeaderRenewError > { let discord = self.discord_url @@ -158,10 +160,14 @@ mod private ( format! ( - r#"[![{}](https://img.shields.io/github/actions/workflow/status/{}/standard_rust_scheduled.yml?label={}&logo=github&branch={})](https://github.com/{}/actions/workflows/standard_rust_scheduled.yml){} + r"[![{}](https://img.shields.io/github/actions/workflow/status/{}/standard_rust_scheduled.yml?label={}&logo=github&branch={})](https://github.com/{}/actions/workflows/standard_rust_scheduled.yml){} [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2F{}_trivial_sample%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20{}_trivial_sample/https://github.com/{}) -[![docs.rs](https://raster.shields.io/static/v1?label=docs&message=online&color=eee&logo=docsdotrs&logoColor=eee)](https://docs.rs/{})"#, - self.workspace_name, url::git_info_extract( &self.repository_url )?, self.workspace_name, self.master_branch, url::git_info_extract( &self.repository_url )?, +[![docs.rs](https://raster.shields.io/static/v1?label=docs&message=online&color=eee&logo=docsdotrs&logoColor=eee)](https://docs.rs/{})", + self.workspace_name, + url::git_info_extract( &self.repository_url )?, + self.workspace_name, + self.master_branch, + url::git_info_extract( &self.repository_url )?, discord, self.workspace_name.to_lowercase(), self.workspace_name.to_lowercase(), url::git_info_extract( &self.repository_url )?, self.workspace_name, @@ -193,7 +199,14 @@ mod private /// [![docs.rs](https://raster.shields.io/static/v1?label=docs&message=online&color=eee&logo=docsdotrs&logoColor=eee)](https://docs.rs/wtools) /// /// ``` - pub fn readme_header_renew( crate_dir : CrateDir ) + /// + /// # Errors + /// qqq: doc + /// + /// # Panics + /// qqq: doc + #[ allow( clippy::uninlined_format_args ) ] + pub fn action( crate_dir : CrateDir ) // -> Result< MainHeaderRenewReport, ( MainHeaderRenewReport, MainHeaderRenewError ) > -> ResultWithReport< MainHeaderRenewReport, MainHeaderRenewError > { @@ -265,7 +278,7 @@ mod private crate::mod_interface! { /// Generate header. - orphan use readme_header_renew; + own use action; /// Report. orphan use MainHeaderRenewReport; /// Error. diff --git a/module/move/willbe/src/action/mod.rs b/module/move/willbe/src/action/mod.rs index 728271c2a5..2fdbe0633b 100644 --- a/module/move/willbe/src/action/mod.rs +++ b/module/move/willbe/src/action/mod.rs @@ -1,5 +1,10 @@ +// module/move/willbe/src/action/mod.rs +mod private {} + crate::mod_interface! { + /// Generate documentation for a crate. + layer crate_doc; // Added new layer /// Deploy new. layer deploy_renew; /// List packages. diff --git a/module/move/willbe/src/action/publish.rs b/module/move/willbe/src/action/publish.rs index 58412a2d7a..c926c43063 100644 --- a/module/move/willbe/src/action/publish.rs +++ b/module/move/willbe/src/action/publish.rs @@ -1,8 +1,9 @@ -/// Internal namespace. +/// Define a private namespace for all its items. +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; - use std::{ env, fmt, fs }; use { @@ -34,10 +35,10 @@ mod private writeln!( f, "Actions :" )?; for ( path, report ) in &self.packages { - let report = report.to_string().replace("\n", "\n "); + let report = report.to_string().replace( '\n', "\n " ); let path = if let Some( wrd ) = &self.workspace_root_dir { - path.as_ref().strip_prefix( &wrd.as_ref() ).unwrap() + path.as_ref().strip_prefix( wrd.as_ref() ).unwrap() } else { @@ -65,7 +66,7 @@ mod private let mut actually_published : Vec< _ > = self.packages.iter() .filter_map ( - |( path, repo )| + | ( path, repo ) | if repo.publish.as_ref().is_some_and( | r | r.error.is_ok() ) { Some( path.clone() ) @@ -109,6 +110,7 @@ mod private /// /// # Arguments /// * `patterns` - A vector of patterns specifying the folders to search for packages. + /// * `exclude_dev_dependencies` - A boolean value indicating whether to exclude dev dependencies from manifest before publish. /// * `dry` - A boolean value indicating whether to perform a dry run. /// * `temp` - A boolean value indicating whether to use a temporary directory. /// @@ -119,6 +121,8 @@ mod private ( patterns : Vec< String >, channel : channel::Channel, + exclude_dev_dependencies : bool, + commit_changes : bool, dry : bool, temp : bool ) @@ -131,6 +135,7 @@ mod private { let current_path = AbsolutePath::try_from ( + // qqq : dont use canonicalizefunction. path does not have exist fs::canonicalize( pattern.as_str() )? )?; // let current_path = AbsolutePath::try_from( std::path::PathBuf::from( pattern ) )?; @@ -155,7 +160,7 @@ mod private let workspace_root_dir : AbsolutePath = workspace .workspace_root() - .try_into()?; + .into(); let packages = workspace.packages(); let packages_to_publish : Vec< String > = packages @@ -213,7 +218,8 @@ mod private &package_map, &tmp, &packages_to_publish, - dir.clone() + dir.clone(), + exclude_dev_dependencies )?; let subgraph = subgraph .map( | _, n | n, | _, e | e ); @@ -233,6 +239,8 @@ mod private .channel( channel ) .workspace_dir( CrateDir::try_from( workspace_root_dir ).unwrap() ) .option_base_temp_dir( dir.clone() ) + .exclude_dev_dependencies( exclude_dev_dependencies ) + .commit_changes( commit_changes ) .dry( dry ) .roots( roots ) .packages( queue ) @@ -258,7 +266,7 @@ mod private for package_report in publish::perform_packages_publish( plan ).err_with_report( &report )? { let path : &std::path::Path = package_report.get_info.as_ref().unwrap().current_path.as_ref(); - report.packages.push(( AbsolutePath::try_from( path ).unwrap(), package_report )); + report.packages.push( ( AbsolutePath::try_from( path ).unwrap(), package_report ) ); } if let Some( dir ) = temp diff --git a/module/move/willbe/src/action/publish_diff.rs b/module/move/willbe/src/action/publish_diff.rs index d27920c7bc..f170053773 100644 --- a/module/move/willbe/src/action/publish_diff.rs +++ b/module/move/willbe/src/action/publish_diff.rs @@ -1,14 +1,14 @@ -/// Internal namespace. +/// Define a private namespace for all its items. +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; - use path::PathBuf; use collection::HashMap; use std::fmt; use colored::Colorize; use crates_tools::CrateArchive; - use action::list::ListReport; use error::untyped::Result; // qqq : group dependencies @@ -22,6 +22,7 @@ mod private pub struct PublishDiffOptions { path : PathBuf, + exclude_dev_dependencies : bool, keep_archive : Option< PathBuf >, } @@ -42,6 +43,7 @@ mod private let root_name = tree.name.clone(); let root_version = tree.version.as_ref().unwrap().clone(); + #[ allow( clippy::items_after_statements, clippy::option_map_unit_fn ) ] fn modify( diffs : &HashMap< AbsolutePath, DiffReport >, tree : &mut ListNodeReport ) { let path = tree.crate_dir.take().unwrap(); @@ -74,7 +76,7 @@ mod private for dep in &mut tree.normal_dependencies { - modify( diffs, dep ) + modify( diffs, dep ); } } modify( &self.diffs, &mut tree ); @@ -82,7 +84,7 @@ mod private let root = AbsolutePath::from( root_path ); let diff = self.diffs.get( &root ).unwrap(); let printer = TreePrinter::new( &tree ); - writeln!( f, "Tree:\n{}", printer )?; + writeln!( f, "Tree:\n{printer}" )?; if diff.has_changes() { writeln!( f, "Changes detected in `{root_name} {root_version}`:" )?; @@ -91,7 +93,7 @@ mod private { writeln!( f, "No changes found in `{root_name} {root_version}`. Files:" )?; } - write!( f, "{}", diff )?; + write!( f, "{diff}" )?; Ok( () ) } @@ -105,14 +107,14 @@ mod private let path = AbsolutePath::try_from( o.path )?; let dir = CrateDir::try_from( path.clone() )?; - let list = action::list + let list = action::list_all ( action::list::ListOptions::former() .path_to_manifest( dir ) .format( action::list::ListFormat::Tree ) - .info([ action::list::PackageAdditionalInfo::Version, action::list::PackageAdditionalInfo::Path ]) - .dependency_sources([ action::list::DependencySource::Local ]) - .dependency_categories([ action::list::DependencyCategory::Primary ]) + .info( [ action::list::PackageAdditionalInfo::Version, action::list::PackageAdditionalInfo::Path ] ) + .dependency_sources( [ action::list::DependencySource::Local ] ) + .dependency_categories( [ action::list::DependencyCategory::Primary ] ) .form() ) .unwrap(); @@ -141,21 +143,21 @@ mod private let name = &package.name()?; let version = &package.version()?; - _ = cargo::pack - ( - cargo::PackOptions::former() - .path( dir.as_ref() ) - .allow_dirty( true ) - .checking_consistency( false ) - .dry( false ).form() - )?; - let l = CrateArchive::read( packed_crate::local_path( name, version, dir )? )?; - let r = CrateArchive::download_crates_io( name, version ).unwrap(); + _ = cargo::pack + ( + cargo::PackOptions::former() + .path( dir.as_ref() ) + .allow_dirty( true ) + .checking_consistency( false ) + .dry( false ).form() + )?; + let l = CrateArchive::read( packed_crate::local_path( name, version, dir )? )?; + let r = CrateArchive::download_crates_io( name, version ).unwrap(); if let Some( out_path ) = &o.keep_archive { - _ = std::fs::create_dir_all( &out_path ); + _ = std::fs::create_dir_all( out_path ); for path in r.list() { let local_path = out_path.join( path ); @@ -167,11 +169,11 @@ mod private std::fs::write( local_path, content )?; } } - diffs.insert( path, crate_diff( &l, &r ).exclude( diff::PUBLISH_IGNORE_LIST ) ); + diffs.insert( path, crate_diff( &l, &r, o.exclude_dev_dependencies ).exclude( diff::PUBLISH_IGNORE_LIST ) ); let report = tasks[ current_idx ].info.normal_dependencies.clone(); let printer : Vec< TreePrinter > = report .iter() - .map( | rep | TreePrinter::new( rep ) ) + .map( TreePrinter::new ) .collect(); tasks.extend( printer ); diff --git a/module/move/willbe/src/action/readme_health_table_renew.rs b/module/move/willbe/src/action/readme_health_table_renew.rs index 438c44dcd8..b97b600c8f 100644 --- a/module/move/willbe/src/action/readme_health_table_renew.rs +++ b/module/move/willbe/src/action/readme_health_table_renew.rs @@ -1,9 +1,11 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; - use std:: { + fmt::Write as FmtWrite, fs::{ OpenOptions, File }, io::{ Write, Read, Seek, SeekFrom }, }; @@ -37,14 +39,14 @@ mod private ( regex::bytes::Regex::new ( - r#""# + r"" ).unwrap() ).ok(); CLOSE_TAG.set ( regex::bytes::Regex::new ( - r#""# + r"" ).unwrap() ).ok(); } @@ -109,7 +111,7 @@ mod private else { // qqq : for Petro : use typed error - Err( HealthTableRenewError::Common( error::untyped::Error::msg( "Cannot find Cargo.toml" ))) + Err( HealthTableRenewError::Common( error::untyped::Error::msg( "Cannot find Cargo.toml" ) ) ) } } @@ -131,6 +133,7 @@ mod private /// Structure that holds the parameters for generating a table. #[ derive( Debug ) ] + #[ allow( clippy::struct_excessive_bools ) ] struct TableOptions { // Relative path from workspace root to directory with modules @@ -149,25 +152,18 @@ mod private { fn from( value : HashMap< String, query::Value > ) -> Self { + // fix clippy let include_branches = value - .get( "with_branches" ) - .map( | v | bool::from( v ) ) - .unwrap_or( true ); + .get( "with_branches" ).is_none_or(bool::from); let include_stability = value - .get( "with_stability" ) - .map( | v | bool::from( v ) ) - .unwrap_or( true ); + .get( "with_stability" ).is_none_or(bool::from); let include_docs = value - .get( "with_docs" ) - .map( | v | bool::from( v ) ) - .unwrap_or( true ); + .get( "with_docs" ).is_none_or(bool::from); let include = value - .get( "with_gitpod" ) - .map( | v | bool::from( v ) ) - .unwrap_or( true ); + .get( "with_gitpod" ).is_none_or(bool::from); let b_p = value.get( "1" ); let base_path = if let Some( query::Value::String( path ) ) = value.get( "path" ).or( b_p ) @@ -198,53 +194,51 @@ mod private let cargo_toml_path = path.join( "Cargo.toml" ); if !cargo_toml_path.exists() { - return Err( HealthTableRenewError::Common( error::untyped::Error::msg( "Cannot find Cargo.toml" ))) + return Err( HealthTableRenewError::Common( error::untyped::Error::msg( "Cannot find Cargo.toml" ) ) ) } - else + + let mut contents = String::new(); + File::open( cargo_toml_path )?.read_to_string( &mut contents )?; + let doc = contents.parse::< Document >()?; + + let core_url = + doc + .get( "workspace" ) + .and_then( | workspace | workspace.get( "metadata" ) ) + .and_then( | metadata | metadata.get( "repo_url" ) ) + .and_then( | url | url.as_str() ) + .map( String::from ); + + let branches = + doc + .get( "workspace" ) + .and_then( | workspace | workspace.get( "metadata" ) ) + .and_then( | metadata | metadata.get( "branches" ) ) + .and_then( | branches | branches.as_array() ) + .map + ( + | array | + array + .iter() + .filter_map( | value | value.as_str() ) + .map( String::from ) + .collect::< Vec< String > >() + ); + let mut user_and_repo = String::new(); + if let Some( core_url ) = &core_url { - let mut contents = String::new(); - File::open( cargo_toml_path )?.read_to_string( &mut contents )?; - let doc = contents.parse::< Document >()?; - - let core_url = - doc - .get( "workspace" ) - .and_then( | workspace | workspace.get( "metadata" ) ) - .and_then( | metadata | metadata.get( "repo_url" ) ) - .and_then( | url | url.as_str() ) - .map( String::from ); - - let branches = - doc - .get( "workspace" ) - .and_then( | workspace | workspace.get( "metadata" ) ) - .and_then( | metadata | metadata.get( "branches" ) ) - .and_then( | branches | branches.as_array()) - .map - ( - | array | - array - .iter() - .filter_map( | value | value.as_str() ) - .map( String::from ) - .collect::< Vec< String > >() - ); - let mut user_and_repo = "".to_string(); - if let Some( core_url ) = &core_url + user_and_repo = url::git_info_extract( core_url )?; + } + Ok + ( + Self { - user_and_repo = url::git_info_extract( core_url )?; + core_url : core_url.unwrap_or_default(), + user_and_repo, + branches, + workspace_root : path.to_path_buf() } - Ok - ( - Self - { - core_url : core_url.unwrap_or_default(), - user_and_repo, - branches, - workspace_root : path.to_path_buf() - } - ) - } + ) } } @@ -259,6 +253,12 @@ mod private /// will mean that at this place the table with modules located in the directory module/core will be generated. /// The tags do not disappear after generation. /// Anything between the opening and closing tag will be destroyed. + /// + /// # Errors + /// qqq: doc + /// + /// # Panics + /// qqq: doc // aaa : for Petro : typed errors // aaa : done pub fn readme_health_table_renew( path : &Path ) -> Result< (), HealthTableRenewError > @@ -284,8 +284,8 @@ mod private let mut tags_closures = vec![]; let mut tables = vec![]; - let open_caps = TAG_TEMPLATE.get().unwrap().captures_iter( &*contents ); - let close_caps = CLOSE_TAG.get().unwrap().captures_iter( &*contents ); + let open_caps = TAG_TEMPLATE.get().unwrap().captures_iter( &contents ); + let close_caps = CLOSE_TAG.get().unwrap().captures_iter( &contents ); // iterate by regex matches and generate table content for each dir which taken from open-tag for ( open_captures, close_captures ) in open_caps.zip( close_caps ) { @@ -324,6 +324,7 @@ mod private } /// Writes tables into a file at specified positions. + #[ allow( clippy::needless_pass_by_value ) ] fn tables_write_into_file ( tags_closures : Vec< ( usize, usize ) >, @@ -341,11 +342,11 @@ mod private ) in tags_closures.iter().zip( tables.iter() ) { - range_to_target_copy( &*contents, &mut buffer, start, *end_of_start_tag )?; + range_to_target_copy( &contents, &mut buffer, start, *end_of_start_tag )?; range_to_target_copy( con.as_bytes(), &mut buffer, 0,con.len() - 1 )?; start = *start_of_end_tag; } - range_to_target_copy( &*contents,&mut buffer,start,contents.len() - 1 )?; + range_to_target_copy( &contents,&mut buffer,start,contents.len() - 1 )?; file.set_len( 0 )?; file.seek( SeekFrom::Start( 0 ) )?; file.write_all( &buffer )?; @@ -353,7 +354,7 @@ mod private } /// Generate table from `table_parameters`. - /// Generate header, iterate over all modules in package (from table_parameters) and append row. + /// Generate header, iterate over all modules in package (from `table_parameters`) and append row. fn package_readme_health_table_generate ( workspace : &Workspace, @@ -369,7 +370,7 @@ mod private workspace .packages() )?; - let mut table = table_header_generate( parameters, &table_parameters ); + let mut table = table_header_generate( parameters, table_parameters ); for package_name in directory_names { let stability = if table_parameters.include_stability @@ -388,7 +389,7 @@ mod private { None }; - if parameters.core_url == "" + if parameters.core_url.is_empty() { let module_path = workspace .workspace_root() @@ -420,7 +421,7 @@ ensure that at least one remotest is present in git. ", &package_name, stability.as_ref(), parameters, - &table_parameters + table_parameters ) ); } @@ -429,6 +430,7 @@ ensure that at least one remotest is present in git. ", /// Return topologically sorted modules name, from packages list, in specified directory. // fn directory_names( path : PathBuf, packages : &[ WorkspacePackageRef< '_ > ] ) -> Result< Vec< String > > + #[ allow( clippy::type_complexity, clippy::unnecessary_wraps ) ] fn directory_names< 'a > ( path : PathBuf, @@ -478,7 +480,7 @@ ensure that at least one remotest is present in git. ", let module_graph = graph::construct( &module_packages_map ); let names : Vec< String > = graph::topological_sort_with_grouping( module_graph ) .into_iter() - .map + .flat_map ( | mut group | { @@ -486,7 +488,6 @@ ensure that at least one remotest is present in git. ", group } ) - .flatten() .map( | n | n.to_string() ) .collect(); @@ -511,35 +512,32 @@ ensure that at least one remotest is present in git. ", ); if table_parameters.include_stability { - let mut stability = stability_generate( &stability.as_ref().unwrap() ); + let mut stability = stability_generate( stability.as_ref().unwrap() ); stability.push_str( " |" ); rou.push_str( &stability ); } if parameters.branches.is_some() && table_parameters.include_branches { - rou.push_str( &branch_cells_generate( ¶meters, &module_name ) ); + rou.push_str( &branch_cells_generate( parameters, module_name ) ); } if table_parameters.include_docs { - rou.push_str + write! ( - &format! - ( - " [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/{}) |", - &module_name - ) - ); + rou, + " [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/{module_name}) |" + ).expect( "Writing to String shouldn't fail" ); } if table_parameters.include { - let path = Path::new( table_parameters.base_path.as_str() ).join( &module_name ); + let path = Path::new( table_parameters.base_path.as_str() ).join( module_name ); let p = Path::new( ¶meters.workspace_root ).join( &path ); // let path = table_parameters.base_path. - let example = if let Some( name ) = find_example_file( p.as_path(), &module_name ) + let example = if let Some( name ) = find_example_file( p.as_path(), module_name ) { - let path = path.to_string_lossy().replace( '\\', "/" ).replace( "/", "%2F" ); + let path = path.to_string_lossy().replace( '\\', "/" ).replace( '/', "%2F" ); let tmp = name.to_string_lossy().replace( '\\', "/" ); - let file_name = tmp.split( '/' ).last().unwrap(); + let file_name = tmp.split( '/' ).next_back().unwrap(); let name = file_name.strip_suffix( ".rs" ).unwrap(); format! ( @@ -552,14 +550,15 @@ ensure that at least one remotest is present in git. ", } else { - "".into() + String::new() }; - rou.push_str( &format!( " {} |", example ) ); + write!(rou, " {example} |").expect( "Writing to String shouldn't fail" ); } format!( "{rou}\n" ) } /// todo + #[ must_use ] pub fn find_example_file( base_path : &Path, module_name : &str ) -> Option< PathBuf > { let examples_dir = base_path.join("examples" ); @@ -568,19 +567,18 @@ ensure that at least one remotest is present in git. ", { if let Ok( entries ) = std::fs::read_dir( &examples_dir ) { - for entry in entries + for entry in entries.flatten() { - if let Ok( entry ) = entry + + let file_name = entry.file_name(); + if let Some( file_name_str ) = file_name.to_str() { - let file_name = entry.file_name(); - if let Some( file_name_str ) = file_name.to_str() + if file_name_str == format!( "{module_name}_trivial.rs" ) { - if file_name_str == format!( "{module_name}_trivial.rs" ) - { - return Some( entry.path() ) - } + return Some( entry.path() ) } } + } } } @@ -588,19 +586,20 @@ ensure that at least one remotest is present in git. ", // If module_trivial.rs doesn't exist, return any other file in the examples directory if let Ok( entries ) = std::fs::read_dir( &examples_dir ) { - for entry in entries + for entry in entries.flatten() { - if let Ok( entry ) = entry + + let file_name = entry.file_name(); + if let Some( file_name_str ) = file_name.to_str() { - let file_name = entry.file_name(); - if let Some( file_name_str ) = file_name.to_str() + // fix clippy + if std::path::Path::new( file_name_str ) + .extension().is_some_and(| ext | ext.eq_ignore_ascii_case( "rs" )) { - if file_name_str.ends_with( ".rs" ) - { - return Some( entry.path() ) - } + return Some( entry.path() ) } } + } } @@ -608,15 +607,21 @@ ensure that at least one remotest is present in git. ", } /// Generate stability cell based on stability + #[ must_use ] pub fn stability_generate( stability : &Stability ) -> String { match stability { - Stability::Experimental => " [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)".into(), - Stability::Stable => " [![stability-stable](https://img.shields.io/badge/stability-stable-green.svg)](https://github.com/emersion/stability-badges#stable)".into(), - Stability::Deprecated => " [![stability-deprecated](https://img.shields.io/badge/stability-deprecated-red.svg)](https://github.com/emersion/stability-badges#deprecated)".into(), - Stability::Unstable => " [![stability-unstable](https://img.shields.io/badge/stability-unstable-yellow.svg)](https://github.com/emersion/stability-badges#unstable)".into(), - Stability::Frozen => " [![stability-frozen](https://img.shields.io/badge/stability-frozen-blue.svg)](https://github.com/emersion/stability-badges#frozen)".into(), + Stability::Experimental => + " [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)".into(), + Stability::Stable => + " [![stability-stable](https://img.shields.io/badge/stability-stable-green.svg)](https://github.com/emersion/stability-badges#stable)".into(), + Stability::Deprecated => + " [![stability-deprecated](https://img.shields.io/badge/stability-deprecated-red.svg)](https://github.com/emersion/stability-badges#deprecated)".into(), + Stability::Unstable => + " [![stability-unstable](https://img.shields.io/badge/stability-unstable-yellow.svg)](https://github.com/emersion/stability-badges#unstable)".into(), + Stability::Frozen => + " [![stability-frozen](https://img.shields.io/badge/stability-frozen-blue.svg)](https://github.com/emersion/stability-badges#frozen)".into(), } } @@ -642,7 +647,7 @@ ensure that at least one remotest is present in git. ", { for branch in branches { - header.push_str( format!( " {} |", branch ).as_str() ); + header.push_str( format!( " {branch} |" ).as_str() ); separator.push_str( "--------|" ); } } @@ -660,7 +665,7 @@ ensure that at least one remotest is present in git. ", separator.push_str( ":------:|" ); } - format!( "{}\n{}\n", header, separator ) + format!( "{header}\n{separator}\n" ) } /// Generate cells for each branch @@ -703,10 +708,7 @@ ensure that at least one remotest is present in git. ", target.extend_from_slice( &source[ from..= to ] ); return Ok( () ) } - else - { - Err( HealthTableRenewError::Common( error::untyped::Error::msg( "Incorrect indexes" ))) - } + Err( HealthTableRenewError::Common( error::untyped::Error::msg( "Incorrect indexes" ) ) ) } } diff --git a/module/move/willbe/src/action/readme_modules_headers_renew.rs b/module/move/willbe/src/action/readme_modules_headers_renew.rs index 5370a9d0fa..6996506873 100644 --- a/module/move/willbe/src/action/readme_modules_headers_renew.rs +++ b/module/move/willbe/src/action/readme_modules_headers_renew.rs @@ -1,5 +1,7 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use std:: { @@ -20,11 +22,11 @@ mod private use package::Package; use error:: { - err, + // err, untyped:: { // Result, - Error as wError, + // Error as wError, Context, }, }; @@ -101,7 +103,7 @@ mod private { /// Represents a common error. #[ error( "Common error: {0}" ) ] - Common(#[ from ] wError ), + Common(#[ from ] error::untyped::Error ), // qqq : rid of /// Represents an I/O error. #[ error( "I/O error: {0}" ) ] IO( #[ from ] std::io::Error ), @@ -130,21 +132,26 @@ mod private { /// Create `ModuleHeader` instance from the folder where Cargo.toml is stored. - fn from_cargo_toml< 'a > + #[ allow( clippy::needless_pass_by_value ) ] + fn from_cargo_toml ( - package : Package< 'a >, - default_discord_url : &Option< String >, + package : Package< '_ >, + // fix clippy + default_discord_url : Option< &String >, ) -> Result< Self, ModulesHeadersRenewError > { let stability = package.stability()?; let module_name = package.name()?; let repository_url = package.repository()? - .ok_or_else::< wError, _ >( || err!( "Fail to find repository_url in module`s Cargo.toml" ) )?; + .ok_or_else::< error::untyped::Error, _ > + ( + || error::untyped::format_err!( "Fail to find repository_url in module`s Cargo.toml" ) + )?; let discord_url = package .discord_url()? - .or_else( || default_discord_url.clone() ); + .or_else( || default_discord_url.cloned() ); Ok ( Self @@ -159,6 +166,7 @@ mod private } /// Convert `ModuleHeader`to header. + #[ allow( clippy::uninlined_format_args, clippy::wrong_self_convention ) ] fn to_header( self, workspace_path : &str ) -> Result< String, ModulesHeadersRenewError > { let discord = self.discord_url.map( | discord_url | @@ -172,25 +180,26 @@ mod private let repo_url = url::repo_url_extract( &self.repository_url ) .and_then( | r | url::git_info_extract( &r ).ok() ) - .ok_or_else::< wError, _ >( || err!( "Fail to parse repository url" ) )?; + .ok_or_else::< error::untyped::Error, _ >( || error::untyped::format_err!( "Fail to parse repository url" ) )?; let example= if let Some( name ) = find_example_file ( self.module_path.as_path(), &self.module_name ) { - let relative_path = proper_path_tools::path::path_relative + let relative_path = pth::path::path_relative ( - workspace_path.try_into().unwrap(), + workspace_path.into(), name ) .to_string_lossy() .to_string(); + // fix clippy #[ cfg( target_os = "windows" ) ] - let relative_path = relative_path.replace( "\\", "/" ); + let relative_path = relative_path.replace( '\\', "/" ); // aaa : for Petro : use path_toools // aaa : used - let p = relative_path.replace( "/","%2F" ); + let p = relative_path.replace( '/',"%2F" ); format! ( " [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE={},RUN_POSTFIX=--example%20{}/https://github.com/{})", @@ -201,7 +210,7 @@ mod private } else { - "".into() + String::new() }; Ok( format! ( @@ -239,6 +248,12 @@ mod private /// [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://github.com/Username/test/actions/workflows/ModuleChainOfPackagesAPush.yml/badge.svg)](https://github.com/Username/test/actions/workflows/ModuleChainOfPackagesAPush.yml)[![docs.rs](https://img.shields.io/docsrs/_chain_of_packages_a?color=e3e8f0&logo=docs.rs)](https://docs.rs/_chain_of_packages_a)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2F_chain_of_packages_a_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20_chain_of_packages_a_trivial/https://github.com/Username/test) /// /// ``` + /// + /// # Errors + /// qqq: doc + /// + /// # Panics + /// qqq: doc pub fn readme_modules_headers_renew( crate_dir : CrateDir ) -> ResultWithReport< ModulesHeadersRenewReport, ModulesHeadersRenewError > // -> Result< ModulesHeadersRenewReport, ( ModulesHeadersRenewReport, ModulesHeadersRenewError ) > @@ -253,7 +268,7 @@ mod private let paths : Vec< AbsolutePath > = workspace .packages() - .filter_map( | p | p.manifest_file().ok().and_then( | a | Some( a.inner() ) ) ) + .filter_map( | p | p.manifest_file().ok().map( crate::entity::files::ManifestFile::inner ) ) .collect(); report.found_files = paths @@ -269,7 +284,7 @@ mod private .join ( repository::readme_path( path.parent().unwrap().as_ref() ) - // .ok_or_else::< wError, _ >( || err!( "Fail to find README.md at {}", &path ) ) + // .ok_or_else::< error::untyped::Error, _ >( || error::untyped::format_err!( "Fail to find README.md at {}", &path ) ) .err_with_report( &report )? ); @@ -284,8 +299,8 @@ mod private .err_with_report( &report )? ) .err_with_report( &report )?; - - let header = ModuleHeader::from_cargo_toml( pakage.into(), &discord_url ) + // fix clippy + let header = ModuleHeader::from_cargo_toml( pakage, discord_url.as_ref() ) .err_with_report( &report )?; let mut file = OpenOptions::new() @@ -324,6 +339,7 @@ mod private Ok( report ) } + #[ allow( clippy::uninlined_format_args ) ] fn header_content_generate< 'a > ( content : &'a str, @@ -340,7 +356,7 @@ mod private .unwrap() .replace ( - &content, + content, &format! ( "\n{}\n", diff --git a/module/move/willbe/src/action/test.rs b/module/move/willbe/src/action/test.rs index be0b90405c..4cb28966aa 100644 --- a/module/move/willbe/src/action/test.rs +++ b/module/move/willbe/src/action/test.rs @@ -1,6 +1,7 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use entity::test::{ TestPlan, TestOptions, TestsReport, tests_run }; @@ -31,6 +32,7 @@ mod private /// - The `exclude_features` field is a vector of strings representing the names of features to exclude when running tests. /// - The `include_features` field is a vector of strings representing the names of features to include when running tests. #[ derive( Debug, Former ) ] + #[ allow( clippy::struct_excessive_bools ) ] pub struct TestsCommandOptions { dir : AbsolutePath, @@ -63,15 +65,21 @@ mod private /// It is possible to enable and disable various features of the crate. /// The function also has the ability to run tests in parallel using `Rayon` crate. /// The result of the tests is written to the structure `TestsReport` and returned as a result of the function execution. + /// # Errors + /// qqq: doc + /// + /// # Panics + /// qqq: doc // zzz : it probably should not be here // xxx : use newtype + #[ allow( clippy::too_many_lines ) ] pub fn test( o : TestsCommandOptions, dry : bool ) -> ResultWithReport< TestsReport, Error > // qqq : for Petro : typed error // -> Result< TestsReport, ( TestsReport, Error ) > { - // qqq : incapsulate progress bar logic into some function of struct. don't keep it here + // aaa : incapsulate progress bar logic into some function of struct. don't keep it here // aaa : done let mut report = TestsReport::default(); @@ -84,16 +92,18 @@ mod private // aaa : for Petro : non readable // aaa : readable and with actual command return Err - (( - report, - format_err! + ( ( - "Missing toolchain(-s) that was required : [{}]. \ + report, + format_err! + ( + "Missing toolchain(-s) that was required : [{}]. \ Try to install it with `rustup install {}` command(-s)", - channels_diff.iter().join( ", " ), - channels_diff.iter().join( " " ) - ) - )) + channels_diff.iter().join( ", " ), + channels_diff.iter().join( " " ) + ) + ) + ) } report.dry = dry; let TestsCommandOptions @@ -123,6 +133,7 @@ Try to install it with `rustup install {}` command(-s)", data_type::Either::Right( manifest ) => CrateDir::from( manifest ) }; + #[ allow( clippy::useless_conversion ) ] let workspace = Workspace ::try_from( CrateDir::try_from( path.clone() ).err_with_report( &report )? ) .err_with_report( &report )? @@ -164,7 +175,7 @@ Try to install it with `rustup install {}` command(-s)", ).err_with_report( &report )?; println!( "{plan}" ); - // aaa : split on two functions for create plan and for execute + // aaa : split on two functions for create plan and for execute // aaa : it's already separated, look line: 203 : let result = tests_run( &options ); let temp_path = if temp diff --git a/module/move/willbe/src/action/workspace_renew.rs b/module/move/willbe/src/action/workspace_renew.rs index 58e4ad61ea..da6bf4bb2f 100644 --- a/module/move/willbe/src/action/workspace_renew.rs +++ b/module/move/willbe/src/action/workspace_renew.rs @@ -1,5 +1,7 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use std::fs; use std::path::Path; @@ -24,6 +26,7 @@ mod private impl WorkspaceTemplate { /// Returns template parameters + #[ must_use ] pub fn get_parameters( &self ) -> &TemplateParameters { &self.parameters @@ -41,9 +44,9 @@ mod private .form(); Self { - files : Default::default(), + files : WorkspaceTemplateFiles::default(), parameters, - values : Default::default(), + values : TemplateValues::default(), } } } @@ -60,57 +63,57 @@ mod private { let formed = TemplateFilesBuilder::former() .file() - .data( include_str!( "../../template/workspace/.gitattributes" ) ) - .path( "./.gitattributes" ) - .end() + .data( include_str!( "../../template/workspace/.gitattributes" ) ) + .path( "./.gitattributes" ) + .end() .file() - .data( include_str!( "../../template/workspace/.gitignore1" ) ) - .path( "./.gitignore" ) - .end() + .data( include_str!( "../../template/workspace/.gitignore1" ) ) + .path( "./.gitignore" ) + .end() .file() - .data( include_str!( "../../template/workspace/.gitpod.yml" ) ) - .path( "./.gitpod.yml" ) - .end() + .data( include_str!( "../../template/workspace/.gitpod.yml" ) ) + .path( "./.gitpod.yml" ) + .end() .file() - .data( include_str!( "../../template/workspace/Cargo.hbs" ) ) - .path( "./Cargo.toml" ) - .is_template( true ) - .end() + .data( include_str!( "../../template/workspace/Cargo.hbs" ) ) + .path( "./Cargo.toml" ) + .is_template( true ) + .end() .file() - .data( include_str!( "../../template/workspace/Makefile" ) ) - .path( "./Makefile" ) - .end() + .data( include_str!( "../../template/workspace/Makefile" ) ) + .path( "./Makefile" ) + .end() .file() - .data( include_str!( "../../template/workspace/Readme.md" ) ) - .path( "./Readme.md" ) - .end() + .data( include_str!( "../../template/workspace/Readme.md" ) ) + .path( "./Readme.md" ) + .end() .file() - .data( include_str!( "../../template/workspace/.cargo/config.toml" ) ) - .path( "./.cargo/config.toml" ) - .end() + .data( include_str!( "../../template/workspace/.cargo/config.toml" ) ) + .path( "./.cargo/config.toml" ) + .end() .file() - .data( include_str!( "../../template/workspace/module/module1/Cargo.toml.x" ) ) - .path( "./module/Cargo.toml" ) - .end() + .data( include_str!( "../../template/workspace/module/module1/Cargo.toml.x" ) ) + .path( "./module/Cargo.toml" ) + .end() .file() - .data( include_str!( "../../template/workspace/module/module1/Readme.md" ) ) - .path( "./module/module1/Readme.md" ) - .end() + .data( include_str!( "../../template/workspace/module/module1/Readme.md" ) ) + .path( "./module/module1/Readme.md" ) + .end() .file() - .data - ( - include_str!( "../../template/workspace/module/module1/examples/module1_example.rs" ) - ) - .path( "./module/module1/examples/module1_example.rs" ) - .end() + .data + ( + include_str!( "../../template/workspace/module/module1/examples/module1_example.rs" ) + ) + .path( "./module/module1/examples/module1_example.rs" ) + .end() .file() - .data( include_str!( "../../template/workspace/module/module1/src/lib.rs" ) ) - .path( "./module/module1/src/lib.rs" ) - .end() + .data( include_str!( "../../template/workspace/module/module1/src/lib.rs" ) ) + .path( "./module/module1/src/lib.rs" ) + .end() .file() - .data( include_str!( "../../template/workspace/module/module1/tests/hello_test.rs" ) ) - .path( "./module/module1/tests/hello_test.rs" ) - .end() + .data( include_str!( "../../template/workspace/module/module1/tests/hello_test.rs" ) ) + .path( "./module/module1/tests/hello_test.rs" ) + .end() .form(); Self( formed.files ) @@ -134,7 +137,11 @@ mod private // qqq : for Petro : should return report // qqq : for Petro : should have typed error /// Creates workspace template - pub fn workspace_renew + /// # Errors + /// qqq: doc + /// # Panics + /// qqq: doc + pub fn action ( path : &Path, mut template : WorkspaceTemplate, @@ -162,7 +169,7 @@ mod private "branches", wca::Value::String ( - branches.into_iter().map( | b | format!( r#""{}""#, b ) ).join( ", " ) + branches.into_iter().map( | b | format!( r#""{b}""# ) ).join( ", " ) ) ); template.files.create_all( path, &template.values )?; @@ -172,6 +179,6 @@ mod private crate::mod_interface! { - exposed use workspace_renew; + own use action; orphan use WorkspaceTemplate; } diff --git a/module/move/willbe/src/bin/cargo-will.rs b/module/move/willbe/src/bin/cargo-will.rs index 53aa39e51e..00c223060d 100644 --- a/module/move/willbe/src/bin/cargo-will.rs +++ b/module/move/willbe/src/bin/cargo-will.rs @@ -3,11 +3,11 @@ #![ doc( html_root_url = "https://docs.rs/willbe/" ) ] #![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] -#[ allow( unused_imports ) ] +#[ allow( unused_imports, clippy::wildcard_imports ) ] use::willbe::*; fn main() -> Result< (), error::untyped::Error > { let args = std::env::args().skip( 1 ).collect(); - Ok( willbe::run( args )? ) + willbe::run( args ) } diff --git a/module/move/willbe/src/bin/will.rs b/module/move/willbe/src/bin/will.rs index cbaad31299..b4c1df035a 100644 --- a/module/move/willbe/src/bin/will.rs +++ b/module/move/willbe/src/bin/will.rs @@ -6,12 +6,12 @@ #![ doc( html_root_url = "https://docs.rs/willbe/" ) ] #![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] -#[ allow( unused_imports ) ] +#[ allow( unused_imports, clippy::wildcard_imports ) ] use::willbe::*; fn main() -> Result< (), error::untyped::Error > { - Ok( willbe::run( std::env::args().collect() )? ) + willbe::run( std::env::args().collect() ) } // cargo_subcommand_metadata::description!( "xxx" ); diff --git a/module/move/willbe/src/bin/willbe.rs b/module/move/willbe/src/bin/willbe.rs index 5943573a67..1ad0cfeab7 100644 --- a/module/move/willbe/src/bin/willbe.rs +++ b/module/move/willbe/src/bin/willbe.rs @@ -3,10 +3,10 @@ #![ doc( html_root_url = "https://docs.rs/willbe/" ) ] #![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] -#[ allow( unused_imports ) ] +#[ allow( unused_imports, clippy::wildcard_imports ) ] use::willbe::*; fn main() -> Result< (), error::untyped::Error > { - Ok( willbe::run( std::env::args().collect() )? ) + willbe::run( std::env::args().collect() ) } diff --git a/module/move/willbe/src/command/cicd_renew.rs b/module/move/willbe/src/command/cicd_renew.rs index 50b1a8de91..07f7f53d24 100644 --- a/module/move/willbe/src/command/cicd_renew.rs +++ b/module/move/willbe/src/command/cicd_renew.rs @@ -1,5 +1,6 @@ mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use error::{ untyped::Context }; @@ -7,10 +8,12 @@ mod private /// /// Generate table. /// + /// # Errors + /// qqq: doc // qqq : typed error pub fn cicd_renew() -> error::untyped::Result< () > { - action::cicd_renew + action::cicd_renew::action ( &std::env::current_dir()? ) diff --git a/module/move/willbe/src/command/crate_doc.rs b/module/move/willbe/src/command/crate_doc.rs new file mode 100644 index 0000000000..5afa7a1ba3 --- /dev/null +++ b/module/move/willbe/src/command/crate_doc.rs @@ -0,0 +1,78 @@ +// module/move/willbe/src/command/crate_doc.rs +mod private +{ + #[ allow( clippy::wildcard_imports ) ] + use crate::*; + + use std::path::PathBuf; + use wca::VerifiedCommand; + use error::untyped::Error; // Use untyped::Error for the command return + use entity::{ Workspace, WorkspaceInitError, PathError }; // Import Workspace, WorkspaceInitError, PathError + use path::{ AbsolutePath, CurrentPath }; // Import AbsolutePath and CurrentPath from pth + + /// + /// Generate documentation for a crate in a single Markdown file. + /// + /// # Errors + /// Returns an error if the command arguments are invalid, the workspace cannot be loaded, + /// or if the documentation generation action fails. + #[allow(clippy::needless_pass_by_value)] + pub fn crate_doc( o : VerifiedCommand ) -> error::untyped::Result< () > + { + let path_arg : PathBuf = o.args.get_owned( 0 ).unwrap_or_else( || "./".into() ); + + // qqq : xxx : refactor this block + // Use the requested `pth::absolute::join` function (see qqq in pth/src/lib.rs) + // to simplify this path resolution. The call should look something like: + // `let absolute_path = pth::absolute::join( ( CurrentPath, path_arg.clone() ) )?` + // This assumes `join_absolute` takes a tuple and handles the logic internally. + // Determine the absolute path explicitly + let absolute_path = if path_arg.is_relative() + { + // If relative, resolve it against the current directory + let current_dir = AbsolutePath::try_from( CurrentPath ) + .map_err( | e | Error::new( e ).context( "Failed to get current directory" ) )?; + current_dir.join( path_arg.clone() ) // Clone path_arg as join consumes it + } + else + { + // If already absolute, try to create AbsolutePath directly + AbsolutePath::try_from( path_arg.clone() ) + .map_err( | e | Error::new( e ).context( format!( "Invalid absolute path provided: {}", path_arg.display() ) ) )? + }; + // Note: AbsolutePath::try_from also performs canonicalization implicitly via path::canonicalize + + // Create CrateDir from the verified AbsolutePath + let crate_dir = CrateDir::try_from( absolute_path ) // This should now work as AbsolutePath is canonical + .map_err( | e : PathError | Error::new( e ).context( "Failed to identify crate directory (does Cargo.toml exist?)" ) )?; + + // Load the workspace based on the crate directory + let workspace = Workspace::try_from( crate_dir.clone() ) + .map_err( | e : WorkspaceInitError | Error::new( e ).context( "Failed to load workspace information" ) )?; + + // Parse output property + let output_path_req : Option< PathBuf > = o.props.get_owned( "output" ); + + // Call the action, passing the workspace reference + match action::crate_doc::doc( &workspace, &crate_dir, output_path_req ) + { + Ok( report ) => + { + println!( "{report}" ); // Print the success report + Ok( () ) + } + Err( ( report, e ) ) => + { + eprintln!( "{report}" ); // Print the report even on failure + // Convert the specific CrateDocError into a general untyped::Error for the command return + Err( Error::new( e ).context( "Documentation generation failed" ) ) + } + } + } +} + +crate::mod_interface! +{ + /// Generate documentation for a crate. + orphan use crate_doc; +} \ No newline at end of file diff --git a/module/move/willbe/src/command/deploy_renew.rs b/module/move/willbe/src/command/deploy_renew.rs index c66107fe8d..1ede46795a 100644 --- a/module/move/willbe/src/command/deploy_renew.rs +++ b/module/move/willbe/src/command/deploy_renew.rs @@ -1,23 +1,25 @@ mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use wca::VerifiedCommand; use error::{ untyped::Context }; - use tool::TemplateHolder; - //use tool::template::Template; - // use action::deploy_renew::*; + #[ allow( clippy::wildcard_imports ) ] + use action::deploy_renew::*; /// /// Create new deploy. /// - + /// # Errors + /// qqq: doc // xxx : qqq : typed error + #[ allow( clippy::needless_pass_by_value ) ] pub fn deploy_renew( o : VerifiedCommand ) -> error::untyped::Result< () > { let current_dir = std::env::current_dir()?; - let mut template = TemplateHolder::default(); + let mut template = DeployTemplate::default(); _ = template.load_existing_params( ¤t_dir ); let parameters = template.parameters(); let mut values = parameters.values_from_props( &o.props ); diff --git a/module/move/willbe/src/command/features.rs b/module/move/willbe/src/command/features.rs index d57a8a7dc0..1e6c19deef 100644 --- a/module/move/willbe/src/command/features.rs +++ b/module/move/willbe/src/command/features.rs @@ -1,5 +1,6 @@ mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use action::features::FeaturesOptions; @@ -13,10 +14,13 @@ mod private /// /// List features of a package. /// - + /// # Errors + /// qqq: doc + #[ allow( clippy::needless_pass_by_value ) ] pub fn features( o : VerifiedCommand ) -> error::untyped::Result< () > // qqq : use typed error { let path : PathBuf = o.args.get_owned( 0 ).unwrap_or_else( || "./".into() ); + // qqq : dont use canonicalizefunction. path does not have exist let crate_dir = CrateDir::try_from( fs::canonicalize( path )? )?; let with_features_deps = o .props diff --git a/module/move/willbe/src/command/list.rs b/module/move/willbe/src/command/list.rs index c1bb086099..1e5e6fe5ae 100644 --- a/module/move/willbe/src/command/list.rs +++ b/module/move/willbe/src/command/list.rs @@ -1,6 +1,8 @@ /// Internal namespace. +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use std:: @@ -9,7 +11,7 @@ mod private path::PathBuf, }; use wca::VerifiedCommand; - use error::{ untyped::Context }; + use error::untyped::Context; use collection::HashSet; use action:: @@ -20,6 +22,7 @@ mod private use former::Former; #[ derive( Former ) ] + #[ allow( clippy::struct_excessive_bools ) ] struct ListProperties { #[ former( default = ListFormat::Tree ) ] @@ -46,14 +49,14 @@ mod private /// /// List workspace packages. /// - + /// # Errors + /// qqq: doc // qqq : typed error pub fn list( o : VerifiedCommand ) -> error::untyped::Result< () > { let path_to_workspace : PathBuf = o.args .get_owned( 0 ) .unwrap_or( std::env::current_dir().context( "Workspace list command without subject" )? ); - // let path_to_workspace = AbsolutePath::try_from( fs::canonicalize( path_to_workspace )? )?; let ListProperties { format, with_version, with_path, with_local, with_remote, with_primary, with_dev, with_build } = o.props.try_into()?; @@ -80,13 +83,13 @@ mod private .dependency_categories( categories ) .form(); - match action::list( o ) + match action::list_all( o ) { Ok( report ) => { println!( "{report}" ); } - Err(( report, e )) => + Err( ( report, e ) ) => { eprintln!( "{report}" ); @@ -97,10 +100,10 @@ mod private Ok( () ) } - impl TryFrom< wca::Props > for ListProperties + impl TryFrom< wca::executor::Props > for ListProperties { type Error = error::untyped::Error; - fn try_from( value : wca::Props ) -> Result< Self, Self::Error > + fn try_from( value : wca::executor::Props ) -> Result< Self, Self::Error > { let mut this = Self::former(); diff --git a/module/move/willbe/src/command/main_header.rs b/module/move/willbe/src/command/main_header.rs index efd23e67c4..2e850208bc 100644 --- a/module/move/willbe/src/command/main_header.rs +++ b/module/move/willbe/src/command/main_header.rs @@ -1,14 +1,18 @@ mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; - use action; + // use action; use error::untyped::{ Error }; /// Generates header to main Readme.md file. + /// + /// # Errors + /// qqq: doc // qqq : typed error pub fn readme_header_renew() -> error::untyped::Result< () > { - match action::readme_header_renew + match crate::action::main_header::action ( CrateDir::transitive_try_from::< AbsolutePath >( CurrentPath )? ) diff --git a/module/move/willbe/src/command/mod.rs b/module/move/willbe/src/command/mod.rs index f6115c3f10..8423756275 100644 --- a/module/move/willbe/src/command/mod.rs +++ b/module/move/willbe/src/command/mod.rs @@ -1,13 +1,15 @@ -/// Internal namespace. +// module/move/willbe/src/command/mod.rs +/// Define a private namespace for all its items. mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use wca::{ Type, CommandsAggregator, CommandsAggregatorFormer }; /// /// Form CA commands grammar. /// - + #[ allow( clippy::too_many_lines ) ] pub fn ca() -> CommandsAggregatorFormer { CommandsAggregator::former() @@ -25,6 +27,16 @@ mod private .kind( Type::String ) .optional( true ) .end() + .property( "exclude_dev_dependencies" ) + .hint( "Setting this option to true will temporarily remove development dependencies before executing the command, then restore them afterward. Default is `true`." ) + .kind( Type::Bool ) + .optional( true ) + .end() + .property( "commit_changes" ) + .hint( "Indicates whether changes should be committed. Default is `false`." ) + .kind( Type::Bool ) + .optional( true ) + .end() .property( "dry" ) .hint( "Enables 'dry run'. Does not publish, only simulates. Default is `true`." ) .kind( Type::Bool ) @@ -47,6 +59,11 @@ mod private .kind( Type::Path ) .optional( true ) .end() + .property( "exclude_dev_dependencies" ) + .hint( "Setting this option to true will temporarily remove development dependencies before executing the command, then restore them afterward. Default is `true`." ) + .kind( Type::Bool ) + .optional( true ) + .end() .property( "keep_archive" ) .hint( "Save remote package version to the specified path" ) .kind( Type::Path ) @@ -128,8 +145,8 @@ with_gitpod: If set to 1, a column with a link to Gitpod will be added. Clicking .end() .command( "test" ) - .hint( "execute tests in specific packages" ) - .long_hint( "this command runs tests in designated packages based on the provided path. It allows for inclusion and exclusion of features, testing on different Rust version channels, parallel execution, and feature combination settings." ) + .hint( "List crate features to run tests for each combination, aiming for full test coverage of the crate." ) + .long_hint( "List crate features, different optimization level (Release & Debug) and toolchain (stable & nightly) to run tests for each combination. Сan be used for packages as well as workspaces. Supports parallel execution." ) .subject().hint( "A path to directories with packages. If no path is provided, the current directory is used." ).kind( Type::Path ).optional( true ).end() .property( "dry" ).hint( "Enables 'dry run'. Does not run tests, only simulates. Default is `true`." ).kind( Type::Bool ).optional( true ).end() .property( "temp" ).hint( "If flag is `true` all test will be running in temporary directories. Default `true`." ).kind( Type::Bool ).optional( true ).end() @@ -225,7 +242,7 @@ with_gitpod: If set to 1, a column with a link to Gitpod will be added. Clicking .command( "deploy.renew" ) .hint( "Create deploy template" ) - .long_hint( "Creates static files and directories.\nDeployment to different hosts is done via Makefile." ) + .long_hint( "Creates static files and directories.\nDeployment to different hosts is done via Makefile.\n\nUsage example: deploy.renew gcp_project_id:wtools" ) .property( "gcp_project_id" ) .hint( "Google Cloud Platform Project id for image deployment, terraform state bucket, and, if specified, GCE instance deployment." ) .kind( Type::String ) @@ -239,12 +256,12 @@ with_gitpod: If set to 1, a column with a link to Gitpod will be added. Clicking .property( "gcp_artifact_repo_name" ) .hint( "Google Cloud Platform Artifact Repository to store docker image in. Will be generated from current directory name if unspecified." ) .kind( Type::String ) - .optional( false ) + .optional( true ) .end() .property( "docker_image_name" ) .hint( "Docker image name to build and deploy. Will be generated from current directory name if unspecified." ) .kind( Type::String ) - .optional( false ) + .optional( true ) .end() .routine( command::deploy_renew ) .end() @@ -282,6 +299,23 @@ with_gitpod: If set to 1, a column with a link to Gitpod will be added. Clicking .end() .routine( command::features ) .end() + + // Updated command definition + .command( "crate.doc" ) + .hint( "Generate documentation for a crate in a single Markdown file." ) + .long_hint( "Generates documentation for the specified crate and outputs it as a single Markdown file." ) + .subject() + .hint( "Path to the crate directory. If not specified, uses the current directory." ) + .kind( Type::Path ) + .optional( true ) + .end() + .property( "output" ) // Added output property + .hint( "Path to the output Markdown file. Defaults to {crate_name}_doc.md in the crate directory." ) + .kind( Type::Path ) + .optional( true ) + .end() + .routine( command::crate_doc ) + .end() } } @@ -290,6 +324,8 @@ crate::mod_interface! own use ca; + /// Generate documentation for a crate. + layer crate_doc; /// List packages. layer list; /// Publish packages. diff --git a/module/move/willbe/src/command/publish.rs b/module/move/willbe/src/command/publish.rs index a70af4265d..17622a61a1 100644 --- a/module/move/willbe/src/command/publish.rs +++ b/module/move/willbe/src/command/publish.rs @@ -1,20 +1,26 @@ -/// Internal namespace. +/// Define a private namespace for all its items. +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use colored::Colorize; - use wca::VerifiedCommand; - use error::{ untyped::Context }; // xxx + use error::untyped::Context; // xxx use former::Former; use std::fmt::Write; use channel::Channel; #[ derive( Former ) ] + #[ allow( clippy::struct_excessive_bools ) ] struct PublishProperties { #[ former( default = Channel::Stable ) ] channel : Channel, + #[ former( default = false ) ] + exclude_dev_dependencies : bool, + #[ former( default = false ) ] + commit_changes : bool, #[ former( default = true ) ] dry : bool, #[ former( default = true ) ] @@ -24,7 +30,8 @@ mod private /// /// Publish package. /// - + /// # Errors + /// qqq: doc pub fn publish( o : VerifiedCommand ) -> error::untyped::Result< () > // qqq : use typed error { let args_line = format! @@ -35,14 +42,11 @@ mod private .get_owned( 0 ) .unwrap_or( std::path::PathBuf::from( "" ) ).display() ); - let prop_line = format! - ( - "{}", - o - .props - .iter() - .map( | p | format!( "{}:{}", p.0, p.1.to_string() ) ) - .collect::< Vec< _ > >().join(" ") ); + let prop_line = o + .props + .iter() + .map( | p | format!( "{}:{}", p.0, p.1 ) ) + .collect::< Vec< _ > >().join(" "); let patterns : Vec< _ > = o .args @@ -52,10 +56,12 @@ mod private let PublishProperties { channel, + exclude_dev_dependencies, + commit_changes, dry, temp } = o.props.try_into()?; - let plan = action::publish_plan( patterns, channel, dry, temp ) + let plan = action::publish_plan( patterns, channel, exclude_dev_dependencies, commit_changes, dry, temp ) .context( "Failed to plan the publication process" )?; let mut formatted_plan = String::new(); @@ -77,9 +83,9 @@ mod private if dry && !report.packages.is_empty() { - let args = if args_line.is_empty() { String::new() } else { format!(" {}", args_line) }; - let prop = if prop_line.is_empty() { String::new() } else { format!(" {}", prop_line) }; - let line = format!("will .publish{}{} dry:0", args, prop ); + let args = if args_line.is_empty() { String::new() } else { format!(" {args_line}" ) }; + let prop = if prop_line.is_empty() { String::new() } else { format!(" {prop_line}" ) }; + let line = format!("will .publish{args}{prop} dry:0" ); println!("To apply plan, call the command `{}`", line.blue() ); // aaa : for Petro : for Bohdan : bad. should be exact command with exact parameters // aaa : it`s already works @@ -95,10 +101,10 @@ mod private } } - impl TryFrom< wca::Props > for PublishProperties + impl TryFrom< wca::executor::Props > for PublishProperties { type Error = error::untyped::Error; - fn try_from( value : wca::Props ) -> Result< Self, Self::Error > + fn try_from( value : wca::executor::Props ) -> Result< Self, Self::Error > { let mut this = Self::former(); @@ -110,6 +116,10 @@ mod private else { this }; + this = if let Some( v ) = value + .get_owned( "exclude_dev_dependencies" ) { this.exclude_dev_dependencies::< bool >( v ) } else { this }; + this = if let Some( v ) = value + .get_owned( "commit_changes" ) { this.commit_changes::< bool >( v ) } else { this }; this = if let Some( v ) = value .get_owned( "dry" ) { this.dry::< bool >( v ) } else { this }; this = if let Some( v ) = value diff --git a/module/move/willbe/src/command/publish_diff.rs b/module/move/willbe/src/command/publish_diff.rs index 4691331866..240dc1e7f8 100644 --- a/module/move/willbe/src/command/publish_diff.rs +++ b/module/move/willbe/src/command/publish_diff.rs @@ -1,5 +1,6 @@ mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use std::fs; @@ -13,6 +14,8 @@ mod private #[ derive( former::Former ) ] struct PublishDiffProperties { + #[ former( default = false ) ] + exclude_dev_dependencies : bool, keep_archive : Option< PathBuf >, } @@ -29,20 +32,24 @@ mod private /// # Errors /// /// Returns an error if there is an issue with the command. - + /// + /// # Panics + /// qqq: doc pub fn publish_diff( o : VerifiedCommand ) -> error::untyped::Result< () > // qqq : use typed error { let path : PathBuf = o.args.get_owned( 0 ).unwrap_or( std::env::current_dir()? ); - let PublishDiffProperties { keep_archive } = o.props.try_into()?; + let PublishDiffProperties { keep_archive, exclude_dev_dependencies } = o.props.try_into()?; let mut o = action::PublishDiffOptions::former() - .path( path ); + .path( path ) + .exclude_dev_dependencies( exclude_dev_dependencies ); if let Some( k ) = keep_archive.clone() { o = o.keep_archive( k ); } let o = o.form(); println!( "{}", action::publish_diff( o )? ); if let Some( keep ) = keep_archive { + // qqq : dont use canonicalizefunction. path does not have exist let keep = AbsolutePath::try_from( fs::canonicalize( keep )? ).unwrap(); println!( "Remote version of the package was saved at `{}`", keep.as_ref().display() ); } @@ -50,13 +57,19 @@ mod private Ok( () ) } - impl TryFrom< wca::Props > for PublishDiffProperties + impl TryFrom< wca::executor::Props > for PublishDiffProperties { type Error = error::untyped::Error; - fn try_from( value : wca::Props ) -> Result< Self, Self::Error > + fn try_from( value : wca::executor::Props ) -> Result< Self, Self::Error > { let mut this = Self::former(); + this = if let Some( v ) = value + .get_owned( "exclude_dev_dependencies" ) + { this.exclude_dev_dependencies::< bool >( v ) } + else + { this }; + this = if let Some( v ) = value .get_owned( "keep_archive" ) { this.keep_archive::< PathBuf >( v ) } diff --git a/module/move/willbe/src/command/readme_headers_renew.rs b/module/move/willbe/src/command/readme_headers_renew.rs index 9a79a2b144..7c39c2169c 100644 --- a/module/move/willbe/src/command/readme_headers_renew.rs +++ b/module/move/willbe/src/command/readme_headers_renew.rs @@ -1,8 +1,10 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; - use action; - use error::{ err }; + // use action; + // use error::{ err }; use std::fmt::{ Display, Formatter }; #[ derive( Debug, Default ) ] @@ -64,8 +66,9 @@ mod private } } - /// Aggregates two commands: `generate_modules_headers` & `generate_main_header` + /// # Errors + /// qqq: doc pub fn readme_headers_renew() -> error::untyped::Result< () > // qqq : use typed error { let mut report = ReadmeHeadersRenewReport::default(); @@ -73,7 +76,7 @@ mod private let crate_dir = CrateDir::transitive_try_from::< AbsolutePath >( CurrentPath )?; let mut fail = false; - match action::readme_header_renew( crate_dir.clone() ) + match crate::action::main_header::action( crate_dir.clone() ) { Ok( r ) => { @@ -85,7 +88,7 @@ mod private report.main_header_renew_report = r; report.main_header_renew_error = Some( error ); } - }; + } match action::readme_modules_headers_renew( crate_dir ) { Ok( r ) => @@ -103,7 +106,7 @@ mod private if fail { eprintln!( "{report}" ); - Err( err!( "Something went wrong" ) ) + Err( error::untyped::format_err!( "Something went wrong" ) ) } else { diff --git a/module/move/willbe/src/command/readme_health_table_renew.rs b/module/move/willbe/src/command/readme_health_table_renew.rs index c91b5b6357..c569a0a1b8 100644 --- a/module/move/willbe/src/command/readme_health_table_renew.rs +++ b/module/move/willbe/src/command/readme_health_table_renew.rs @@ -1,5 +1,6 @@ mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use error::{ untyped::Context }; @@ -7,6 +8,8 @@ mod private /// /// Generate table. /// + /// # Errors + /// qqq: doc // qqq : typed error pub fn readme_health_table_renew() -> error::untyped::Result< () > { diff --git a/module/move/willbe/src/command/readme_modules_headers_renew.rs b/module/move/willbe/src/command/readme_modules_headers_renew.rs index 391205210e..bfd5ed7db1 100644 --- a/module/move/willbe/src/command/readme_modules_headers_renew.rs +++ b/module/move/willbe/src/command/readme_modules_headers_renew.rs @@ -1,10 +1,14 @@ mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; // use path::AbsolutePath; // use error::{ untyped::Error }; /// Generate headers for workspace members + /// + /// # Errors + /// qqq: doc // qqq : typed error pub fn readme_modules_headers_renew() -> error::untyped::Result< () > { diff --git a/module/move/willbe/src/command/test.rs b/module/move/willbe/src/command/test.rs index 9a05c92c89..a31daf2142 100644 --- a/module/move/willbe/src/command/test.rs +++ b/module/move/willbe/src/command/test.rs @@ -1,6 +1,7 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use collection::HashSet; @@ -17,6 +18,7 @@ mod private use optimization::Optimization; #[ derive( Former, Debug ) ] + #[ allow( clippy::struct_excessive_bools ) ] struct TestsProperties { #[ former( default = true ) ] @@ -48,6 +50,8 @@ mod private } /// run tests in specified crate + /// # Errors + /// qqq: doc // qqq : don't use 1-prameter Result pub fn test( o : VerifiedCommand ) -> error::untyped::Result< () > // qqq : use typed error { @@ -60,17 +64,14 @@ mod private .unwrap_or( std::path::PathBuf::from( "" ) ) .display() ); - let prop_line = format! - ( - "{}", - o - .props - .iter() - .map( | p | format!( "{}:{}", p.0, p.1.to_string() ) ) - .collect::< Vec< _ > >().join(" ") - ); + let prop_line = o + .props + .iter() + .map( | p | format!( "{}:{}", p.0, p.1 ) ) + .collect::< Vec< _ > >().join(" "); let path : PathBuf = o.args.get_owned( 0 ).unwrap_or_else( || "./".into() ); + // qqq : dont use canonicalizefunction. path does not have exist let path = AbsolutePath::try_from( fs::canonicalize( path )? )?; let TestsProperties { @@ -127,10 +128,10 @@ Set at least one of them to true." ); { if dry { - let args = if args_line.is_empty() { String::new() } else { format!(" {}", args_line) }; - let prop = if prop_line.is_empty() { String::new() } else { format!(" {}", prop_line) }; - let line = format!("will .publish{}{} dry:0", args, prop); - println!("To apply plan, call the command `{}`", line.blue()); + let args = if args_line.is_empty() { String::new() } else { format!(" {args_line}" ) }; + let prop = if prop_line.is_empty() { String::new() } else { format!(" {prop_line}" ) }; + let line = format!( "will .publish{args}{prop} dry:0" ); + println!( "To apply plan, call the command `{}`", line.blue() ); } else { @@ -147,10 +148,10 @@ Set at least one of them to true." ); } } - impl TryFrom< wca::Props > for TestsProperties + impl TryFrom< wca::executor::Props > for TestsProperties { type Error = error::untyped::Error; - fn try_from( value : wca::Props ) -> Result< Self, Self::Error > + fn try_from( value : wca::executor::Props ) -> Result< Self, Self::Error > { let mut this = Self::former(); @@ -192,4 +193,4 @@ crate::mod_interface! { /// run tests in specified crate exposed use test; -} \ No newline at end of file +} diff --git a/module/move/willbe/src/command/workspace_renew.rs b/module/move/willbe/src/command/workspace_renew.rs index 7baa1515f6..9ea05e64a7 100644 --- a/module/move/willbe/src/command/workspace_renew.rs +++ b/module/move/willbe/src/command/workspace_renew.rs @@ -1,10 +1,11 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use former::Former; - use wca::VerifiedCommand; - use error::{ untyped::Context }; + use error::untyped::Context; use action::WorkspaceTemplate; #[ derive( Former ) ] @@ -17,13 +18,14 @@ mod private /// /// Create new workspace. /// - + /// # Errors + /// qqq: doc // qqq : typed error pub fn workspace_renew( o : VerifiedCommand ) -> error::untyped::Result< () > // qqq : use typed error { let WorkspaceNewProperties { repository_url, branches } = o.props.try_into()?; let template = WorkspaceTemplate::default(); - action::workspace_renew + action::workspace_renew::action ( &std::env::current_dir()?, template, @@ -33,11 +35,11 @@ mod private .context( "Fail to create workspace" ) } - impl TryFrom< wca::Props > for WorkspaceNewProperties + impl TryFrom< wca::executor::Props > for WorkspaceNewProperties { type Error = error::untyped::Error; - fn try_from( value : wca::Props ) -> std::result::Result< Self, Self::Error > + fn try_from( value : wca::executor::Props ) -> std::result::Result< Self, Self::Error > { let mut this = Self::former(); diff --git a/module/move/willbe/src/entity/channel.rs b/module/move/willbe/src/entity/channel.rs index cb45418c06..c073db2922 100644 --- a/module/move/willbe/src/entity/channel.rs +++ b/module/move/willbe/src/entity/channel.rs @@ -1,5 +1,7 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use std:: { @@ -9,6 +11,7 @@ mod private use path::Path; use collection::HashSet; use error::untyped::{ Error }; + #[ allow( clippy::wildcard_imports ) ] use process_tools::process::*; /// The `Channel` enum represents different release channels for rust. @@ -51,6 +54,9 @@ mod private /// Retrieves a list of available channels. /// /// This function takes a path and returns a `Result` with a vector of strings representing the available channels. + /// + /// # Errors + /// qqq: doc // qqq : typed error pub fn available_channels< P >( path : P ) -> error::untyped::Result< HashSet< Channel > > where @@ -61,7 +67,7 @@ mod private .bin_path( program ) .args( options.into_iter().map( OsString::from ).collect::< Vec< _ > >() ) .current_path( path.as_ref().to_path_buf() ) - .run().map_err::< Error, _ >( | report | err!( report.to_string() ) )?; + .run().map_err::< Error, _ >( | report | error::untyped::format_err!( report.to_string() ) )?; let list = report .out @@ -73,7 +79,7 @@ mod private "stable" => Some( Channel::Stable ), "nightly" => Some( Channel::Nightly ), _ => None - } ) + }) .collect(); Ok( list ) diff --git a/module/move/willbe/src/entity/code.rs b/module/move/willbe/src/entity/code.rs index 5c8418bad8..6ca091b1f4 100644 --- a/module/move/willbe/src/entity/code.rs +++ b/module/move/willbe/src/entity/code.rs @@ -1,5 +1,7 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use std:: @@ -13,11 +15,12 @@ mod private /// typically as a string. This can be useful for generating code from various data structures /// or objects. /// - /// ``` pub trait AsCode { /// Converts the object to its code representation. - fn as_code< 'a >( &'a self ) -> std::io::Result< Cow< 'a, str > >; + /// # Errors + /// qqq: doc + fn as_code( &self ) -> std::io::Result< Cow< '_, str > >; } /// A trait for retrieving an iterator over items of a source file. diff --git a/module/move/willbe/src/entity/dependency.rs b/module/move/willbe/src/entity/dependency.rs index 337ecb01a2..50d6c08f8b 100644 --- a/module/move/willbe/src/entity/dependency.rs +++ b/module/move/willbe/src/entity/dependency.rs @@ -1,6 +1,7 @@ mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; // use crates_tools::CrateArchive; @@ -19,12 +20,13 @@ mod private { inner : &'a cargo_metadata::Dependency, } - - impl< 'a > DependencyRef< 'a > + // fix clippy + impl DependencyRef< '_ > { /// The file system path for a local path dependency. /// Only produced on cargo 1.51+ + #[ must_use ] pub fn crate_dir( &self ) -> Option< CrateDir > { match &self.inner.path @@ -35,12 +37,14 @@ mod private } /// Name as given in the Cargo.toml. + #[ must_use ] pub fn name( &self ) -> String { self.inner.name.clone() } /// The kind of dependency this is. + #[ must_use ] pub fn kind( &self ) -> DependencyKind { match self.inner.kind @@ -53,6 +57,7 @@ mod private } /// Required version + #[ must_use ] pub fn req( &self ) -> semver::VersionReq { self.inner.req.clone() @@ -114,7 +119,7 @@ mod private { Self { - name : value.name().into(), + name : value.name(), crate_dir : value.crate_dir(), // path : value.path().clone().map( | path | AbsolutePath::try_from( path ).unwrap() ), } @@ -161,10 +166,16 @@ mod private // qqq : for Bohdan : poor description /// Recursive implementation of the `list` function - pub fn _list< 'a > + /// # Errors + /// qqq: doc + /// + /// # Panics + /// qqq: doc + #[ allow( clippy::needless_pass_by_value, clippy::implicit_hasher ) ] + pub fn list_rec ( workspace : &Workspace, // aaa : for Bohdan : no mut // aaa : no mut - package : &Package< 'a >, + package : &Package< '_ >, graph : &mut collection::HashMap< CrateId, collection::HashSet< CrateId > >, opts : DependenciesOptions ) @@ -183,7 +194,7 @@ mod private let manifest_file = &package.manifest_file(); let package = workspace - .package_find_by_manifest( &manifest_file ) + .package_find_by_manifest( manifest_file ) .ok_or( format_err!( "Package not found in the workspace with path : `{}`", manifest_file.as_ref().display() ) )?; let deps : collection::HashSet< _ > = package @@ -203,7 +214,7 @@ mod private if graph.get( &dep ).is_none() { // unwrap because `recursive` + `with_remote` not yet implemented - _list + list_rec ( workspace, &dep.crate_dir.unwrap().try_into()?, @@ -229,18 +240,21 @@ mod private /// # Returns /// /// If the operation is successful, returns a vector of `PathBuf` objects, where each `PathBuf` represents the path to a local dependency of the specified package. + /// # Errors + /// qqq: doc // qqq : typed error? - pub fn list< 'a > + #[ allow( clippy::needless_pass_by_value ) ] + pub fn list ( workspace : &mut Workspace, - package : &Package< 'a >, + package : &Package< '_ >, opts : DependenciesOptions ) // qqq : use typed error -> error::untyped::Result< Vec< CrateId > > { let mut graph = collection::HashMap::new(); - let root = _list( workspace, package, &mut graph, opts.clone() )?; + let root = list_rec( workspace, package, &mut graph, opts.clone() )?; let output = match opts.sort { @@ -260,8 +274,13 @@ mod private } DependenciesSort::Topological => { - // qqq : too long line - graph::toposort( graph::construct( &graph ) ).map_err( | err | format_err!( "{}", err ) )?.into_iter().filter( | x | x != &root ).collect() + // aaa : too long line + // aaa : splited + graph::toposort( graph::construct( &graph ) ) + .map_err( | err | format_err!( "{}", err ) )? + .into_iter() + .filter( | x | x != &root ) + .collect() }, }; @@ -281,7 +300,7 @@ crate::mod_interface! own use CrateId; own use DependenciesSort; own use DependenciesOptions; - own use _list; + own use list_rec; own use list; } diff --git a/module/move/willbe/src/entity/diff.rs b/module/move/willbe/src/entity/diff.rs index 08b0638b77..99f7f2e1af 100644 --- a/module/move/willbe/src/entity/diff.rs +++ b/module/move/willbe/src/entity/diff.rs @@ -1,11 +1,10 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; - use std:: - { - fmt::Formatter, - }; + use std::fmt::Formatter; use path::PathBuf; use collection::HashMap; use colored::Colorize; @@ -73,6 +72,9 @@ mod private /// # Returns /// /// Returns a new instance of the struct with the excluded items removed from the internal report. + /// # Panics + /// qqq: doc + #[ must_use ] pub fn exclude< Is, I >( mut self, items : Is ) -> Self where Is : Into< HashSet< I > >, @@ -89,14 +91,15 @@ mod private Self( map ) } - /// Checks if there are any changes in the DiffItems. + /// Checks if there are any changes in the `DiffItems`. /// /// # Returns - /// * `true` if there are changes in any of the DiffItems. - /// * `false` if all DiffItems are the same. + /// * `true` if there are changes in any of the `DiffItems`. + /// * `false` if all `DiffItems` are the same. + #[ must_use ] pub fn has_changes( &self ) -> bool { - !self.0.iter().all( |( _, item )| matches!( item, DiffItem::File( Diff::Same( () ) ) )) + !self.0.iter().all( | ( _, item ) | matches!( item, DiffItem::File( Diff::Same( () ) ) ) ) } } @@ -104,7 +107,7 @@ mod private { fn fmt( &self, f : &mut Formatter< '_ > ) -> std::fmt::Result { - for ( path , diff ) in self.0.iter().sorted_by_key( |( k, _ )| k.as_path() ) + for ( path , diff ) in self.0.iter().sorted_by_key( | ( k, _ ) | k.as_path() ) { match diff { @@ -112,10 +115,10 @@ mod private { match item { - Diff::Same( _ ) => writeln!( f, " {}", path.display() )?, - Diff::Add( _ ) => writeln!( f, "+ {} NEW", path.to_string_lossy().green() )?, - Diff::Rem( _ ) => writeln!( f, "- {} REMOVED", path.to_string_lossy().red() )?, - }; + Diff::Same( () ) => writeln!( f, " {}", path.display() )?, + Diff::Add( () ) => writeln!( f, "+ {} NEW", path.to_string_lossy().green() )?, + Diff::Rem( () ) => writeln!( f, "- {} REMOVED", path.to_string_lossy().red() )?, + } } DiffItem::Content( items ) => { @@ -127,14 +130,14 @@ mod private { match item { - Diff::Same( t ) => write!( f, "| {}", t )?, + Diff::Same( t ) => write!( f, "| {t}" )?, Diff::Add( t ) => write!( f, "| + {}", t.green() )?, Diff::Rem( t ) => write!( f, "| - {}", t.red() )?, - }; + } } writeln!( f, "{}", "=".repeat( len + 2 ) )?; } - }; + } } Ok( () ) @@ -149,24 +152,29 @@ mod private /// # Arguments /// /// * `left`: A reference to the first crate archive. - /// Changes that are present here but lacking in 'right' are classified as additions. + /// Changes that are present here but lacking in 'right' are classified as additions. /// * `right`: A reference to the second crate archive. - /// Changes not found in 'left' but present in 'right' are classified as removals. + /// Changes not found in 'left' but present in 'right' are classified as removals. /// /// # Returns /// /// A `DiffReport` struct, representing the unique and shared attributes of the two crate archives. - pub fn crate_diff( left : &CrateArchive, right : &CrateArchive ) -> DiffReport + /// # Panics + /// qqq: doc + #[ must_use ] + pub fn crate_diff( left : &CrateArchive, right : &CrateArchive, exclude_dev_dependencies : bool ) -> DiffReport { let mut report = DiffReport::default(); let local_package_files : HashSet< _ > = left.list().into_iter().collect(); let remote_package_files : HashSet< _ > = right.list().into_iter().collect(); + let local_only = local_package_files.difference( &remote_package_files ); let remote_only = remote_package_files.difference( &local_package_files ); let both = local_package_files.intersection( &remote_package_files ); + for &path in local_only { report.0.insert( path.to_path_buf(), DiffItem::File( Diff::Add( () ) ) ); @@ -179,9 +187,39 @@ mod private for &path in both { + // unwraps are safe because the paths to the files was compared previously - let local = left.content_bytes( path ).unwrap(); - let remote = right.content_bytes( path ).unwrap(); + let mut local = left.content_bytes( path ).unwrap(); + let mut remote = right.content_bytes( path ).unwrap(); + + + let ( l, r ) = if path.ends_with( "Cargo.toml.orig" ) && exclude_dev_dependencies + { + + let local = std::str::from_utf8( left.content_bytes( path ).unwrap() ).unwrap(); + let mut local_data = local.parse::< toml_edit::Document >().unwrap(); + local_data.remove( "dev-dependencies" ); + let local = local_data.to_string().as_bytes().to_vec(); + + + let remote = std::str::from_utf8( right.content_bytes( path ).unwrap() ).unwrap(); + let mut remote_data = remote.parse::< toml_edit::Document >().unwrap(); + remote_data.remove( "dev-dependencies" ); + let remote = remote_data.to_string().as_bytes().to_vec(); + + ( local, remote ) + } + else + { + ( vec![], vec![] ) + }; + + + if !l.is_empty() && !r.is_empty() + { + local = l.as_slice(); + remote = r.as_slice(); + } if local == remote { @@ -206,10 +244,11 @@ mod private items.push( item ); } } + report.0.insert( path.to_path_buf(), DiffItem::Content( items ) ); } } - + report } } diff --git a/module/move/willbe/src/entity/features.rs b/module/move/willbe/src/entity/features.rs index 300fa7ca2f..ae7cfabd64 100644 --- a/module/move/willbe/src/entity/features.rs +++ b/module/move/willbe/src/entity/features.rs @@ -1,5 +1,7 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use collection::{ BTreeSet, HashSet }; use error::untyped::{ bail }; // xxx @@ -38,7 +40,10 @@ mod private /// let feature_combinations = features_powerset( &package, power, &exclude_features, &include_features, enabled_features, false, false ); /// // Use `feature_combinations` as needed. /// ``` - + /// + /// # Errors + /// qqq: doc + #[ allow( clippy::too_many_arguments ) ] pub fn features_powerset ( package : WorkspacePackageRef< '_ >, @@ -58,7 +63,7 @@ mod private let filtered_features : BTreeSet< _ > = package .features() .keys() - .filter( | f | !exclude_features.contains( f ) && (include_features.contains(f) || include_features.is_empty()) ) + .filter( | f | !exclude_features.contains( f ) && ( include_features.contains(f) || include_features.is_empty() ) ) .cloned() .collect(); @@ -96,6 +101,7 @@ mod private } /// Calculate estimate for `features_powerset.length` + #[ must_use ] pub fn estimate_with ( n : usize, @@ -104,8 +110,7 @@ mod private with_none_features : bool, enabled_features : &[ String ], total_features : usize - ) - -> usize + ) -> usize { let mut estimate = 0; let mut binom = 1; diff --git a/module/move/willbe/src/entity/files.rs b/module/move/willbe/src/entity/files.rs index 8385e87167..b6cc1ac89a 100644 --- a/module/move/willbe/src/entity/files.rs +++ b/module/move/willbe/src/entity/files.rs @@ -1,6 +1,8 @@ -/// Internal namespace. +/// Define a private namespace for all its items. +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use std:: diff --git a/module/move/willbe/src/entity/files/crate_dir.rs b/module/move/willbe/src/entity/files/crate_dir.rs index 7ea3424e56..eab1ed5a1d 100644 --- a/module/move/willbe/src/entity/files/crate_dir.rs +++ b/module/move/willbe/src/entity/files/crate_dir.rs @@ -1,3 +1,7 @@ +#![ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] + + +#[ allow( clippy::wildcard_imports ) ] use crate::*; use entity:: @@ -34,6 +38,7 @@ impl CrateDir /// Returns inner type which is an absolute path. #[ inline( always ) ] + #[ must_use ] pub fn absolute_path( self ) -> AbsolutePath { self.0 @@ -41,6 +46,7 @@ impl CrateDir /// Returns path to manifest aka cargo file. #[ inline( always ) ] + #[ must_use ] pub fn manifest_file( self ) -> ManifestFile { self.into() @@ -50,7 +56,7 @@ impl CrateDir impl fmt::Display for CrateDir { - fn fmt( &self, f : &mut fmt::Formatter<'_> ) -> fmt::Result + fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result { write!( f, "{}", self.0.display() ) } @@ -58,7 +64,7 @@ impl fmt::Display for CrateDir impl fmt::Debug for CrateDir { - fn fmt( &self, f : &mut fmt::Formatter<'_> ) -> fmt::Result + fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result { write!( f, "crate dir :: {}", self.0.display() ) } diff --git a/module/move/willbe/src/entity/files/either.rs b/module/move/willbe/src/entity/files/either.rs index aa7fdb5863..d6c2dbb2cb 100644 --- a/module/move/willbe/src/entity/files/either.rs +++ b/module/move/willbe/src/entity/files/either.rs @@ -1,3 +1,4 @@ +#[ allow( clippy::wildcard_imports ) ] use crate::*; use core:: { @@ -7,22 +8,20 @@ use core:: DerefMut, }, }; -use std:: -{ - path::Path, -}; +use std::path::Path; // use error:: // { // Result, // }; -/// Wrapper over `data_type::Either< CrateDir, ManifestFile >` with utils methods. +/// Wrapper over `data_type::Either< CrateDir, ManifestFile >` with util methods. #[ derive( Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug ) ] pub struct EitherDirOrFile( data_type::Either< CrateDir, ManifestFile > ); impl EitherDirOrFile { - /// Returns inner type which is an data_type::Either< CrateDir, ManifestFile >. + /// Returns inner type which is an `data_type::Either`< `CrateDir`, `ManifestFile` >. + #[ must_use ] pub fn inner( self ) -> data_type::Either< CrateDir, ManifestFile > { self.0 @@ -75,6 +74,7 @@ impl Deref for EitherDirOrFile { type Target = Path; + #[ allow( clippy::explicit_deref_methods ) ] fn deref( &self ) -> &Self::Target { self.0.deref() @@ -83,6 +83,7 @@ impl Deref for EitherDirOrFile impl DerefMut for EitherDirOrFile { + #[ allow( clippy::explicit_deref_methods ) ] fn deref_mut( &mut self ) -> &mut Self::Target { self.0.deref_mut() diff --git a/module/move/willbe/src/entity/files/manifest_file.rs b/module/move/willbe/src/entity/files/manifest_file.rs index 78af49e41b..cb6286647c 100644 --- a/module/move/willbe/src/entity/files/manifest_file.rs +++ b/module/move/willbe/src/entity/files/manifest_file.rs @@ -1,3 +1,6 @@ +#![ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] + +#[ allow( clippy::wildcard_imports ) ] use crate::*; use entity:: @@ -42,6 +45,7 @@ impl ManifestFile /// Returns inner type whicj is an absolute path. #[ inline( always ) ] + #[ must_use ] pub fn inner( self ) -> AbsolutePath { self.0 @@ -49,6 +53,7 @@ impl ManifestFile /// Returns path to crate dir. #[ inline( always ) ] + #[ must_use ] pub fn crate_dir( self ) -> CrateDir { self.into() @@ -58,7 +63,7 @@ impl ManifestFile impl fmt::Display for ManifestFile { - fn fmt( &self, f : &mut fmt::Formatter<'_> ) -> fmt::Result + fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result { write!( f, "{}", self.0.display() ) } @@ -66,7 +71,7 @@ impl fmt::Display for ManifestFile impl fmt::Debug for ManifestFile { - fn fmt( &self, f : &mut fmt::Formatter<'_> ) -> fmt::Result + fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result { write!( f, "manifest file :: {}", self.0.display() ) } diff --git a/module/move/willbe/src/entity/files/source_file.rs b/module/move/willbe/src/entity/files/source_file.rs index b895d3eec2..6e9d9260fe 100644 --- a/module/move/willbe/src/entity/files/source_file.rs +++ b/module/move/willbe/src/entity/files/source_file.rs @@ -1,3 +1,7 @@ +#![ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] + + +#[ allow( clippy::wildcard_imports ) ] use crate::*; use entity:: @@ -35,6 +39,7 @@ impl SourceFile /// Returns inner type which is an absolute path. #[ inline( always ) ] + #[ must_use ] pub fn inner( self ) -> AbsolutePath { self.0 @@ -44,7 +49,7 @@ impl SourceFile impl fmt::Display for SourceFile { - fn fmt( &self, f : &mut fmt::Formatter<'_> ) -> fmt::Result + fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result { write!( f, "{}", self.0.display() ) } @@ -52,7 +57,7 @@ impl fmt::Display for SourceFile impl fmt::Debug for SourceFile { - fn fmt( &self, f : &mut fmt::Formatter<'_> ) -> fmt::Result + fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result { write!( f, "source file :: {}", self.0.display() ) } @@ -229,15 +234,15 @@ impl CodeItems for SourceFile fn items( &self ) -> impl IterTrait< '_, syn::Item > { // xxx : use closures instead of expect - let content = fs::read_to_string( self.as_ref() ).expect( &format!( "Failed to parse file {self}" ) ); - let parsed : syn::File = syn::parse_file( &content ).expect( &format!( "Failed to parse file {self}" ) ); + let content = fs::read_to_string( self.as_ref() ).unwrap_or_else( | _ | panic!( "Failed to parse file {self}" ) ); + let parsed : syn::File = syn::parse_file( &content ).unwrap_or_else( | _ | panic!( "Failed to parse file {self}" ) ); parsed.items.into_iter() } } impl AsCode for SourceFile { - fn as_code< 'a >( &'a self ) -> std::io::Result< Cow< 'a, str > > + fn as_code( &self ) -> std::io::Result< Cow< '_, str > > { Ok( Cow::Owned( std::fs::read_to_string( self.as_ref() )? ) ) } diff --git a/module/move/willbe/src/entity/git.rs b/module/move/willbe/src/entity/git.rs index eeeddfd5a4..7cb60d449f 100644 --- a/module/move/willbe/src/entity/git.rs +++ b/module/move/willbe/src/entity/git.rs @@ -1,5 +1,7 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use std::fmt; @@ -23,7 +25,7 @@ mod private impl fmt::Display for ExtendedGitReport { - fn fmt( &self, f : &mut fmt::Formatter<'_> ) -> fmt::Result + fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result { let Self { add, commit, push } = &self; @@ -53,6 +55,9 @@ mod private } /// Performs a Git commit operation using the provided options + /// # Errors + /// qqq: doc + #[ allow( clippy::needless_pass_by_value ) ] pub fn perform_git_commit( o : GitOptions ) -> error::untyped::Result< ExtendedGitReport > // qqq : use typed error { diff --git a/module/move/willbe/src/entity/manifest.rs b/module/move/willbe/src/entity/manifest.rs index 4df6ead08d..89c688be5f 100644 --- a/module/move/willbe/src/entity/manifest.rs +++ b/module/move/willbe/src/entity/manifest.rs @@ -1,6 +1,8 @@ -/// Internal namespace. +/// Define a private namespace for all its items. +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use std:: @@ -100,12 +102,16 @@ mod private } /// Returns path to `Cargo.toml`. + #[ must_use ] pub fn manifest_file( &self ) -> &AbsolutePath { &self.manifest_file } /// Path to directory where `Cargo.toml` located. + /// # Panics + /// qqq: doc + #[ must_use ] pub fn crate_dir( &self ) -> CrateDir { self.manifest_file.parent().unwrap().try_into().unwrap() @@ -113,6 +119,8 @@ mod private } /// Store manifest. + /// # Errors + /// qqq: doc pub fn store( &self ) -> io::Result< () > { fs::write( &self.manifest_file, self.data.to_string() )?; @@ -121,6 +129,7 @@ mod private } /// Check that the current manifest is the manifest of the package (can also be a virtual workspace). + #[ must_use ] pub fn package_is( &self ) -> bool { // let data = self.data.as_ref().ok_or_else( || ManifestError::EmptyManifestData )?; @@ -129,7 +138,8 @@ mod private } /// Check that module is local. - /// The package is defined as local if the `publish` field is set to `false' or the registers are specified. + /// The package is defined as local if the `publish` field is set to `false` or the registers are specified. + #[ must_use ] pub fn local_is( &self ) -> bool { // let data = self.data.as_ref().ok_or_else( || ManifestError::EmptyManifestData )?; @@ -137,7 +147,7 @@ mod private if data.get( "package" ).is_some() && data[ "package" ].get( "name" ).is_some() { let remote = data[ "package" ].get( "publish" ).is_none() - || data[ "package" ][ "publish" ].as_bool().or( Some( true ) ).unwrap(); + || data[ "package" ][ "publish" ].as_bool().unwrap_or( true ); return !remote; } @@ -146,6 +156,8 @@ mod private } /// Retrieves the repository URL of a package from its `Cargo.toml` file. + /// # Errors + /// qqq: doc // qqq : use typed error pub fn repo_url( crate_dir : &CrateDir ) -> error::untyped::Result< String > { @@ -168,7 +180,7 @@ mod private else { let report = tool::git::ls_remote_url( crate_dir.clone().absolute_path() )?; - url::repo_url_extract( &report.out.trim() ).ok_or_else( || format_err!( "Fail to extract repository url from git remote.") ) + url::repo_url_extract( report.out.trim() ).ok_or_else( || format_err!( "Fail to extract repository url from git remote.") ) } } else diff --git a/module/move/willbe/src/entity/mod.rs b/module/move/willbe/src/entity/mod.rs index 6f26700128..100b331e89 100644 --- a/module/move/willbe/src/entity/mod.rs +++ b/module/move/willbe/src/entity/mod.rs @@ -1,3 +1,5 @@ +mod private {} + crate::mod_interface! { /// Rust toolchain channel: stable/nightly. diff --git a/module/move/willbe/src/entity/package.rs b/module/move/willbe/src/entity/package.rs index 5e53b6ea19..dc8965e209 100644 --- a/module/move/willbe/src/entity/package.rs +++ b/module/move/willbe/src/entity/package.rs @@ -1,12 +1,9 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; - - use std:: - { - hash::Hash, - }; - + use std::hash::Hash; use crates_tools::CrateArchive; use error:: { @@ -32,8 +29,9 @@ mod private #[ derive( Debug, Clone ) ] pub enum Package< 'a > { + /// `Cargo.toml` file. - Manifest( Manifest ), + Manifest( Box< Manifest > ), // fix clippy /// Cargo package package. WorkspacePackageRef( WorkspacePackageRef< 'a > ), } @@ -62,7 +60,8 @@ mod private NotAPackage, } - impl< 'a > TryFrom< ManifestFile > for Package< 'a > + // fix clippy + impl TryFrom< ManifestFile > for Package< '_ > { type Error = PackageError; @@ -74,11 +73,11 @@ mod private return Err( PackageError::NotAPackage ); } - Ok( Self::Manifest( package ) ) + Ok( Self::Manifest( Box::new( package ) ) ) // fix clippy } } - impl< 'a > TryFrom< CrateDir > for Package< 'a > + impl TryFrom< CrateDir > for Package< '_ > // fix clippy { type Error = PackageError; @@ -90,11 +89,11 @@ mod private return Err( PackageError::NotAPackage ); } - Ok( Self::Manifest( package ) ) + Ok( Self::Manifest( Box::new( package ) ) ) // fix clippy } } - impl< 'a > TryFrom< Manifest > for Package< 'a > + impl TryFrom< Manifest > for Package< '_ > // fix clippy { type Error = PackageError; @@ -105,7 +104,7 @@ mod private return Err( PackageError::NotAPackage ); } - Ok( Self::Manifest( value ) ) + Ok( Self::Manifest( Box::new( value ) ) ) // fix clippy } } @@ -117,10 +116,13 @@ mod private } } - impl< 'a > Package< 'a > + impl Package< '_ > // fix clippy { /// Path to `Cargo.toml` + /// # Panics + /// qqq: doc + #[ must_use ] pub fn manifest_file( &self ) -> ManifestFile { match self @@ -131,6 +133,9 @@ mod private } /// Path to folder with `Cargo.toml` + /// # Panics + /// qqq: doc + #[ must_use ] pub fn crate_dir( &self ) -> CrateDir { match self @@ -141,6 +146,10 @@ mod private } /// Package version + /// # Errors + /// qqq: doc + /// # Panics + /// qqq: doc pub fn version( &self ) -> Result< String, PackageError > { match self @@ -161,6 +170,7 @@ mod private } /// Check that module is local. + #[ must_use ] pub fn local_is( &self ) -> bool { match self @@ -179,11 +189,13 @@ mod private } /// Returns the `Manifest` + /// # Errors + /// qqq: doc pub fn manifest( &self ) -> Result< Manifest, PackageError > { match self { - Package::Manifest( package ) => Ok( package.clone() ), + Package::Manifest( package ) => Ok( *package.clone() ), // fix clippy Package::WorkspacePackageRef( package ) => Manifest::try_from ( package.manifest_file().map_err( | _ | PackageError::LocalPath )? // qqq : use trait @@ -205,14 +217,15 @@ mod private /// - `false` if there is no need to publish the package. /// /// Panics if the package is not loaded or local package is not packed. - - pub fn publish_need< 'a >( package : &Package< 'a >, path : Option< path::PathBuf > ) -> Result< bool, PackageError > + /// # Errors + /// qqq: doc + pub fn publish_need( package : &Package< '_ >, path : Option< path::PathBuf >, exclude_dev_dependencies : bool ) -> Result< bool, PackageError > { let name = package.name()?; let version = package.version()?; let local_package_path = path - .map( | p | p.join( format!( "package/{0}-{1}.crate", name, version ) ) ) - .unwrap_or( packed_crate::local_path( &name, &version, package.crate_dir() ).map_err( | _ | PackageError::LocalPath )? ); + .map( | p | p.join( format!( "package/{name}-{version}.crate" ) ) ) + .unwrap_or( packed_crate::local_path( name, &version, package.crate_dir() ).map_err( | _ | PackageError::LocalPath )? ); let local_package = CrateArchive::read( local_package_path ).map_err( | _ | PackageError::ReadArchive )?; let remote_package = match CrateArchive::download_crates_io( name, version ) @@ -223,7 +236,7 @@ mod private _ => return Err( PackageError::LoadRemotePackage ), }; - Ok( diff::crate_diff( &local_package, &remote_package ).exclude( diff::PUBLISH_IGNORE_LIST ).has_changes() ) + Ok( diff::crate_diff( &local_package, &remote_package, exclude_dev_dependencies ).exclude( diff::PUBLISH_IGNORE_LIST ).has_changes() ) } } diff --git a/module/move/willbe/src/entity/package_md_extension.rs b/module/move/willbe/src/entity/package_md_extension.rs index 76ffdbac88..c7c2ef6b00 100644 --- a/module/move/willbe/src/entity/package_md_extension.rs +++ b/module/move/willbe/src/entity/package_md_extension.rs @@ -1,27 +1,42 @@ -/// Internal namespace. +/// Define a private namespace for all its items. +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; /// Md's extension for workspace pub trait PackageMdExtension { /// Package name + /// # Errors + /// qqq: doc fn name( &self ) -> Result< &str, package::PackageError >; /// Stability + /// # Errors + /// qqq: doc fn stability( &self ) -> Result< action::readme_health_table_renew::Stability, package::PackageError >; /// Repository + /// # Errors + /// qqq: doc fn repository( &self ) -> Result< Option< String >, package::PackageError >; /// Discord url + /// # Errors + /// qqq: doc fn discord_url( &self ) -> Result< Option< String >, package::PackageError >; } - - impl < 'a > package::Package< 'a > + // fix clippy + impl package::Package< '_ > { /// Package name + /// # Errors + /// qqq: doc + /// + /// # Panics + /// qqq: doc pub fn name( &self ) -> Result< &str, package::PackageError > { match self @@ -43,6 +58,9 @@ mod private } /// Stability + /// + /// # Errors + /// qqq: doc pub fn stability( &self ) -> Result< action::readme_health_table_renew::Stability, package::PackageError > { // aaa : for Petro : bad : first of all it should be in trait. also there is duplicated code @@ -78,6 +96,9 @@ mod private } /// Repository + /// + /// # Errors + /// qqq: doc pub fn repository( &self ) -> Result< Option< String >, package::PackageError > { match self @@ -93,7 +114,7 @@ mod private data[ "package" ] .get( "repository" ) .and_then( | r | r.as_str() ) - .map( | r | r.to_string()) + .map( std::string::ToString::to_string ) ) } Self::WorkspacePackageRef( package ) => @@ -104,6 +125,9 @@ mod private } /// Discord url + /// + /// # Errors + /// qqq: doc pub fn discord_url( &self ) -> Result< Option< String >, package::PackageError > { match self @@ -116,12 +140,12 @@ mod private self.package_metadata() .and_then( | m | m.get( "discord_url" ) ) .and_then( | url | url.as_str() ) - .map( | r | r.to_string() ) + .map( std::string::ToString::to_string ) ) } Self::WorkspacePackageRef( package ) => { - Ok( package.metadata()[ "discord_url" ].as_str().map( | url | url.to_string() ) ) + Ok( package.metadata()[ "discord_url" ].as_str().map( std::string::ToString::to_string ) ) } } } @@ -136,7 +160,7 @@ mod private data[ "package" ] .get( "metadata" ) } - package::Package::WorkspacePackageRef(_) => + package::Package::WorkspacePackageRef( _ ) => { None } diff --git a/module/move/willbe/src/entity/packages.rs b/module/move/willbe/src/entity/packages.rs index 6dd4006db3..7ca63a0eb0 100644 --- a/module/move/willbe/src/entity/packages.rs +++ b/module/move/willbe/src/entity/packages.rs @@ -1,10 +1,9 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; - use std:: - { - fmt::Formatter, - }; + use std::fmt::Formatter; use package::PackageName; use collection::{ HashMap, HashSet }; @@ -16,6 +15,7 @@ mod private /// A configuration struct for specifying optional filters when using the /// `filter` function. It allows users to provide custom filtering /// functions for packages and dependencies. + #[ allow( clippy::type_complexity ) ] #[ derive( Default ) ] pub struct FilterMapOptions { @@ -44,6 +44,7 @@ mod private } } + /// Provides a means to filter both packages and dependencies of an existing package metadata set. /// /// # Arguments @@ -71,10 +72,7 @@ mod private /// * `dependency_filter`: When specified, it's used with each package and its dependencies to decide /// which dependencies should be included in the return for that package. If not provided, all /// dependencies for a package are included. - - // aaa : for Bohdan : for Petro : bad. don't use PackageMetadata directly, use its abstraction only! - - pub fn filter< 'a > + pub fn filter< 'a > // aaa : for Bohdan : for Petro : bad. don't use PackageMetadata directly, use its abstraction only! ( // packages : &[ WorkspacePackageRef< '_ > ], packages : impl Iterator< Item = WorkspacePackageRef< 'a > >, diff --git a/module/move/willbe/src/entity/packed_crate.rs b/module/move/willbe/src/entity/packed_crate.rs index 77da22b98e..1cb55a3ac1 100644 --- a/module/move/willbe/src/entity/packed_crate.rs +++ b/module/move/willbe/src/entity/packed_crate.rs @@ -1,7 +1,8 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; - use std:: { io::Read, @@ -21,10 +22,13 @@ mod private /// /// # Returns : /// The local packed `.crate` file of the package + /// + /// # Errors + /// qqq: doc // qqq : typed error pub fn local_path< 'a >( name : &'a str, version : &'a str, crate_dir : CrateDir ) -> error::untyped::Result< PathBuf > { - let buf = format!( "package/{0}-{1}.crate", name, version ); + let buf = format!( "package/{name}-{version}.crate" ); let workspace = Workspace::try_from( crate_dir )?; let mut local_package_path = PathBuf::new(); @@ -37,6 +41,11 @@ mod private /// /// Get data of remote package from crates.io. /// + /// # Errors + /// qqq: doc + /// + /// # Panics + /// qqq: doc // qqq : typed error pub fn download< 'a >( name : &'a str, version : &'a str ) -> error::untyped::Result< Vec< u8 > > { @@ -45,7 +54,7 @@ mod private .timeout_write( Duration::from_secs( 5 ) ) .build(); let mut buf = String::new(); - write!( &mut buf, "https://static.crates.io/crates/{0}/{0}-{1}.crate", name, version )?; + write!( &mut buf, "https://static.crates.io/crates/{name}/{name}-{version}.crate" )?; let resp = agent.get( &buf[ .. ] ).call().context( "Get data of remote package" )?; diff --git a/module/move/willbe/src/entity/progress_bar.rs b/module/move/willbe/src/entity/progress_bar.rs index c9fef4cf07..51ad62b22c 100644 --- a/module/move/willbe/src/entity/progress_bar.rs +++ b/module/move/willbe/src/entity/progress_bar.rs @@ -1,3 +1,4 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { /// The `ProgressBar` structure is used to display progress indicators in the terminal. @@ -18,12 +19,12 @@ mod private } #[ cfg( feature = "progress_bar" ) ] - impl < 'a > std::fmt::Debug for ProgressBar< 'a > + impl std::fmt::Debug for ProgressBar< '_ > // fix clippy { fn fmt( &self, f : &mut std::fmt::Formatter< '_ > ) -> std::fmt::Result { f.debug_struct( "ProgressBar" ) - .finish() + .finish() } } @@ -52,7 +53,8 @@ mod private /// # Returns /// /// A `ProgressBar` instance that can be used to update and display progress. - pub fn progress_bar< 'a >( &'a self, variants_len : u64 ) -> ProgressBar< 'a > + #[ must_use ] + pub fn progress_bar( &self, variants_len : u64 ) -> ProgressBar< '_ > { let progress_bar = { diff --git a/module/move/willbe/src/entity/publish.rs b/module/move/willbe/src/entity/publish.rs index ed1e336129..f5e03c4fe7 100644 --- a/module/move/willbe/src/entity/publish.rs +++ b/module/move/willbe/src/entity/publish.rs @@ -1,5 +1,7 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use std::fmt; @@ -26,7 +28,7 @@ mod private /// Options for bumping the package version. pub bump : version::BumpOptions, /// Git options related to the package. - pub git_options : entity::git::GitOptions, + pub git_options : Option< entity::git::GitOptions >, /// Options for publishing the package using Cargo. pub publish : cargo::PublishOptions, /// Indicates whether the process should be dry-run (no actual publishing). @@ -43,10 +45,12 @@ mod private channel : channel::Channel, base_temp_dir : Option< path::PathBuf >, #[ former( default = true ) ] + commit_changes : bool, + #[ former( default = true ) ] dry : bool, } - impl< 'a > PublishSinglePackagePlanner< 'a > + impl PublishSinglePackagePlanner< '_ > // fix clippy { fn build( self ) -> PackagePublishInstruction { @@ -73,13 +77,16 @@ mod private dependencies : dependencies.clone(), dry : self.dry, }; - let git_options = entity::git::GitOptions + let git_options = if self.commit_changes { - git_root : workspace_root, - items : dependencies.iter().chain([ &crate_dir ]).map( | d | d.clone().absolute_path().join( "Cargo.toml" ) ).collect(), - message : format!( "{}-v{}", self.package.name().unwrap(), new_version ), - dry : self.dry, - }; + Some( entity::git::GitOptions + { + git_root : workspace_root, + items : dependencies.iter().chain( [ &crate_dir ] ).map( | d | d.clone().absolute_path().join( "Cargo.toml" ) ).collect(), + message : format!( "{}-v{}", self.package.name().unwrap(), new_version ), + dry : self.dry, + }) + } else { None }; let publish = cargo::PublishOptions { path : crate_dir.clone().absolute_path().inner(), @@ -121,6 +128,14 @@ mod private /// Release channels for rust. pub channel : channel::Channel, + /// Setting this option to true will temporarily remove development dependencies before executing the command, then restore them afterward. + #[ allow( dead_code ) ] // former related + pub exclude_dev_dependencies : bool, + + /// Indicates whether changes should be committed. + #[ former( default = true ) ] + pub commit_changes : bool, + /// `dry` - A boolean value indicating whether to do a dry run. If set to `true`, the application performs /// a simulated run without making any actual changes. If set to `false`, the operations are actually executed. /// This property is optional and defaults to `true`. @@ -161,21 +176,22 @@ mod private .collect(); for wanted in &self.roots { - let list = action::list + let list = action::list_all ( action::list::ListOptions::former() .path_to_manifest( wanted.clone() ) .format( action::list::ListFormat::Tree ) - .dependency_sources([ action::list::DependencySource::Local ]) - .dependency_categories([ action::list::DependencyCategory::Primary ]) + .dependency_sources( [ action::list::DependencySource::Local ] ) + .dependency_categories( [ action::list::DependencyCategory::Primary ] ) .form() ) - .map_err( |( _, _e )| fmt::Error )?; + .map_err( | ( _, _e ) | fmt::Error )?; let action::list::ListReport::Tree( list ) = list else { unreachable!() }; + #[ allow( clippy::items_after_statements ) ] fn callback( name_bump_report : &collection::HashMap< &String, ( String, String ) >, mut r : tool::ListNodeReport ) -> tool::ListNodeReport { - if let Some(( old, new )) = name_bump_report.get( &r.name ) + if let Some( ( old, new ) ) = name_bump_report.get( &r.name ) { r.version = Some( format!( "({old} -> {new})" ) ); } @@ -188,10 +204,10 @@ mod private let printer = list; let rep : Vec< tool::ListNodeReport > = printer.iter().map( | printer | printer.info.clone() ).collect(); let list: Vec< tool::ListNodeReport > = rep.into_iter().map( | r | callback( &name_bump_report, r ) ).collect(); - let printer : Vec< tool::TreePrinter > = list.iter().map( | rep | tool::TreePrinter::new( rep ) ).collect(); + let printer : Vec< tool::TreePrinter > = list.iter().map( tool::TreePrinter::new ).collect(); let list = action::list::ListReport::Tree( printer ); - writeln!( f, "{}", list )?; + writeln!( f, "{list}" )?; } Ok( () ) @@ -246,6 +262,10 @@ mod private { plan = plan.dry( dry ); } + if let Some( commit_changes ) = &self.storage.commit_changes + { + plan = plan.commit_changes( *commit_changes ); + } let plan = plan .channel( channel ) .package( package ) @@ -311,11 +331,11 @@ mod private return Ok( () ) } let info = get_info.as_ref().unwrap(); - write!( f, "{}", info )?; + write!( f, "{info}" )?; if let Some( bump ) = bump { - writeln!( f, "{}", bump )?; + writeln!( f, "{bump}" )?; } if let Some( add ) = add { @@ -347,7 +367,10 @@ mod private /// # Returns /// /// * `Result` - The result of the publishing operation, including information about the publish, version bump, and git operations. - + /// + /// # Errors + /// qqq: doc + #[ allow( clippy::option_map_unit_fn ) ] pub fn perform_package_publish( instruction : PackagePublishInstruction ) -> ResultWithReport< PublishReport, Error > { let mut report = PublishReport::default(); @@ -362,45 +385,58 @@ mod private } = instruction; pack.dry = dry; bump.dry = dry; - git_options.dry = dry; + git_options.as_mut().map( | d | d.dry = dry ); publish.dry = dry; report.get_info = Some( cargo::pack( pack ).err_with_report( &report )? ); // aaa : redundant field? // aaa : removed let bump_report = version::bump( bump ).err_with_report( &report )?; report.bump = Some( bump_report.clone() ); - let git_root = git_options.git_root.clone(); - let git = match entity::git::perform_git_commit( git_options ) + + let git_root = git_options.as_ref().map( | g | g.git_root.clone() ); + if let Some( git_options ) = git_options { - Ok( git ) => git, - Err( e ) => + let git = match entity::git::perform_git_commit( git_options ) { - version::revert( &bump_report ) - .map_err( | le | format_err!( "Base error:\n{}\nRevert error:\n{}", e.to_string().replace( '\n', "\n\t" ), le.to_string().replace( '\n', "\n\t" ) ) ) - .err_with_report( &report )?; - return Err(( report, e )); - } - }; - report.add = git.add; - report.commit = git.commit; + Ok( git ) => git, + Err( e ) => + { + version::revert( &bump_report ) + .map_err( | le | format_err! + ( + "Base error:\n{}\nRevert error:\n{}", e.to_string().replace( '\n', "\n\t" ), le.to_string().replace( '\n', "\n\t" ) + )) + .err_with_report( &report )?; + return Err( ( report, e ) ); + } + }; + report.add = git.add; + report.commit = git.commit; + } report.publish = match cargo::publish( publish ) { Ok( publish ) => Some( publish ), Err( e ) => { - tool::git::reset( git_root.as_ref(), true, 1, false ) - .map_err - ( - | le | - format_err!( "Base error:\n{}\nRevert error:\n{}", e.to_string().replace( '\n', "\n\t" ), le.to_string().replace( '\n', "\n\t" ) ) - ) - .err_with_report( &report )?; - return Err(( report, e )); + if let Some( git_root ) = git_root.as_ref() + { + tool::git::reset( git_root.as_ref(), true, 1, false ) + .map_err + ( + | le | + format_err!( "Base error:\n{}\nRevert error:\n{}", e.to_string().replace( '\n', "\n\t" ), le.to_string().replace( '\n', "\n\t" ) ) + ) + .err_with_report( &report )?; + } + return Err( ( report, e ) ); } }; - let res = tool::git::push( &git_root, dry ).err_with_report( &report )?; - report.push = Some( res ); + if let Some( git_root ) = git_root.as_ref() + { + let res = tool::git::push( git_root, dry ).err_with_report( &report )?; + report.push = Some( res ); + } Ok( report ) } @@ -414,13 +450,20 @@ mod private /// # Returns /// /// Returns a `Result` containing a vector of `PublishReport` if successful, else an error. + /// + /// # Errors + /// qqq: doc pub fn perform_packages_publish( plan : PublishPlan ) -> error::untyped::Result< Vec< PublishReport > > // qqq : use typed error { let mut report = vec![]; for package in plan.plans { - let res = perform_package_publish( package ).map_err( |( current_rep, e )| format_err!( "{}\n{current_rep}\n{e}", report.iter().map( | r | format!( "{r}" ) ).join( "\n" ) ) )?; + let res = perform_package_publish( package ).map_err + ( + | ( current_rep, e ) | + format_err!( "{}\n{current_rep}\n{e}", report.iter().map( | r | format!( "{r}" ) ).join( "\n" ) ) + )?; report.push( res ); } diff --git a/module/move/willbe/src/entity/table.rs b/module/move/willbe/src/entity/table.rs index 38e789686c..a49acf6350 100644 --- a/module/move/willbe/src/entity/table.rs +++ b/module/move/willbe/src/entity/table.rs @@ -1,3 +1,4 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { use std::fmt::{Display, Formatter}; @@ -13,13 +14,14 @@ mod private { fn fmt( &self, f : &mut Formatter< '_ > ) -> std::fmt::Result { - writeln!( f, "{}", self.inner.to_string() ) + writeln!( f, "{}", self.inner ) } } impl Table { /// Create an empty table. + #[ must_use ] pub fn new() -> Self { Self @@ -57,7 +59,7 @@ mod private fn default_format() -> prettytable::format::TableFormat { - let format = prettytable::format::FormatBuilder::new() + prettytable::format::FormatBuilder::new() .column_separator( ' ' ) .borders( ' ' ) .separators @@ -66,8 +68,7 @@ mod private prettytable::format::LineSeparator::new( '-', '+', '+', '+' ) ) .padding( 1, 1 ) - .build(); - format + .build() } /// Represent a table row made of cells. @@ -89,9 +90,11 @@ mod private } } + #[ allow( clippy::new_without_default ) ] impl Row { /// Create an row of length size, with empty strings stored. + #[ must_use ] pub fn new() -> Self { Self diff --git a/module/move/willbe/src/entity/test.rs b/module/move/willbe/src/entity/test.rs index 938c5ca415..0877c250dd 100644 --- a/module/move/willbe/src/entity/test.rs +++ b/module/move/willbe/src/entity/test.rs @@ -1,7 +1,10 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; + #[ allow( clippy::wildcard_imports ) ] use table::*; // qqq : for Bohdan no asterisk imports, but in special cases use std:: @@ -10,6 +13,7 @@ mod private sync, }; use colored::Colorize as _; + #[ allow( clippy::wildcard_imports ) ] use process_tools::process::*; use error:: { @@ -36,12 +40,12 @@ mod private /// Represents the optimization setting for the test variant. optimization : optimization::Optimization, /// Contains additional features or characteristics of the test variant. - features : collection::BTreeSet, + features : collection::BTreeSet< String >, } impl fmt::Display for TestVariant { - fn fmt( &self, f : &mut fmt::Formatter< '_ >) -> fmt::Result + fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result { let features = if self.features.is_empty() { " ".to_string() } else { self.features.iter().join( " " ) }; writeln!( f, "{} {} {}", self.optimization, self.channel, features )?; @@ -58,7 +62,7 @@ mod private impl fmt::Display for TestPlan { - fn fmt( &self, f : &mut fmt::Formatter< '_ >) -> std::fmt::Result + fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> std::fmt::Result { writeln!( f, "Plan: " )?; for plan in &self.packages_plan @@ -82,6 +86,10 @@ mod private /// `with_all_features` - If it's true - add to powerset one subset which contains all features. /// `with_none_features` - If it's true - add to powerset one empty subset. /// `variants_cap` - Maximum of subset in powerset + /// + /// # Errors + /// qqq: doc + #[ allow( clippy::needless_pass_by_value, clippy::too_many_arguments ) ] pub fn try_from< 'a > ( packages : impl core::iter::Iterator< Item = WorkspacePackageRef< 'a > >, @@ -135,7 +143,7 @@ mod private impl fmt::Display for TestPackagePlan { - fn fmt( &self, f : &mut fmt::Formatter< '_ >) -> std::fmt::Result + fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> std::fmt::Result { writeln!( f, "Package : {}\nThe tests will be executed using the following configurations :", self.crate_dir.clone().absolute_path() )?; let mut all_features = collection::BTreeSet::new(); @@ -148,7 +156,7 @@ mod private } all_features.extend( features ); } - let mut ff = Vec::from_iter( self.enabled_features.iter().cloned() ); + let mut ff: Vec< _ > = self.enabled_features.iter().cloned().collect(); for feature in all_features { if !ff.contains( &feature ) @@ -178,13 +186,13 @@ mod private row.add_cell( &variant.optimization.to_string() ); let counter = 0; let flag = true; - generate_features_cells(&mut ff, variant, &mut row, counter, flag, &self.enabled_features ); + generate_features_cells( &mut ff, variant, &mut row, counter, flag, &self.enabled_features ); table.add_row( row ); } // aaa : for Petro : bad, DRY // aaa : replace with method - writeln!( f, "{}", table )?; + writeln!( f, "{table}" )?; Ok( () ) } } @@ -202,9 +210,10 @@ mod private /// `with_all_features` - If it's true - add to powerset one subset which contains all features. /// `with_none_features` - If it's true - add to powerset one empty subset. /// `variants_cap` - Maximum of subset in powerset - fn try_from< 'a > + #[ allow( clippy::too_many_arguments ) ] + fn try_from ( - package : WorkspacePackageRef< 'a >, + package : WorkspacePackageRef< '_ >, channels : &collection::HashSet< channel::Channel >, power : u32, include_features : &[ String ], @@ -241,8 +250,8 @@ mod private ( TestVariant { - channel : channel.clone(), - optimization : optimization.clone(), + channel : *channel, + optimization : *optimization, features : feature.clone(), } ); @@ -314,10 +323,11 @@ mod private /// Represents the options for the test. #[ derive( Debug, former::Former, Clone ) ] + #[ allow( clippy::struct_excessive_bools ) ] pub struct SingleTestOptions { /// Specifies the release channels for rust. - /// More details : https://rust-lang.github.io/rustup/concepts/channels.html#:~:text=Rust%20is%20released%20to%20three,releases%20are%20made%20every%20night. + /// More details : . channel : channel::Channel, /// Specifies the optimization for rust. optimization : optimization::Optimization, @@ -335,7 +345,7 @@ mod private temp_directory_path : Option< path::PathBuf >, /// A boolean indicating whether to perform a dry run or not. dry : bool, - /// RUST_BACKTRACE + /// `RUST_BACKTRACE` #[ former( default = true ) ] backtrace : bool, } @@ -355,7 +365,11 @@ mod private .chain( if self.with_all_features { Some( "--all-features".into() ) } else { None } ) // aaa : for Petro : bad, --all-features is always disabled! // aaa : add `debug_assert!( !self.with_all_features )` - .chain( if self.enable_features.is_empty() { None } else { Some([ "--features".into(), self.enable_features.iter().join( "," ) ]) }.into_iter().flatten() ) + .chain( if self.enable_features.is_empty() { None } + else + { + Some( [ "--features".into(), self.enable_features.iter().join( "," ) ] ) + }.into_iter().flatten() ) .chain( self.temp_directory_path.clone().map( | p | vec![ "--target-dir".to_string(), p.to_string_lossy().into() ] ).into_iter().flatten() ) .collect() } @@ -373,7 +387,11 @@ mod private /// /// Returns a `Result` containing a `Report` if the command is executed successfully, /// or an error if the command fails to execute. - pub fn _run< P >( path : P, options : SingleTestOptions ) -> Result< Report, Report > + /// + /// # Errors + /// qqq: doc + #[ allow( clippy::needless_pass_by_value ) ] + pub fn run_rec< P >( path : P, options : SingleTestOptions ) -> Result< Report, Report > // xxx where P : AsRef< path::Path > @@ -396,7 +414,11 @@ mod private } else { - let envs = if options.backtrace { [( "RUST_BACKTRACE".to_string(), "full".to_string() )].into_iter().collect() } else { collection::HashMap::new() }; + let envs = if options.backtrace + { + [ ( "RUST_BACKTRACE".to_string(), "full".to_string() ) ].into_iter().collect() + } + else { collection::HashMap::new() }; Run::former() .bin_path( program ) .args( args.into_iter().map( std::ffi::OsString::from ).collect::< Vec< _ > >() ) @@ -414,7 +436,7 @@ mod private /// Plan for testing pub plan : TestPlan, - /// `concurrent` - A usize value indicating how much test`s can be run at the same time. + /// `concurrent` - A usize value indicating how much test's can be run at the same time. pub concurrent : u32, /// `temp_path` - path to temp directory. @@ -430,6 +452,7 @@ mod private // aaa : for Petro : remove after Former fix // aaa : done + #[ allow( clippy::missing_fields_in_debug ) ] impl fmt::Debug for TestOptions { fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> std::fmt::Result @@ -499,7 +522,7 @@ mod private } all_features.extend( features ); } - let mut ff = Vec::from_iter( self.enabled_features.iter().cloned() ); + let mut ff : Vec< _ > = self.enabled_features.iter().cloned().collect(); for feature in all_features { if !ff.contains( &feature ) @@ -537,8 +560,8 @@ mod private Err( report ) => { failed += 1; - let mut out = report.out.replace( "\n", "\n " ); - out.push_str( "\n" ); + let mut out = report.out.replace( '\n', "\n " ); + out.push( '\n' ); write!( f, " ❌ > {}\n\n{out}", report.command )?; "❌" }, @@ -555,7 +578,7 @@ mod private } // aaa : for Petro : bad, DRY // aaa : replace with method - writeln!( f, "{}", table )?; + writeln!( f, "{table}" )?; writeln!( f, " {}", generate_summary_message( failed, success ) )?; Ok( () ) @@ -617,7 +640,7 @@ mod private writeln!( f, "Successful :" )?; for report in &self.success_reports { - writeln!( f, "{}", report )?; + writeln!( f, "{report}" )?; } } if !self.failure_reports.is_empty() @@ -625,10 +648,11 @@ mod private writeln!( f, "Failure :" )?; for report in &self.failure_reports { - writeln!( f, "{}", report )?; + writeln!( f, "{report}" )?; } } writeln!( f, "Global report" )?; + #[ allow( clippy::cast_possible_wrap, clippy::cast_possible_truncation ) ] writeln!( f, " {}", generate_summary_message( self.failure_reports.len() as i32, self.success_reports.len() as i32 ) )?; Ok( () ) @@ -637,13 +661,17 @@ mod private /// `tests_run` is a function that runs tests on a given package with specified arguments. /// It returns a `TestReport` on success, or a `TestReport` and an `Error` on failure. + /// + /// # Errors + /// qqq: doc + /// + /// # Panics + /// qqq: doc pub fn run( options : &PackageTestOptions< '_ > ) -> ResultWithReport< TestReport, TestError > // -> Result< TestReport, ( TestReport, TestError ) > { - let mut report = TestReport::default(); - report.dry = options.dry; - report.enabled_features = options.plan.enabled_features.clone(); + let report = TestReport { dry: options.dry, enabled_features: options.plan.enabled_features.clone(), ..Default::default() }; let report = sync::Arc::new( sync::Mutex::new( report ) ); let crate_dir = options.plan.crate_dir.clone(); @@ -678,14 +706,14 @@ mod private { let _s = { - let s = options.progress_bar.multi_progress.add( indicatif::ProgressBar::new_spinner().with_message( format!( "{}", variant ) ) ); + let s = options.progress_bar.multi_progress.add( indicatif::ProgressBar::new_spinner().with_message( format!( "{variant}" ) ) ); s.enable_steady_tick( std::time::Duration::from_millis( 100 ) ); s }; } let args = args_t.form(); let temp_dir = args.temp_directory_path.clone(); - let cmd_rep = _run( crate_dir, args ); + let cmd_rep = run_rec( crate_dir, args ); r.lock().unwrap().tests.insert( variant.clone(), cmd_rep ); #[ cfg( feature = "progress_bar" ) ] if options.with_progress @@ -712,6 +740,11 @@ mod private } /// Run tests for given packages. + /// # Errors + /// qqq: doc + /// + /// # Panics + /// qqq: doc pub fn tests_run( args : &TestOptions ) -> ResultWithReport< TestsReport, TestError > // -> Result< TestsReport, ( TestsReport, TestError ) > @@ -720,8 +753,7 @@ mod private let multi_progress = progress_bar::MultiProgress::default(); #[ cfg( feature = "progress_bar" ) ] let mm = &multi_progress; - let mut report = TestsReport::default(); - report.dry = args.dry; + let report = TestsReport { dry: args.dry, ..Default::default() }; let report = sync::Arc::new( sync::Mutex::new( report ) ); let pool = rayon::ThreadPoolBuilder::new().use_current_thread().num_threads( args.concurrent as usize ).build().unwrap(); pool.scope @@ -753,7 +785,7 @@ mod private { report.lock().unwrap().success_reports.push( r ); } - Err(( r, _ )) => + Err( ( r, _ ) ) => { report.lock().unwrap().failure_reports.push( r ); } @@ -770,7 +802,7 @@ mod private } else { - Err(( report, format_err!( "Some tests was failed" ).into() )) + Err( ( report, format_err!( "Some tests was failed" ).into() ) ) } } } @@ -780,7 +812,7 @@ crate::mod_interface! own use SingleTestOptions; own use TestVariant; - own use _run; + own use run_rec; own use TestPlan; own use TestOptions; diff --git a/module/move/willbe/src/entity/version.rs b/module/move/willbe/src/entity/version.rs index 0722b8a59c..bfde248bec 100644 --- a/module/move/willbe/src/entity/version.rs +++ b/module/move/willbe/src/entity/version.rs @@ -1,6 +1,8 @@ -/// Internal namespace. +/// Define a private namespace for all its items. +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use std:: @@ -17,7 +19,7 @@ mod private use package::Package; use { error::untyped::format_err, iter::Itertools }; - /// Wrapper for a SemVer structure + /// Wrapper for a `SemVer` structure #[ derive( Debug, Clone, Eq, PartialEq, Ord, PartialOrd ) ] pub struct Version( SemVersion ); @@ -55,7 +57,7 @@ mod private { fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result { - write!( f, "{}", self.0.to_string() ) + write!( f, "{}", self.0 ) } } @@ -64,6 +66,7 @@ mod private /// Bump a version with default strategy /// /// This function increases first not 0 number + #[ must_use ] pub fn bump( self ) -> Self { let mut ver = self.0; @@ -187,6 +190,9 @@ mod private /// /// Returns a result containing the extended bump report if successful. /// + /// + /// # Errors + /// qqq: doc // qqq : should be typed error, apply err_with // qqq : don't use 1-prameter Result pub fn bump( o : BumpOptions ) -> Result< ExtendedBumpReport > @@ -201,7 +207,11 @@ mod private let current_version = version::Version::try_from( package_version.as_str() ).map_err( | e | format_err!( "{report:?}\n{e:#?}" ) )?; if current_version > o.new_version { - return Err( format_err!( "{report:?}\nThe current version of the package is higher than need to be set\n\tpackage: {name}\n\tcurrent_version: {current_version}\n\tnew_version: {}", o.new_version ) ); + return Err( format_err! + ( + "{report:?}\nThe current version of the package is higher than need to be set\n\tpackage: {name}\n\tcurrent_version: {current_version}\n\tnew_version: {}", + o.new_version + )); } report.old_version = Some( o.old_version.to_string() ); report.new_version = Some( o.new_version.to_string() ); @@ -211,7 +221,7 @@ mod private { // let data = package_manifest.data.as_mut().unwrap(); let data = &mut package_manifest.data; - data[ "package" ][ "version" ] = value( &o.new_version.to_string() ); + data[ "package" ][ "version" ] = value( o.new_version.to_string() ); package_manifest.store()?; } report.changed_files = vec![ manifest_file ]; @@ -226,9 +236,9 @@ mod private let item = if let Some( item ) = data.get_mut( "package" ) { item } else if let Some( item ) = data.get_mut( "workspace" ) { item } else { return Err( format_err!( "{report:?}\nThe manifest nor the package and nor the workspace" ) ); }; - if let Some( dependency ) = item.get_mut( "dependencies" ).and_then( | ds | ds.get_mut( &name ) ) + if let Some( dependency ) = item.get_mut( "dependencies" ).and_then( | ds | ds.get_mut( name ) ) { - if let Some( previous_version ) = dependency.get( "version" ).and_then( | v | v.as_str() ).map( | v | v.to_string() ) + if let Some( previous_version ) = dependency.get( "version" ).and_then( | v | v.as_str() ).map( std::string::ToString::to_string ) { if previous_version.starts_with('~') { @@ -256,6 +266,12 @@ mod private /// # Returns /// /// Returns `Ok(())` if the version is reverted successfully. Returns `Err` with an error message if there is any issue with reverting the version. + /// + /// # Errors + /// qqq: doc + /// + /// # Panics + /// qqq: doc // qqq : don't use 1-prameter Result pub fn revert( report : &ExtendedBumpReport ) -> error::untyped::Result< () > // qqq : use typed error { @@ -267,17 +283,31 @@ mod private { if let Some( dependency ) = item_maybe_with_dependencies.get_mut( "dependencies" ).and_then( | ds | ds.get_mut( name ) ) { - if let Some( current_version ) = dependency.get( "version" ).and_then( | v | v.as_str() ).map( | v | v.to_string() ) + if let Some( current_version ) = dependency.get( "version" ).and_then( | v | v.as_str() ).map( std::string::ToString::to_string ) { let version = &mut dependency[ "version" ]; if let Some( current_version ) = current_version.strip_prefix( '~' ) { - if current_version != new_version { return Err( format_err!( "The current version of the package does not match the expected one. Expected: `{new_version}` Current: `{}`", version.as_str().unwrap_or_default() ) ); } - *version = value( format!( "~{}", old_version ) ); + if current_version != new_version + { + return Err( format_err! + ( + "The current version of the package does not match the expected one. Expected: `{new_version}` Current: `{}`", + version.as_str().unwrap_or_default() + )); + } + *version = value( format!( "~{old_version}" ) ); } else { - if version.as_str().unwrap() != new_version { return Err( format_err!( "The current version of the package does not match the expected one. Expected: `{new_version}` Current: `{}`", version.as_str().unwrap_or_default() ) ); } + if version.as_str().unwrap() != new_version + { + return Err( format_err! + ( + "The current version of the package does not match the expected one. Expected: `{new_version}` Current: `{}`", + version.as_str().unwrap_or_default() + )); + } *version = value( old_version.clone() ); } } @@ -299,7 +329,14 @@ mod private if package.get_mut( "name" ).unwrap().as_str().unwrap() == name { let version = &mut package[ "version" ]; - if version.as_str().unwrap() != new_version { return Err( format_err!( "The current version of the package does not match the expected one. Expected: `{new_version}` Current: `{}`", version.as_str().unwrap_or_default() ) ); } + if version.as_str().unwrap() != new_version + { + return Err( format_err! + ( + "The current version of the package does not match the expected one. Expected: `{new_version}` Current: `{}`", + version.as_str().unwrap_or_default() + )); + } *version = value( old_version.clone() ); } else @@ -321,12 +358,18 @@ mod private /// # Args : /// - `manifest` - a manifest mutable reference /// - `dry` - a flag that indicates whether to apply the changes or not - /// - `true` - does not modify the manifest file, but only returns the new version; - /// - `false` - overwrites the manifest file with the new version. + /// - `true` - does not modify the manifest file, but only returns the new version; + /// - `false` - overwrites the manifest file with the new version. /// /// # Returns : /// - `Ok` - the new version number as a string; /// - `Err` - if the manifest file cannot be read, written, parsed. + /// + /// # Errors + /// qqq: doc + /// + /// # Panics + /// qqq: doc pub fn manifest_bump( manifest : &mut Manifest, dry : bool ) -> Result< BumpReport, manifest::ManifestError > { let mut report = BumpReport::default(); diff --git a/module/move/willbe/src/entity/workspace.rs b/module/move/willbe/src/entity/workspace.rs index 3fc37828fd..567daca43a 100644 --- a/module/move/willbe/src/entity/workspace.rs +++ b/module/move/willbe/src/entity/workspace.rs @@ -1,12 +1,12 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; - // qqq : for Bohdan : bad // use std::*; - use std::slice; - use former::{ Former }; + use former::Former; /// Stores information about the current workspace. #[ derive( Debug, Clone ) ] @@ -97,6 +97,10 @@ mod private } /// Returns the path to workspace root + /// + /// # Panics + /// qqq: doc + #[ must_use ] pub fn workspace_root( &self ) -> CrateDir { // Safe because workspace_root.as_std_path() is always a path to a directory @@ -104,13 +108,17 @@ mod private } /// Returns the path to target directory + #[ must_use ] pub fn target_directory( &self ) -> &std::path::Path { self.metadata.target_directory.as_std_path() } /// Find a package by its manifest file path - pub fn package_find_by_manifest< 'a, P >( &'a self, manifest_file : P ) -> Option< WorkspacePackageRef< 'a > > + /// + /// # Panics + /// qqq: doc + pub fn package_find_by_manifest< P >( &self, manifest_file : P ) -> Option< WorkspacePackageRef< '_ > > where P : AsRef< std::path::Path >, { @@ -120,7 +128,8 @@ mod private } /// Filter of packages. - pub fn packages_which< 'a >( &'a self ) -> PackagesFilterFormer< 'a > + #[ must_use ] + pub fn packages_which( &self ) -> PackagesFilterFormer< '_ > { // PackagesFilter::new( self ) PackagesFilter::former().workspace( self ) @@ -208,12 +217,13 @@ mod private Self { workspace, - crate_dir : Default::default(), - manifest_file : Default::default(), + crate_dir : Box::default(), + manifest_file : Box::default(), } } #[ inline( always ) ] + #[ allow( clippy::unused_self ) ] pub fn iter( &'a self ) -> impl Iterator< Item = WorkspacePackageRef< 'a > > + Clone { @@ -245,11 +255,10 @@ mod private .packages() .find( | &p | { - if !formed.crate_dir.include( p ) { return false }; - if !formed.manifest_file.include( p ) { return false }; - return true; + if !formed.crate_dir.include( p ) { return false } + if !formed.manifest_file.include( p ) { return false } + true }) - .clone() // .unwrap() // let filter_crate_dir = if Some( crate_dir ) = self.crate_dir diff --git a/module/move/willbe/src/entity/workspace_graph.rs b/module/move/willbe/src/entity/workspace_graph.rs index 9d129fdf07..11b592520f 100644 --- a/module/move/willbe/src/entity/workspace_graph.rs +++ b/module/move/willbe/src/entity/workspace_graph.rs @@ -1,8 +1,11 @@ mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; /// Returns a graph of packages. + #[ allow( clippy::type_complexity ) ] + #[ must_use ] pub fn graph( workspace : &Workspace ) -> petgraph::Graph< String, String > { let packages = workspace.packages(); diff --git a/module/move/willbe/src/entity/workspace_md_extension.rs b/module/move/willbe/src/entity/workspace_md_extension.rs index f463d4cf60..afbc2442a9 100644 --- a/module/move/willbe/src/entity/workspace_md_extension.rs +++ b/module/move/willbe/src/entity/workspace_md_extension.rs @@ -1,6 +1,8 @@ -/// Internal namespace. +/// Define a private namespace for all its items. +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; /// Md's extension for workspace @@ -15,7 +17,7 @@ mod private /// Return the repository url fn repository_url( &self ) -> Option< String >; - /// Return the workspace_name + /// Return the `workspace_name` fn workspace_name( &self ) -> Option< String >; } @@ -27,7 +29,7 @@ mod private .metadata .workspace_metadata[ "discord_url" ] .as_str() - .map( | url | url.to_string() ) + .map( std::string::ToString::to_string ) } fn master_branch( &self ) -> Option< String > @@ -37,7 +39,7 @@ mod private .workspace_metadata .get( "master_branch" ) .and_then( | b | b.as_str() ) - .map( | b | b.to_string() ) + .map( std::string::ToString::to_string ) } fn repository_url( &self ) -> Option< String > @@ -47,7 +49,7 @@ mod private .workspace_metadata .get( "repo_url" ) .and_then( | b | b.as_str() ) - .map( | b | b.to_string() ) + .map( std::string::ToString::to_string ) } fn workspace_name( &self ) -> Option< String > @@ -57,7 +59,7 @@ mod private .workspace_metadata .get( "workspace_name" ) .and_then( | b | b.as_str() ) - .map( | b | b.to_string() ) + .map( std::string::ToString::to_string ) } } diff --git a/module/move/willbe/src/entity/workspace_package.rs b/module/move/willbe/src/entity/workspace_package.rs index 6ecada7108..de72bb8577 100644 --- a/module/move/willbe/src/entity/workspace_package.rs +++ b/module/move/willbe/src/entity/workspace_package.rs @@ -1,5 +1,7 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; use macros::kw; use collection::BTreeMap; @@ -12,7 +14,7 @@ mod private // xxx : qqq : Deref, DerefMut, AsRef, AsMut - /// Facade for cargo_metadata::Package + /// Facade for `cargo_metadata::Package` #[ derive( Debug, Clone, Copy ) ] #[ repr( transparent ) ] pub struct WorkspacePackageRef< 'a > @@ -35,6 +37,7 @@ mod private impl< 'a > WorkspacePackageRef< 'a > { /// The name field as given in the Cargo.toml + #[ must_use ] pub fn name( &'a self ) -> &'a str { &self.inner.name @@ -56,12 +59,21 @@ mod private } /// Path to the manifest Cargo.toml + /// + /// # Errors + /// qqq: doc pub fn manifest_file( &self ) -> Result< ManifestFile, PathError > { self.inner.manifest_path.as_path().try_into() } /// Path to the directory with manifest Cargo.toml. + /// + /// # Errors + /// qqq: doc + /// + /// # Panics + /// qqq: docs pub fn crate_dir( &self ) -> Result< CrateDir, PathError > { // SAFE because `manifest_path containing the Cargo.toml` @@ -69,6 +81,7 @@ mod private } /// The version field as specified in the Cargo.toml + #[ must_use ] pub fn version( &self ) -> semver::Version { self.inner.version.clone() @@ -77,6 +90,7 @@ mod private /// List of registries to which this package may be published (derived from the publish field). /// Publishing is unrestricted if None, and forbidden if the Vec is empty. /// This is always None if running with a version of Cargo older than 1.39. + #[ must_use ] pub fn publish( &self ) -> Option< &Vec< String > > { self.inner.publish.as_ref() @@ -105,39 +119,42 @@ mod private /// assert_eq!( package_metadata.some_value, 42 ); /// } /// ``` + #[ must_use ] pub fn metadata( &self ) -> &Value { &self.inner.metadata } /// The repository URL as specified in the Cargo.toml + #[ must_use ] pub fn repository( &self ) -> Option< &String > { self.inner.repository.as_ref() } /// Features provided by the crate, mapped to the features required by that feature. + #[ must_use ] pub fn features( &self ) -> &BTreeMap< String, Vec< String > > { &self.inner.features } } - impl< 'a > Entries for WorkspacePackageRef< 'a > + impl Entries for WorkspacePackageRef< '_ > // fix clippy { fn entries( &self ) -> impl IterTrait< '_, SourceFile > { self.inner.targets.iter().map( | target | { let src_path = &target.src_path; - let source : SourceFile = src_path.try_into().expect( &format!( "Illformed path to source file {src_path}" ) ); + let source : SourceFile = src_path.try_into().unwrap_or_else( | _ | panic!( "Illformed path to source file {src_path}" ) ); // println!( " -- {:?} {:?}", source, target.kind ); source }) } } - impl< 'a > Sources for WorkspacePackageRef< 'a > + impl Sources for WorkspacePackageRef< '_ > // fix clippy { fn sources( &self ) -> impl IterTrait< '_, SourceFile > { @@ -146,14 +163,14 @@ mod private WalkDir::new( crate_dir ) .into_iter() .filter_map( Result::ok ) - .filter( | e | e.path().extension().map_or( false, | ext | ext == "rs" ) ) + .filter( | e | e.path().extension().is_some_and(| ext | ext == "rs") ) // fix clippy .map( | e | SourceFile::try_from( e.path() ).unwrap() ) .collect::< Vec< _ > >() .into_iter() } } - impl< 'a > CodeItems for WorkspacePackageRef< 'a > + impl CodeItems for WorkspacePackageRef< '_ > // fix clippy { fn items( &self ) -> impl IterTrait< '_, syn::Item > { @@ -164,9 +181,9 @@ mod private } } - impl< 'a > AsCode for WorkspacePackageRef< 'a > + impl AsCode for WorkspacePackageRef< '_ > // fix clippy { - fn as_code< 'b >( &'b self ) -> std::io::Result< Cow< 'b, str > > + fn as_code( &self ) -> std::io::Result< Cow< '_, str > > { let mut results : Vec< String > = Vec::new(); // zzz : introduce formatter @@ -178,9 +195,9 @@ mod private .as_ref() .with_extension( "" ) .file_name() - .expect( &format!( "Cant get file name of path {}", source.as_ref().display() ) ) + .unwrap_or_else( || panic!( "Cant get file name of path {}", source.as_ref().display() ) ) .to_string_lossy() - .replace( ".", "_" ); + .replace( '.', "_" ); if kw::is( &filename ) { @@ -190,7 +207,7 @@ mod private // qqq : xxx : use callbacks instead of expect results.push( format!( "// === Begin of File {}", source.as_ref().display() ) ); - results.push( format!( "mod {}\n{{\n", filename ) ); + results.push( format!( "mod {filename}\n{{\n" ) ); results.push( code ); results.push( "\n}".to_string() ); results.push( format!( "// === End of File {}", source.as_ref().display() ) ); diff --git a/module/move/willbe/src/lib.rs b/module/move/willbe/src/lib.rs index 87149b74fa..e80c1e45fd 100644 --- a/module/move/willbe/src/lib.rs +++ b/module/move/willbe/src/lib.rs @@ -3,11 +3,66 @@ #![ 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; -/// Internal namespace. +/// Define a private namespace for all its items. mod private { + #[ allow( clippy::wildcard_imports ) ] use crate::*; /// Takes the command line arguments and perform associated function(s). @@ -15,6 +70,9 @@ mod private /// It then terminates the program with an exit code of 1 to indicate an error due to the lack of input. /// /// Do not support interactive mode. + /// + /// # Errors + /// qqq: doc pub fn run( args : Vec< String > ) -> Result< (), error::untyped::Error > { #[ cfg( feature = "tracing" ) ] diff --git a/module/move/willbe/src/tool/cargo.rs b/module/move/willbe/src/tool/cargo.rs index 71590ecd45..6d0f687c40 100644 --- a/module/move/willbe/src/tool/cargo.rs +++ b/module/move/willbe/src/tool/cargo.rs @@ -1,13 +1,15 @@ -/// Internal namespace. +/// Define a private namespace for all its items. +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { - #[ allow( unused_imports ) ] + + #[ allow( unused_imports, clippy::wildcard_imports ) ] use crate::tool::*; use std::ffi::OsString; use std::path::PathBuf; - use error::err; - use error::untyped::format_err; + // use error::err; + // use error::untyped::format_err; use former::Former; use process_tools::process; // use process_tools::process::*; @@ -25,6 +27,7 @@ mod private /// The `PackOptions` struct encapsulates various options that can be configured when packaging a project, /// including the path to the project, the distribution channel, and various flags for controlling the behavior of the packaging process. #[ derive( Debug, Former, Clone ) ] + #[ allow( clippy::struct_excessive_bools ) ] pub struct PackOptions { /// The path to the project to be packaged. @@ -47,6 +50,7 @@ mod private // aaa : don't abuse negative form, rename to checking_consistency // renamed and changed logic pub( crate ) checking_consistency : bool, + /// An optional temporary path to be used during packaging. /// /// This field may contain a path to a temporary directory that will be used during the packaging process. @@ -68,6 +72,7 @@ mod private impl PackOptions { + #[ allow( clippy::if_not_else ) ] fn to_pack_args( &self ) -> Vec< String > { [ "run".to_string(), self.channel.to_string(), "cargo".into(), "package".into() ] @@ -79,6 +84,7 @@ mod private } } + /// /// Assemble the local package into a distributable tarball. /// @@ -96,6 +102,7 @@ mod private // qqq : use typed error pub fn pack( args : PackOptions ) -> error::untyped::Result< process::Report > { + let ( program, options ) = ( "rustup", args.to_pack_args() ); if args.dry @@ -107,7 +114,7 @@ mod private command : format!( "{program} {}", options.join( " " ) ), out : String::new(), err : String::new(), - current_path: args.path.to_path_buf(), + current_path: args.path.clone(), error: Ok( () ), } ) @@ -118,7 +125,7 @@ mod private .bin_path( program ) .args( options.into_iter().map( OsString::from ).collect::< Vec< _ > >() ) .current_path( args.path ) - .run().map_err( | report | err!( report.to_string() ) ) + .run().map_err( | report | error::untyped::format_err!( report.to_string() ) ) } } @@ -162,6 +169,7 @@ mod private pub fn publish( args : PublishOptions ) -> error::untyped::Result< process::Report > // qqq : use typed error { + let ( program, arguments) = ( "cargo", args.as_publish_args() ); if args.dry @@ -173,7 +181,7 @@ mod private command : format!( "{program} {}", arguments.join( " " ) ), out : String::new(), err : String::new(), - current_path: args.path.to_path_buf(), + current_path: args.path.clone(), error: Ok( () ), } ) @@ -182,7 +190,7 @@ mod private { let mut results = Vec::with_capacity( args.retry_count + 1 ); let run_args : Vec< _ > = arguments.into_iter().map( OsString::from ).collect(); - for _ in 0 .. args.retry_count + 1 + for _ in 0 ..=args.retry_count { let result = process::Run::former() .bin_path( program ) @@ -197,11 +205,20 @@ mod private } if args.retry_count > 0 { - Err( format_err!( "It took {} attempts, but still failed. Here are the errors:\n{}", args.retry_count + 1, results.into_iter().map( | r | format!( "- {r}" ) ).collect::< Vec< _ > >().join( "\n" ) ) ) + Err( error::untyped::format_err! + ( + "It took {} attempts, but still failed. Here are the errors:\n{}", + args.retry_count + 1, + results + .into_iter() + .map( | r | format!( "- {r}" ) ) + .collect::< Vec< _ > >() + .join( "\n" ) + )) } else { - Err( results.remove( 0 ) ).map_err( | report | err!( report.to_string() ) ) + Err( results.remove( 0 ) ).map_err( | report | error::untyped::format_err!( report.to_string() ) ) } } } diff --git a/module/move/willbe/src/tool/collection.rs b/module/move/willbe/src/tool/collection.rs deleted file mode 100644 index edd7bec8c8..0000000000 --- a/module/move/willbe/src/tool/collection.rs +++ /dev/null @@ -1,12 +0,0 @@ -/// Internal namespace. -mod private -{ -} - -crate::mod_interface! -{ - - use ::collection_tools; - own use ::collection_tools::own::*; - -} diff --git a/module/move/willbe/src/tool/error.rs b/module/move/willbe/src/tool/error.rs deleted file mode 100644 index bc00b92ba9..0000000000 --- a/module/move/willbe/src/tool/error.rs +++ /dev/null @@ -1,21 +0,0 @@ -/// Internal namespace. -#[ allow( unused_imports ) ] -mod private -{ - use crate::tool::*; - use ::error_tools::own::*; - -} - -crate::mod_interface! -{ - // #![ debug ] - - use ::error_tools; - own use ::error_tools::own::*; - - // exposed use ErrWith; - // exposed use ResultWithReport; - // exposed use ::error_tools::Result; - -} diff --git a/module/move/willbe/src/tool/files.rs b/module/move/willbe/src/tool/files.rs index 38878c477d..fcd7413abd 100644 --- a/module/move/willbe/src/tool/files.rs +++ b/module/move/willbe/src/tool/files.rs @@ -1,8 +1,9 @@ -/// Internal namespace. +/// Define a private namespace for all its items. +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { - #[ allow( unused_imports ) ] + #[ allow( unused_imports, clippy::wildcard_imports ) ] use crate::tool::*; use std::path::{ Path, PathBuf }; @@ -10,8 +11,10 @@ mod private /// /// Find paths. /// - + /// # Panics + /// qqq: doc /* xxx : check */ + #[ allow( clippy::useless_conversion ) ] pub fn find< P, S >( base_dir : P, patterns : &[ S ] ) -> Vec< PathBuf > where P : AsRef< Path >, @@ -27,6 +30,7 @@ mod private } /// Check if path is valid. + #[ must_use ] pub fn valid_is( path : &str ) -> bool { std::fs::metadata( path ).is_ok() diff --git a/module/move/willbe/src/tool/git.rs b/module/move/willbe/src/tool/git.rs index 828e4d3c64..b9f2761a58 100644 --- a/module/move/willbe/src/tool/git.rs +++ b/module/move/willbe/src/tool/git.rs @@ -1,13 +1,16 @@ -/// Internal namespace. +/// Define a private namespace for all its items. +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { - #[ allow( unused_imports ) ] + #[ allow( unused_imports, clippy::wildcard_imports ) ] use crate::tool::*; use std::ffi::OsString; use std::path::Path; + + #[ allow( clippy::wildcard_imports ) ] use process_tools::process::*; - use error::err; + // use error::err; // qqq : group dependencies /// Adds changes to the Git staging area. @@ -22,7 +25,11 @@ mod private /// # Returns : /// Returns a result containing a report indicating the result of the operation. // qqq : should be typed error, apply err_with - #[ cfg_attr( feature = "tracing", tracing::instrument( skip( path, objects ), fields( path = %path.as_ref().display() ) ) ) ] + #[ cfg_attr + ( + feature = "tracing", + tracing::instrument( skip( path, objects ), fields( path = %path.as_ref().display() ) ) + )] pub fn add< P, Os, O >( path : P, objects : Os, dry : bool ) -> error::untyped::Result< Report > // qqq : use typed error @@ -31,7 +38,7 @@ mod private Os : AsRef< [ O ] >, O : AsRef< str >, { - let objects = objects.as_ref().iter().map( | x | x.as_ref() ); + let objects = objects.as_ref().iter().map( std::convert::AsRef::as_ref ); // qqq : for Bohdan : don't enlarge length of lines artificially let ( program, args ) : ( _, Vec< _ > ) = ( "git", Some( "add" ).into_iter().chain( objects ).collect() ); @@ -56,7 +63,7 @@ mod private .bin_path( program ) .args( args.into_iter().map( OsString::from ).collect::< Vec< _ > >() ) .current_path( path.as_ref().to_path_buf() ) - .run().map_err( | report | err!( report.to_string() ) ) + .run().map_err( | report | error::untyped::format_err!( report.to_string() ) ) } } @@ -73,7 +80,15 @@ mod private /// # Returns : /// Returns a result containing a report indicating the result of the operation. // qqq : should be typed error, apply err_with - #[ cfg_attr( feature = "tracing", tracing::instrument( skip( path, message ), fields( path = %path.as_ref().display(), message = %message.as_ref() ) ) ) ] + #[ cfg_attr + ( + feature = "tracing", + tracing::instrument + ( + skip( path, message ), + fields( path = %path.as_ref().display(), message = %message.as_ref() ) + ) + )] pub fn commit< P, M >( path : P, message : M, dry : bool ) -> error::untyped::Result< Report > // qqq : don't use 1-prameter Result where @@ -102,7 +117,7 @@ mod private .bin_path( program ) .args( args.into_iter().map( OsString::from ).collect::< Vec< _ > >() ) .current_path( path.as_ref().to_path_buf() ) - .run().map_err( | report | err!( report.to_string() ) ) + .run().map_err( | report | error::untyped::format_err!( report.to_string() ) ) } } @@ -148,7 +163,7 @@ mod private .bin_path( program ) .args( args.into_iter().map( OsString::from ).collect::< Vec< _ > >() ) .current_path( path.as_ref().to_path_buf() ) - .run().map_err( | report | err!( report.to_string() ) ) + .run().map_err( | report | error::untyped::format_err!( report.to_string() ) ) } } @@ -164,16 +179,17 @@ mod private /// # Returns : /// This function returns a `Result` containing a `Report` if the command is executed successfully. The `Report` contains the command executed, the output /// git reset command wrapper - + /// + /// # Errors + /// qqq: doc // qqq : should be typed error, apply err_with - pub fn reset< P >( path : P, hard : bool, commits_count : usize, dry : bool ) -> error::untyped::Result< Report > // qqq : don't use 1-prameter Result where P : AsRef< Path >, { - if commits_count < 1 { return Err( err!( "Cannot reset, the count of commits must be greater than 0" ) ) } + if commits_count < 1 { return Err( error::untyped::format_err!( "Cannot reset, the count of commits must be greater than 0" ) ) } let ( program, args ) : ( _, Vec< _ > ) = ( "git", @@ -181,7 +197,7 @@ mod private .into_iter() .chain( if hard { Some( "--hard" ) } else { None } ) .map( String::from ) - .chain( Some( format!( "HEAD~{}", commits_count ) ) ) + .chain( Some( format!( "HEAD~{commits_count}" ) ) ) .collect() ); @@ -205,7 +221,7 @@ mod private .bin_path( program ) .args( args.into_iter().map( OsString::from ).collect::< Vec< _ > >() ) .current_path( path.as_ref().to_path_buf() ) - .run().map_err( | report | err!( report.to_string() ) ) + .run().map_err( | report | error::untyped::format_err!( report.to_string() ) ) } } @@ -218,10 +234,11 @@ mod private /// # Returns /// /// A `Result` containing a `Report`, which represents the result of the command execution. - + /// + /// # Errors + /// qqq: doc // qqq : should be typed error, apply err_with // qqq : don't use 1-prameter Result - pub fn ls_remote_url< P >( path : P ) -> error::untyped::Result< Report > where P : AsRef< Path >, @@ -232,7 +249,7 @@ mod private .bin_path( program ) .args( args.into_iter().map( OsString::from ).collect::< Vec< _ > >() ) .current_path( path.as_ref().to_path_buf() ) - .run().map_err( | report | err!( report.to_string() ) ) + .run().map_err( | report | error::untyped::format_err!( report.to_string() ) ) } } diff --git a/module/move/willbe/src/tool/graph.rs b/module/move/willbe/src/tool/graph.rs index 296547ac82..92a4f61ff1 100644 --- a/module/move/willbe/src/tool/graph.rs +++ b/module/move/willbe/src/tool/graph.rs @@ -1,7 +1,8 @@ -/// Internal namespace. +/// Define a private namespace for all its items. +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { - #[ allow( unused_imports ) ] + #[ allow( unused_imports, clippy::wildcard_imports ) ] use crate::*; // use crate::tool::*; @@ -21,12 +22,10 @@ mod private algo::toposort as pg_toposort, }; use petgraph::graph::NodeIndex; + #[ allow( clippy::wildcard_imports ) ] use petgraph::prelude::*; - use error:: - { - typed::Error, - }; + use error::typed::Error; use package::{ Package, publish_need }; // qqq : for Bohdan : bad : tools can't depend on entitties! @@ -45,13 +44,14 @@ mod private /// /// Returns : /// The graph with all accepted packages + /// + /// # Panics + /// qqq: doc + #[ allow( clippy::implicit_hasher ) ] + #[ must_use ] pub fn construct< PackageIdentifier > ( - packages : &HashMap - < - PackageIdentifier, - HashSet< PackageIdentifier >, - > + packages : &HashMap< PackageIdentifier, HashSet< PackageIdentifier >, > ) -> Graph< &PackageIdentifier, &PackageIdentifier > where @@ -92,6 +92,10 @@ mod private /// /// # Panics /// If there is a cycle in the dependency graph + /// + /// # Errors + /// qqq: doc + #[ allow( clippy::needless_pass_by_value ) ] pub fn toposort< 'a, PackageIdentifier : Clone + std::fmt::Debug > ( graph : Graph< &'a PackageIdentifier, &'a PackageIdentifier > @@ -123,6 +127,11 @@ mod private /// # Returns /// /// The function returns a vector of vectors, where each inner vector represents a group of nodes that can be executed in parallel. Tasks within each group are sorted in topological order. + /// + /// # Panics + /// qqq: doc + #[ must_use ] + #[ allow( clippy::needless_pass_by_value ) ] pub fn topological_sort_with_grouping< 'a, PackageIdentifier : Clone + std::fmt::Debug > ( graph : Graph< &'a PackageIdentifier, &'a PackageIdentifier > @@ -136,7 +145,7 @@ mod private } let mut roots = VecDeque::new(); - for ( node, °ree ) in in_degree.iter() + for ( node, °ree ) in &in_degree { if degree == 0 { @@ -194,6 +203,10 @@ mod private /// /// # Constraints /// * `N` must implement the `PartialEq` trait. + /// + /// # Panics + /// qqq: doc + #[ allow( clippy::single_match, clippy::map_entry ) ] pub fn subgraph< N, E >( graph : &Graph< N, E >, roots : &[ N ] ) -> Graph< NodeIndex, EdgeIndex > where N : PartialEq< N >, @@ -215,7 +228,7 @@ mod private } } - for ( _, sub_node_id ) in &node_map + for sub_node_id in node_map.values() { let node_id_graph = subgraph[ *sub_node_id ]; @@ -246,14 +259,21 @@ mod private /// # Returns /// /// A new `Graph` with the nodes that are not required to be published removed. - + /// + /// # Errors + /// qqq: doc + /// + /// # Panics + /// qqq: doc // qqq : for Bohdan : typed error - pub fn remove_not_required_to_publish< 'a > + #[ allow( clippy::single_match, clippy::needless_pass_by_value, clippy::implicit_hasher ) ] + pub fn remove_not_required_to_publish ( - package_map : &HashMap< String, Package< 'a > >, + package_map : &HashMap< String, Package< '_ > >, graph : &Graph< String, String >, roots : &[ String ], temp_path : Option< PathBuf >, + exclude_dev_dependencies : bool, ) -> error::untyped::Result< Graph< String, String > > // qqq : use typed error! @@ -264,8 +284,9 @@ mod private for root in roots { let root = graph.node_indices().find( | &i | graph[ i ] == *root ).unwrap(); + // qqq : no unwraps. simulate crash here and check output. it should be verbal let mut dfs = DfsPostOrder::new( &graph, root ); - 'main : while let Some( n ) = dfs.next(&graph) + 'main : while let Some( n ) = dfs.next( &graph ) { for neighbor in graph.neighbors_directed( n, Outgoing ) { @@ -285,7 +306,7 @@ mod private .allow_dirty( true ) .form() )?; - if publish_need( package, temp_path.clone() ).unwrap() + if publish_need( package, temp_path.clone(), exclude_dev_dependencies ).unwrap() { nodes.insert( n ); } diff --git a/module/move/willbe/src/tool/http.rs b/module/move/willbe/src/tool/http.rs index d682a79d69..f62f86005f 100644 --- a/module/move/willbe/src/tool/http.rs +++ b/module/move/willbe/src/tool/http.rs @@ -1,7 +1,8 @@ -/// Internal namespace. +/// Define a private namespace for all its items. +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { - #[ allow( unused_imports ) ] + #[ allow( unused_imports, clippy::wildcard_imports ) ] use crate::tool::*; use std:: @@ -16,6 +17,12 @@ mod private /// /// Get data of remote package. /// + /// # Errors + /// qqq: doc + /// + /// # Panics + /// qqq: docs + /// // qqq : typed error pub fn download< 'a >( name : &'a str, version : &'a str ) -> error::untyped::Result< Vec< u8 > > { @@ -24,7 +31,7 @@ mod private .timeout_write( Duration::from_secs( 5 ) ) .build(); let mut buf = String::new(); - write!( &mut buf, "https://static.crates.io/crates/{0}/{0}-{1}.crate", name, version )?; + write!( &mut buf, "https://static.crates.io/crates/{name}/{name}-{version}.crate" )?; let resp = agent.get( &buf[ .. ] ).call().context( "Get data of remote package" )?; diff --git a/module/move/willbe/src/tool/iter.rs b/module/move/willbe/src/tool/iter.rs index a7b82abd7a..855c492006 100644 --- a/module/move/willbe/src/tool/iter.rs +++ b/module/move/willbe/src/tool/iter.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { } diff --git a/module/move/willbe/src/tool/macros.rs b/module/move/willbe/src/tool/macros.rs index 81861cb3de..96f7a3b06d 100644 --- a/module/move/willbe/src/tool/macros.rs +++ b/module/move/willbe/src/tool/macros.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { } diff --git a/module/move/willbe/src/tool/mod.rs b/module/move/willbe/src/tool/mod.rs index 060a323c2f..7864cdc660 100644 --- a/module/move/willbe/src/tool/mod.rs +++ b/module/move/willbe/src/tool/mod.rs @@ -1,3 +1,5 @@ +mod private {} + crate::mod_interface! { @@ -6,12 +8,15 @@ crate::mod_interface! orphan use super::cargo; /// Function and structures to work with collections. - layer collection; - orphan use super::collection; + // layer collection; + // orphan use super::collection; + use ::collection_tools; + // own use ::collection_tools::own::*; /// Errors handling. - layer error; - orphan use super::error; + // layer error; + // orphan use super::error; + use ::error_tools; /// Operate over files. layer files; diff --git a/module/move/willbe/src/tool/path.rs b/module/move/willbe/src/tool/path.rs index c07f0b3d6e..611a9c21ed 100644 --- a/module/move/willbe/src/tool/path.rs +++ b/module/move/willbe/src/tool/path.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { } @@ -6,7 +6,7 @@ mod private crate::mod_interface! { - use ::proper_path_tools; - own use ::proper_path_tools::own::*; + use ::pth; + own use ::pth::own::*; } diff --git a/module/move/willbe/src/tool/query.rs b/module/move/willbe/src/tool/query.rs index 3528d887ae..6724a0093f 100644 --- a/module/move/willbe/src/tool/query.rs +++ b/module/move/willbe/src/tool/query.rs @@ -1,7 +1,8 @@ -/// Internal namespace. +/// Define a private namespace for all its items. +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { - #[ allow( unused_imports ) ] + #[ allow( unused_imports, clippy::wildcard_imports ) ] use crate::tool::*; use std:: @@ -36,10 +37,12 @@ mod private if let Ok( i ) = s.parse::< i32 >() { Ok( Value::Int( i ) ) - } else if let Ok( b ) = s.parse::< bool >() + } + else if let Ok( b ) = s.parse::< bool >() { Ok( Value::Bool( b ) ) - } else + } + else { let s = s.trim_matches( '\'' ); Ok( Value::String( s.to_string() ) ) @@ -85,6 +88,7 @@ mod private /// assert!( result.contains( &Value::Int( 2 ) ) ); /// assert!( result.contains( &Value::Int( 3 ) ) ); /// ``` + #[ must_use ] pub fn into_vec( self ) -> Vec< Value > { match self @@ -111,6 +115,8 @@ mod private /// assert_eq!( HashMap::from( [ ( "1".to_string(), Value::Int( 1 ) ), ( "2".to_string(),Value::Int( 2 ) ), ( "3".to_string(),Value::Int( 3 ) ) ] ), unnamed_map ); /// assert_eq!( HashMap::from( [ ( "var0".to_string(), Value::Int( 1 ) ), ( "1".to_string(),Value::Int( 2 ) ), ( "2".to_string(),Value::Int( 3 ) ) ] ), mixed_map ); /// ``` + #[ allow( clippy::needless_pass_by_value ) ] + #[ must_use ] pub fn into_map( self, names : Vec< String > ) -> HashMap< String, Value > { match self @@ -148,6 +154,12 @@ mod private /// expected_map.insert( "key".to_string(), Value::String( r#"hello\'test\'test"#.into() ) ); /// assert_eq!( parse( r#"{ key : 'hello\'test\'test' }"# ).unwrap().into_map( vec![] ), expected_map ); /// ``` + /// + /// # Errors + /// qqq: doc + /// + /// # Panics + /// qqq: doc // qqq : use typed error pub fn parse( input_string : &str ) -> error::untyped::Result< ParseResult > { @@ -253,6 +265,7 @@ mod private } // qqq : use typed error + #[ allow( clippy::unnecessary_wraps ) ] fn parse_to_vec( input : Vec< String > ) -> error::untyped::Result< Vec< Value > > { Ok( input.into_iter().filter_map( | w | Value::from_str( w.trim() ).ok() ).collect() ) diff --git a/module/move/willbe/src/tool/repository.rs b/module/move/willbe/src/tool/repository.rs index 66474d906d..7ec6fb7323 100644 --- a/module/move/willbe/src/tool/repository.rs +++ b/module/move/willbe/src/tool/repository.rs @@ -1,7 +1,7 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { - #[ allow( unused_imports ) ] + #[ allow( unused_imports, clippy::wildcard_imports ) ] use crate::tool::*; /// Searches for a README file in specific subdirectories of the given directory path. @@ -9,6 +9,9 @@ mod private /// This function attempts to find a README file in the following subdirectories: ".github", /// the root directory, and "./docs". It returns the path to the first found README file, or /// `None` if no README file is found in any of these locations. + /// + /// # Errors + /// qqq: doc pub fn readme_path( dir_path : &std::path::Path ) -> Result< std::path::PathBuf, std::io::Error > { if let Some( path ) = readme_in_dir_find( &dir_path.join( ".github" ) ) diff --git a/module/move/willbe/src/tool/template.rs b/module/move/willbe/src/tool/template.rs index affe4072be..c8ac11af89 100644 --- a/module/move/willbe/src/tool/template.rs +++ b/module/move/willbe/src/tool/template.rs @@ -1,7 +1,7 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { - #[ allow( unused_imports ) ] + #[ allow( unused_imports, clippy::wildcard_imports ) ] use crate::tool::*; use std:: @@ -15,15 +15,10 @@ mod private }; use error::untyped::Context; - // qqq : for Nikita : is that trait really necessary? - // Template - remove - // DeployTemplate - move here - // DeployTemplateFiles - remove - - /// Template for creating deploy files. + /// Container for templates. /// - /// Includes terraform deploy options to GCP, and Hetzner, - /// a Makefile for useful commands, and a key directory. + /// Includes files to create, parameters that those templates accept, + /// and values for those parameters. #[ derive( Debug ) ] pub struct TemplateHolder { @@ -33,11 +28,15 @@ mod private pub parameters : TemplateParameters, /// The values associated with the template. pub values : TemplateValues, + /// Path to the parameter storage for recovering values + /// for already generated templated files. + pub parameter_storage : &'static Path, + /// Name of the template to generate + pub template_name : &'static str, } impl TemplateFiles for Vec< TemplateFileDescriptor > {} - // qqq : for Viktor : why DeployTemplate can't be part of template.rs? impl TemplateHolder { @@ -50,6 +49,9 @@ mod private /// # Returns /// /// A `Result` which is `Ok` if the files are created successfully, or an `Err` otherwise. + /// + /// # Errors + /// qqq: doc pub fn create_all( self, path : &path::Path ) -> error::untyped::Result< () > // qqq : use typed error { self.files.create_all( path, &self.values ) @@ -60,6 +62,7 @@ mod private /// # Returns /// /// A reference to `TemplateParameters`. + #[ must_use ] pub fn parameters( &self ) -> &TemplateParameters { &self.parameters @@ -72,7 +75,7 @@ mod private /// - `values`: The new `TemplateValues` to be set. pub fn set_values( &mut self, values : TemplateValues ) { - self.values = values + self.values = values; } /// Returns a reference to the template values. @@ -80,6 +83,7 @@ mod private /// # Returns /// /// A reference to `TemplateValues`. + #[ must_use ] pub fn get_values( &self ) -> &TemplateValues { &self.values @@ -95,27 +99,6 @@ mod private &mut self.values } - /// Returns the path to the parameter storage file. - /// - /// # Returns - /// - /// A reference to a `Path` representing the parameter storage file. - pub fn parameter_storage( &self ) -> &Path - { - "./.deploy_template.toml".as_ref() - // qqq : for Mykyta : hardcode? - } - - /// Returns the name of the template. - /// - /// # Returns - /// - /// A static string slice representing the template name. - pub fn template_name( &self ) -> &'static str - { - "deploy" - } - /// Loads existing parameters from the specified path and updates the template values. /// /// # Parameters @@ -127,10 +110,10 @@ mod private /// An `Option` which is `Some(())` if the parameters are loaded successfully, or `None` otherwise. pub fn load_existing_params( &mut self, path : &Path ) -> Option< () > { - let data = fs::read_to_string( path.join( self.parameter_storage() ) ).ok()?; + let data = fs::read_to_string( path.join( self.parameter_storage ) ).ok()?; let document = data.parse::< toml_edit::Document >().ok()?; let parameters : Vec< _ > = self.parameters().descriptors.iter().map( | d | &d.parameter ).cloned().collect(); - let template_table = document.get( self.template_name() )?; + let template_table = document.get( self.template_name )?; for parameter in parameters { let value = template_table.get( ¶meter ) @@ -152,6 +135,7 @@ mod private } /// Fetches mandatory parameters that are not set yet. + #[ must_use ] pub fn get_missing_mandatory( &self ) -> Vec< &str > { let values = self.get_values(); @@ -159,31 +143,11 @@ mod private .parameters() .list_mandatory() .into_iter() - .filter( | key | values.0.get( *key ).map( | val | val.as_ref() ).flatten().is_none() ) + .filter( | key | values.0.get( *key ).and_then( | val | val.as_ref() ).is_none() ) .collect() } } - impl Default for TemplateHolder - { - fn default() -> Self - { - let parameters = TemplateParameters::former() - .parameter( "gcp_project_id" ).is_mandatory( true ).end() - .parameter( "gcp_region" ).end() - .parameter( "gcp_artifact_repo_name" ).end() - .parameter( "docker_image_name" ).end() - .form(); - - Self - { - files : Default::default(), - parameters, - values : Default::default(), - } - } - } - /// Files stored in a template. /// /// Can be iterated over, consuming the owner of the files. @@ -192,10 +156,13 @@ mod private /// Creates all files in provided path with values for required parameters. /// /// Consumes owner of the files. + /// + /// # Errors + /// qqq: doc fn create_all( self, path : &Path, values : &TemplateValues ) -> error::untyped::Result< () > // qqq : use typed error { let fsw = FileSystem; - for file in self.into_iter() + for file in self { file.create_file( &fsw, path, values )?; } @@ -214,17 +181,19 @@ mod private impl TemplateParameters { /// Extracts template values from props for parameters required for this template. - pub fn values_from_props( &self, props : &wca::Props ) -> TemplateValues + #[ must_use ] + pub fn values_from_props( &self, props : &wca::executor::Props ) -> TemplateValues { let values = self.descriptors .iter() .map( | d | &d.parameter ) - .map( | param | ( param.clone(), props.get( param ).map( wca::Value::clone ) ) ) + .map( | param | ( param.clone(), props.get( param ).cloned() ) ) .collect(); TemplateValues( values ) } /// Get a list of all mandatory parameters. + #[ must_use ] pub fn list_mandatory( &self ) -> Vec< &str > { self.descriptors.iter().filter( | d | d.is_mandatory ).map( | d | d.parameter.as_str() ).collect() @@ -261,27 +230,28 @@ mod private /// Converts values to a serializable object. /// /// Currently only `String`, `Number`, and `Bool` are supported. + #[ must_use ] pub fn to_serializable( &self ) -> collection::BTreeMap< String, String > { self.0.iter().map ( | ( key, value ) | { - let value = value.as_ref().map + let value = value.as_ref().map_or ( + "___UNSPECIFIED___".to_string(), | value | { match value { wca::Value::String( val ) => val.to_string(), wca::Value::Number( val ) => val.to_string(), - wca::Value::Path( _ ) => "unsupported".to_string(), wca::Value::Bool( val ) => val.to_string(), + wca::Value::Path( _ ) | wca::Value::List( _ ) => "unsupported".to_string(), } } - ) - .unwrap_or( "___UNSPECIFIED___".to_string() ); + ); ( key.to_owned(), value ) } ) @@ -291,7 +261,7 @@ mod private /// Inserts new value if parameter wasn't initialized before. pub fn insert_if_empty( &mut self, key : &str, value : wca::Value ) { - if let None = self.0.get( key ).and_then( | v | v.as_ref() ) + if self.0.get( key ).and_then( | v | v.as_ref() ).is_none() { self.0.insert( key.into() , Some( value ) ); } @@ -300,7 +270,7 @@ mod private /// Interactively asks user to provide value for a parameter. pub fn interactive_if_empty( &mut self, key : &str ) { - if let None = self.0.get( key ).and_then( | v | v.as_ref() ) + if self.0.get( key ).and_then( | v | v.as_ref() ).is_none() { println! ("Parameter `{key}` is not set" ); let answer = wca::ask( "Enter value" ); @@ -341,7 +311,7 @@ mod private WriteMode::TomlExtend => { let instruction = FileReadInstruction { path : path.into() }; - if let Some(existing_contents) = fs.read( &instruction ).ok() + if let Ok( existing_contents ) = fs.read( &instruction ) { let document = contents.parse::< toml_edit::Document >().context( "Failed to parse template toml file" )?; let template_items = document.iter(); @@ -349,10 +319,10 @@ mod private let mut existing_document = existing_toml_contents.parse::< toml_edit::Document >().context( "Failed to parse existing toml file" )?; for ( template_key, template_item ) in template_items { - match existing_document.get_mut( &template_key ) + match existing_document.get_mut( template_key ) { - Some( item ) => *item = template_item.to_owned(), - None => existing_document[ &template_key ] = template_item.to_owned(), + Some( item ) => template_item.clone_into( item ), + None => template_item.clone_into( &mut existing_document[ template_key ] ), } } return Ok( existing_document.to_string() ); @@ -438,9 +408,13 @@ mod private pub trait FileSystemPort { /// Writing to file implementation. + /// # Errors + /// qqq: doc fn write( &self, instruction : &FileWriteInstruction ) -> error::untyped::Result< () >; // qqq : use typed error /// Reading from a file implementation. + /// # Errors + /// qqq: doc fn read( &self, instruction : &FileReadInstruction ) -> error::untyped::Result< Vec< u8 > >; // qqq : use typed error } diff --git a/module/move/willbe/src/tool/tree.rs b/module/move/willbe/src/tool/tree.rs index 3c1e0c670b..8525d0f2e0 100644 --- a/module/move/willbe/src/tool/tree.rs +++ b/module/move/willbe/src/tool/tree.rs @@ -1,3 +1,4 @@ +#[ allow( clippy::std_instead_of_alloc, clippy::std_instead_of_core ) ] mod private { use std::fmt::Write; @@ -26,7 +27,8 @@ mod private /// # Returns /// /// A new instance of `TreePrinter`. - pub fn new(info : &ListNodeReport) -> Self + #[ must_use ] + pub fn new(info : &ListNodeReport) -> Self { TreePrinter { @@ -44,15 +46,21 @@ mod private /// # Returns /// /// * A `Result` containing the formatted string or a `std::fmt::Error` if formatting fails. + /// + /// # Errors + /// qqq: doc + /// + /// # Panics + /// qqq: doc pub fn display_with_spacer( &self, spacer : &str ) -> Result< String, std::fmt::Error > { let mut f = String::new(); write!( f, "{}", self.info.name )?; if let Some( version ) = &self.info.version { write!( f, " {version}" )? } - if let Some( crate_dir ) = &self.info.crate_dir { write!( f, " {}", crate_dir )? } + if let Some( crate_dir ) = &self.info.crate_dir { write!( f, " {crate_dir}" )? } if self.info.duplicate { write!( f, "(*)" )? } - write!( f, "\n" )?; + writeln!( f )?; let mut new_spacer = format!( "{spacer}{} ", if self.info.normal_dependencies.len() < 2 { " " } else { self.symbols.down } ); @@ -72,7 +80,7 @@ mod private { let mut dev_dependencies_iter = self.info.dev_dependencies.iter(); let last = dev_dependencies_iter.next_back(); - write!( f, "{spacer}[dev-dependencies]\n" )?; + writeln!( f, "{spacer}[dev-dependencies]" )?; for dep in dev_dependencies_iter { write!( f, "{spacer}{}{} {}", self.symbols.tee, self.symbols.right, Self::display_with_spacer( &TreePrinter::new( dep ), &new_spacer )? )?; @@ -84,7 +92,7 @@ mod private { let mut build_dependencies_iter = self.info.build_dependencies.iter(); let last = build_dependencies_iter.next_back(); - write!( f, "{spacer}[build-dependencies]\n" )?; + writeln!( f, "{spacer}[build-dependencies]" )?; for dep in build_dependencies_iter { write!( f, "{spacer}{}{} {}", self.symbols.tee, self.symbols.right, Self::display_with_spacer( &TreePrinter::new( dep ), &new_spacer )? )?; @@ -146,15 +154,15 @@ mod private /// This field is a flag indicating whether the Node is a duplicate or not. pub duplicate : bool, /// A list that stores normal dependencies. - /// Each element in the list is also of the same 'ListNodeReport' type to allow + /// Each element in the list is also of the same '`ListNodeReport`' type to allow /// storage of nested dependencies. pub normal_dependencies : Vec< ListNodeReport >, /// A list that stores dev dependencies(dependencies required for tests or examples). - /// Each element in the list is also of the same 'ListNodeReport' type to allow + /// Each element in the list is also of the same '`ListNodeReport`' type to allow /// storage of nested dependencies. pub dev_dependencies : Vec< ListNodeReport >, /// A list that stores build dependencies. - /// Each element in the list is also of the same 'ListNodeReport' type to allow + /// Each element in the list is also of the same '`ListNodeReport`' type to allow /// storage of nested dependencies. pub build_dependencies : Vec< ListNodeReport >, } diff --git a/module/move/willbe/src/tool/url.rs b/module/move/willbe/src/tool/url.rs index f1ab5b8f9c..a7f76716c4 100644 --- a/module/move/willbe/src/tool/url.rs +++ b/module/move/willbe/src/tool/url.rs @@ -1,7 +1,7 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { - #[ allow( unused_imports ) ] + #[ allow( unused_imports, clippy::wildcard_imports ) ] use crate::tool::*; use error::untyped:: @@ -11,15 +11,16 @@ mod private }; /// Extracts the repository URL from a full URL. + #[ must_use ] pub fn repo_url_extract( full_url : &str ) -> Option< String > { let parts : Vec< &str > = full_url.split( '/' ).collect(); - if parts.len() >= 4 && parts[ 0 ] == "https:" && parts[ 1 ] == "" && parts[ 2 ] == "github.com" + if parts.len() >= 4 && parts[ 0 ] == "https:" && parts[ 1 ].is_empty() && parts[ 2 ] == "github.com" { let user = parts[ 3 ]; let repo = parts[ 4 ]; - let repo_url = format!( "https://github.com/{}/{}", user, repo ); + let repo_url = format!( "https://github.com/{user}/{repo}" ); Some( repo_url ) } else @@ -29,8 +30,10 @@ mod private } /// Extracts the username and repository name from a given URL. + /// # Errors + /// qqq: doc // qqq : use typed error - pub fn git_info_extract( url : &String ) -> error::untyped::Result< String > + pub fn git_info_extract( url : &str ) -> error::untyped::Result< String > { let parts : Vec< &str > = url.split( '/' ).collect(); if parts.len() >= 2 diff --git a/module/move/willbe/src/wtools.rs b/module/move/willbe/src/wtools.rs index 1735805c0b..4fe43d10e9 100644 --- a/module/move/willbe/src/wtools.rs +++ b/module/move/willbe/src/wtools.rs @@ -21,8 +21,8 @@ // /// Collection of function and structures to manipulate paths. // pub mod path_tools // { -// // pub use proper_path_tools::own::*; -// // pub use proper_path_tools::own::path; -// // zzz : make use proper_path_tools::own::path working +// // pub use pth::own::*; +// // pub use pth::own::path; +// // zzz : make use pth::own::path working // pub use proper_path::own as path; // } diff --git a/module/move/willbe/template/deploy/Makefile.hbs b/module/move/willbe/template/deploy/Makefile.hbs index 2f3461aea8..4c593877c7 100644 --- a/module/move/willbe/template/deploy/Makefile.hbs +++ b/module/move/willbe/template/deploy/Makefile.hbs @@ -1,16 +1,5 @@ .PHONY: deploy -# Secrets that can be provided via ENV vars or files in ./key/ directory. - -# Hetzner API token -export SECRET_CSP_HETZNER ?= $(shell cat key/SECRET_CSP_HETZNER 2> /dev/null) -# Cloud Storage file encryption key -export SECRET_STATE_ARCHIVE_KEY ?= $(shell cat key/SECRET_STATE_ARCHIVE_KEY 2> /dev/null) -# AWS Access Key id -export SECRET_AWS_ACCESS_KEY_ID ?= $(shell cat key/SECRET_AWS_ACCESS_KEY_ID 2> /dev/null) -# AWS Access Key -export SECRET_AWS_ACCESS_KEY ?= $(shell cat key/SECRET_AWS_ACCESS_KEY 2> /dev/null) - # Configuration variables for deployment. Can be edited for desired behavior. # Base terraform directory @@ -24,9 +13,9 @@ export TF_VAR_REPO_NAME ?= {{gcp_artifact_repo_name}} # Pushed image name export TF_VAR_IMAGE_NAME ?= {{docker_image_name}} # Path to the service account credentials -export google_sa_creds ?= key/service_account.json +export google_sa_creds ?= key/-service_account.json # Cloud Storage bucket name -export TF_VAR_BUCKET_NAME ?= uaconf_tfstate +export TF_VAR_BUCKET_NAME ?= {{docker_image_name}}_tfstate # Specifies where to deploy the project. Possible values: `hetzner`, `gce`, `aws` export CSP ?= hetzner @@ -43,129 +32,121 @@ export AWS_ACCESS_KEY_ID ?= $(SECRET_AWS_ACCESS_KEY_ID) # AWS Secret Access key for deploying to an EC2 instance export AWS_SECRET_ACCESS_KEY ?= $(SECRET_AWS_ACCESS_KEY) -# Check Hetzner and deployment related keys +# Check Hetzner and deployment related keys check-hetzner-keys: - @[ -f key/SECRET_CSP_HETZNER ] \ - || [ ! -z "${SECRET_CSP_HETZNER}" ] \ - || { echo "ERROR: File key/SECRET_CSP_HETZNER does not exist"; exit 1; } + @[ ! -z "${SECRET_CSP_HETZNER}" ] \ + || { echo "ERROR: Key SECRET_CSP_HETZNER does not exist"; exit 1; } -# Check AWS and deployment related keys +# Check AWS and deployment related keys check-aws-keys: - @[ -f key/SECRET_AWS_ACCESS_KEY_ID ] \ - || [ ! -z "${SECRET_AWS_ACCESS_KEY_ID}" ] \ - || echo "ERROR: File key/SECRET_AWS_ACCESS_KEY_ID does not exist" - @[ -f key/SECRET_AWS_ACCESS_KEY ] \ - || [ ! -z "${SECRET_AWS_ACCESS_KEY}" ] \ - || echo "ERROR: File key/SECRET_AWS_ACCESS_KEY does not exist" - @[ -f key/SECRET_AWS_ACCESS_KEY_ID ] \ - || [ ! -z "${SECRET_AWS_ACCESS_KEY_ID}" ] \ - || exit 1 - @[ -f key/SECRET_AWS_ACCESS_KEY ] \ - || [ ! -z "${SECRET_AWS_ACCESS_KEY}" ] \ - || exit 1 + @[ ! -z "${SECRET_AWS_ACCESS_KEY_ID}" ] \ + || echo "ERROR: Key SECRET_AWS_ACCESS_KEY_ID does not exist" + @[ ! -z "${SECRET_AWS_ACCESS_KEY}" ] \ + || echo "ERROR: Key SECRET_AWS_ACCESS_KEY does not exist" + @[ ! -z "${SECRET_AWS_ACCESS_KEY_ID}" ] || exit 1 + @[ ! -z "${SECRET_AWS_ACCESS_KEY}" ] || exit 1 check-gce-keys: - @echo "All required GCE keys are the same as GCP keys" + @echo "All required GCE keys are the same as GCP keys" # Check if required GCP keys are present check-gcp-keys: - @[ -f key/service_account.json ] \ - || echo "ERROR: File key/service_account.json does not exist" - @[ -f key/SECRET_STATE_ARCHIVE_KEY ] \ - || [ ! -z "${SECRET_STATE_ARCHIVE_KEY}" ] \ - || echo "ERROR: File key/SECRET_STATE_ARCHIVE_KEY does not exist" - @[ -f key/service_account.json ] \ - || exit 1 - @[ -f key/SECRET_STATE_ARCHIVE_KEY ] \ - || [ ! -z "${SECRET_STATE_ARCHIVE_KEY}" ] \ - || exit 1 + @[ -f key/-service_account.json ] \ + || echo "ERROR: Key file key/-service_account.json does not exist" + @[ ! -z "${SECRET_STATE_ARCHIVE_KEY}" ] \ + || echo "ERROR: Key SECRET_STATE_ARCHIVE_KEY does not exist" + @[ -f key/-service_account.json ] || exit 1 + @[ ! -z "${SECRET_STATE_ARCHIVE_KEY}" ] || exit 1 # Start local docker container start: - docker compose up -d + docker compose up -d # Stop local docker container stop: - docker compose down + docker compose down # Remove created docker image clean: stop - docker rmi $(TF_VAR_IMAGE_NAME) - docker buildx prune -af + docker rmi $(TF_VAR_IMAGE_NAME) + docker buildx prune -af # Install gcloud for Debian/Ubuntu install-gcloud: - # GCloud - sudo apt-get update - sudo apt-get install -y apt-transport-https ca-certificates gnupg curl sudo - curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg - echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list - sudo apt-get update && sudo apt-get install -y google-cloud-cli + # GCloud + sudo apt-get update + sudo apt-get install -y apt-transport-https ca-certificates gnupg curl sudo + curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg + echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list + sudo apt-get update && sudo apt-get install -y google-cloud-cli # Install terraform for Debian/Ubuntu install-terraform: - sudo apt-get update && sudo apt-get install -y gnupg software-properties-common - wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg - gpg --no-default-keyring --keyring /usr/share/keyrings/hashicorp-archive-keyring.gpg --fingerprint - echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list - sudo apt update && sudo apt-get install terraform + sudo apt-get update && sudo apt-get install -y gnupg software-properties-common + wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg + gpg --no-default-keyring --keyring /usr/share/keyrings/hashicorp-archive-keyring.gpg --fingerprint + echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list + sudo apt update && sudo apt-get install terraform # Install gcloud and terraform install: install-gcloud install-terraform - gcloud --version - terraform -version + gcloud --version + terraform -version # Login to GCP with user account gcp-auth: - gcloud auth application-default login + gcloud auth application-default login # Authorize to GCP with service account gcp-service: - gcloud auth activate-service-account --key-file=$(google_sa_creds) + gcloud auth activate-service-account --key-file=$(google_sa_creds) # Add docker repo auth helper gcp-docker: - gcloud auth configure-docker $(TF_VAR_REGION)-docker.pkg.dev --quiet + gcloud auth configure-docker $(TF_VAR_REGION)-docker.pkg.dev --quiet # Initializes all terraform projects # Downloads required modules and validates .tf files tf-init: - terraform -chdir=$(tf_dir)/gar init - terraform -chdir=$(tf_dir)/gce init - terraform -chdir=$(tf_dir)/hetzner init - terraform -chdir=$(tf_dir)/aws init + terraform -chdir=$(tf_dir)/gar init + terraform -chdir=$(tf_dir)/gce init + terraform -chdir=$(tf_dir)/hetzner init + terraform -chdir=$(tf_dir)/aws init # Creates Artifact Registry repository on GCP in specified location -create-artifact-repo: tf-init - terraform -chdir=$(tf_dir)/gar apply -auto-approve +create-artifact-repo: state_storage_pull tf-init + terraform -chdir=$(tf_dir)/gar apply -auto-approve -# Builds uarust_conf_site image +# Builds {{docker_image_name}} image build-image: - docker build . -t name:$(TF_VAR_IMAGE_NAME) -t $(tag) + docker build . -f Dockerfile.1 -t name:$(TF_VAR_IMAGE_NAME) -t $(tag)_1 + docker build . -f Dockerfile.2 -t name:$(TF_VAR_IMAGE_NAME) -t $(tag)_2 # Builds and pushes local docker image to the private repository -push-image: gcp-docker create-artifact-repo - docker push $(tag) +push-image: gcp-docker create-artifact-repo state_storage_push + docker push $(tag)_1 + docker push $(tag)_2 # Creates GCE instance with the website configured on boot create-gce: check-gce-keys gcp-service state_storage_pull push-image - terraform -chdir=$(tf_dir)/gce apply -auto-approve + terraform -chdir=$(tf_dir)/gce apply -auto-approve # Creates AWS EC2 instance with the website configured on boot create-aws: check-aws-keys gcp-service state_storage_pull push-image - terraform -chdir=$(tf_dir)/aws apply -auto-approve + terraform -chdir=$(tf_dir)/aws apply -auto-approve # Creates Hetzner instance with the website configured on boot create-hetzner: check-hetzner-keys gcp-service state_storage_pull push-image - terraform -chdir=$(tf_dir)/hetzner apply -auto-approve + terraform -chdir=$(tf_dir)/hetzner apply -auto-approve # Deploys everything and updates terraform states -deploy-in-container: create-$(CSP) state_storage_push +deploy-in-container: + make create-$(CSP) && make state_storage_push || { make state_storage_push; echo "Deployment failed"; exit 1; } # Deploys using tools from the container deploy: check-gcp-keys build-image - docker build . -t deploy-$(TF_VAR_IMAGE_NAME) -f ./$(tf_dir)/Dockerfile --build-arg google_sa_creds="$(google_sa_creds)" - @docker run -v //var/run/docker.sock:/var/run/docker.sock -v .:/app \ + docker build . -t deploy-$(TF_VAR_IMAGE_NAME) -f ./$(tf_dir)/Dockerfile --build-arg google_sa_creds="$(google_sa_creds)" + @docker run -v //var/run/docker.sock:/var/run/docker.sock -v .:/app \ -e SECRET_STATE_ARCHIVE_KEY=$(SECRET_STATE_ARCHIVE_KEY) \ -e SECRET_CSP_HETZNER=$(SECRET_CSP_HETZNER) \ -e SECRET_AWS_ACCESS_KEY_ID=$(SECRET_AWS_ACCESS_KEY_ID) \ @@ -175,35 +156,39 @@ deploy: check-gcp-keys build-image # Review changes that terraform will do on apply tf-plan: tf-init - terraform -chdir=$(tf_dir)/gar plan - terraform -chdir=$(tf_dir)/gce plan - terraform -chdir=$(tf_dir)/hetzner plan - terraform -chdir=$(tf_dir)/aws plan + terraform -chdir=$(tf_dir)/gar plan + terraform -chdir=$(tf_dir)/gce plan + terraform -chdir=$(tf_dir)/hetzner plan + terraform -chdir=$(tf_dir)/aws plan # Destroy created infrastracture on GCP tf-destroy: tf-init - terraform -chdir=$(tf_dir)/gar destroy - terraform -chdir=$(tf_dir)/gce destroy - terraform -chdir=$(tf_dir)/hetzner destroy - terraform -chdir=$(tf_dir)/aws destroy + terraform -chdir=$(tf_dir)/gar destroy + terraform -chdir=$(tf_dir)/gce destroy + terraform -chdir=$(tf_dir)/hetzner destroy + terraform -chdir=$(tf_dir)/aws destroy # Pushes encrypted terraform state files to the GCS Bucket state_storage_push: - @echo Pushing encrypted terraform state files to the GCS Bucket - @gcloud storage cp $(tf_dir)/gce/terraform.tfstate gs://$(TF_VAR_BUCKET_NAME)/gce.tfstate --encryption-key="$(SECRET_STATE_ARCHIVE_KEY)" - @gcloud storage cp $(tf_dir)/gar/terraform.tfstate gs://$(TF_VAR_BUCKET_NAME)/gar.tfstate --encryption-key="$(SECRET_STATE_ARCHIVE_KEY)" - @gcloud storage cp $(tf_dir)/hetzner/terraform.tfstate gs://$(TF_VAR_BUCKET_NAME)/hetzner.tfstate --encryption-key="$(SECRET_STATE_ARCHIVE_KEY)" - @gcloud storage cp $(tf_dir)/aws/terraform.tfstate gs://$(TF_VAR_BUCKET_NAME)/aws.tfstate --encryption-key="$(SECRET_STATE_ARCHIVE_KEY)" + @echo Pushing encrypted terraform state files to the GCS Bucket + -@gcloud storage cp $(tf_dir)/gce/terraform.tfstate gs://$(TF_VAR_BUCKET_NAME)/gce.tfstate --encryption-key="$(SECRET_STATE_ARCHIVE_KEY)" + -@gcloud storage cp $(tf_dir)/gar/terraform.tfstate gs://$(TF_VAR_BUCKET_NAME)/gar.tfstate --encryption-key="$(SECRET_STATE_ARCHIVE_KEY)" + -@gcloud storage cp $(tf_dir)/hetzner/terraform.tfstate gs://$(TF_VAR_BUCKET_NAME)/hetzner.tfstate --encryption-key="$(SECRET_STATE_ARCHIVE_KEY)" + -@gcloud storage cp $(tf_dir)/aws/terraform.tfstate gs://$(TF_VAR_BUCKET_NAME)/aws.tfstate --encryption-key="$(SECRET_STATE_ARCHIVE_KEY)" # Pulls and decrypts terraform state files to the GCS Bucket state_storage_pull: - @echo Pulling terraform state files to the GCS Bucket - -@gcloud storage cp gs://$(TF_VAR_BUCKET_NAME)/gce.tfstate $(tf_dir)/gce/terraform.tfstate --decryption-keys="$(SECRET_STATE_ARCHIVE_KEY)" - -@gcloud storage cp gs://$(TF_VAR_BUCKET_NAME)/gar.tfstate $(tf_dir)/gar/terraform.tfstate --decryption-keys="$(SECRET_STATE_ARCHIVE_KEY)" - -@gcloud storage cp gs://$(TF_VAR_BUCKET_NAME)/hetzner.tfstate $(tf_dir)/hetzner/terraform.tfstate --decryption-keys="$(SECRET_STATE_ARCHIVE_KEY)" - -@gcloud storage cp gs://$(TF_VAR_BUCKET_NAME)/aws.tfstate $(tf_dir)/aws/terraform.tfstate --decryption-keys="$(SECRET_STATE_ARCHIVE_KEY)" + @echo Pulling terraform state files to the GCS Bucket + -@gcloud storage cp gs://$(TF_VAR_BUCKET_NAME)/gce.tfstate $(tf_dir)/gce/terraform.tfstate --decryption-keys="$(SECRET_STATE_ARCHIVE_KEY)" + -@gcloud storage cp gs://$(TF_VAR_BUCKET_NAME)/gar.tfstate $(tf_dir)/gar/terraform.tfstate --decryption-keys="$(SECRET_STATE_ARCHIVE_KEY)" + -@gcloud storage cp gs://$(TF_VAR_BUCKET_NAME)/hetzner.tfstate $(tf_dir)/hetzner/terraform.tfstate --decryption-keys="$(SECRET_STATE_ARCHIVE_KEY)" + -@gcloud storage cp gs://$(TF_VAR_BUCKET_NAME)/aws.tfstate $(tf_dir)/aws/terraform.tfstate --decryption-keys="$(SECRET_STATE_ARCHIVE_KEY)" # Creates GCS Bucket for terraform states state_storage_init: - terraform -chdir=$(tf_dir)/gcs init - terraform -chdir=$(tf_dir)/gcs apply + terraform -chdir=$(tf_dir)/gcs init + terraform -chdir=$(tf_dir)/gcs apply + +# Destroys GCS Bucket for terraform states +state_storage_destroy: + terraform -chdir=$(tf_dir)/gcs destroy diff --git a/module/move/willbe/template/deploy/deploy/Dockerfile b/module/move/willbe/template/deploy/deploy/Dockerfile index c196de7aff..1fa8f2bf8b 100644 --- a/module/move/willbe/template/deploy/deploy/Dockerfile +++ b/module/move/willbe/template/deploy/deploy/Dockerfile @@ -3,7 +3,7 @@ ENV TF_VERSION=1.7.4 WORKDIR / -# Installation terraform +# Install terraform RUN apt update --allow-releaseinfo-change \ && apt install wget unzip \ && mkdir -p /usr/lib/terraform/${TF_VERSION} \ diff --git a/module/move/willbe/template/deploy/deploy/aws/main.tf b/module/move/willbe/template/deploy/deploy/aws/main.tf index 4e83260aaf..9572193a66 100644 --- a/module/move/willbe/template/deploy/deploy/aws/main.tf +++ b/module/move/willbe/template/deploy/deploy/aws/main.tf @@ -60,17 +60,16 @@ resource "aws_instance" "web" { associate_public_ip_address = true # Startup script for the instance - # Installs docker, gcloud CLI, downloads docker images and starts the container - user_data = templatefile("${path.module}/templates/cloud-init.tpl", { + # Installs docker and gcloud CLI + user_data = templatefile("${path.module}/../cloud-init.tpl", { location = "${var.REGION}" project_id = "${var.PROJECT_ID}" repo_name = "${var.REPO_NAME}" image_name = "${var.IMAGE_NAME}" service_account_creds = "${replace(data.local_sensitive_file.service_account_creds.content, "\n", "")}" - timestamp = "${timestamp()}" }) - user_data_replace_on_change = true + key_name = aws_key_pair.redeploy.key_name } # Static IP address for the instace that will persist on restarts and redeploys @@ -78,3 +77,33 @@ resource "aws_eip" "static" { instance = aws_instance.web.id domain = "vpc" } + +resource "aws_key_pair" "redeploy" { + public_key = data.local_sensitive_file.ssh_public_key.content +} + +resource "terraform_data" "redeploy" { + triggers_replace = timestamp() + + connection { + type = "ssh" + user = "ubuntu" + private_key = data.local_sensitive_file.ssh_private_key.content + host = aws_eip.static.public_ip + } + + provisioner "file" { + source = "${path.module}/../redeploy.sh" + destination = "/tmp/redeploy.sh" + } + + provisioner "remote-exec" { + inline = [ + "#!/bin/bash", + "( tail -f -n1 /var/log/deploy-init.log & ) | grep -q 'Docker configuration file updated.'", + "source /etc/environment", + "chmod +x /tmp/redeploy.sh", + "sudo /tmp/redeploy.sh" + ] + } +} diff --git a/module/move/willbe/template/deploy/deploy/aws/templates/cloud-init.tpl b/module/move/willbe/template/deploy/deploy/aws/templates/cloud-init.tpl deleted file mode 100644 index 7a19732c3a..0000000000 --- a/module/move/willbe/template/deploy/deploy/aws/templates/cloud-init.tpl +++ /dev/null @@ -1,46 +0,0 @@ -#cloud-config - -write_files: -- path: /etc/systemd/system/${image_name}.service - permissions: 0644 - owner: root - content: | - [Unit] - Description=Start ${image_name} docker container. Build: ${timestamp} - Wants=network-online.target - After=network-online.target - - [Service] - Environment="HOME=/root" - ExecStart=/usr/bin/docker run --restart unless-stopped -d -p 80:80 --name=${image_name} ${location}-docker.pkg.dev/${project_id}/${repo_name}/${image_name} -- path: /root/service_account.json - permissions: 0600 - owner: root - content: | - ${service_account_creds} -- path: /root/init.sh - permissions: 0700 - owner: root - content: | - # Install docker - apt update - apt install apt-transport-https ca-certificates curl software-properties-common -y - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - - add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" - apt update - apt install docker-ce -y - # Install gcloud CLI - curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg - echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list - apt-get update - apt-get install -y google-cloud-cli - # Configure docker with gcloud - gcloud auth activate-service-account --key-file=/root/service_account.json - gcloud auth configure-docker ${location}-docker.pkg.dev --quiet - # Start docker container - systemctl daemon-reload - systemctl start ${image_name}.service - - -runcmd: -- nohup /root/init.sh > /var/log/uaconf-instance-init.log 2>&1 & diff --git a/module/move/willbe/template/deploy/deploy/aws/variables.tf b/module/move/willbe/template/deploy/deploy/aws/variables.tf index ede2b296f3..c536019a13 100644 --- a/module/move/willbe/template/deploy/deploy/aws/variables.tf +++ b/module/move/willbe/template/deploy/deploy/aws/variables.tf @@ -20,5 +20,15 @@ variable "IMAGE_NAME" { # Google Cloud Platform credentials data "local_sensitive_file" "service_account_creds" { - filename = "${path.module}/../../key/service_account.json" + filename = "${path.module}/../../key/-service_account.json" +} + +# Private key for SSH connection +data "local_sensitive_file" "ssh_private_key" { + filename = "${path.module}/../../key/-rsa_ssh_key" +} + +# Public key for SSH connection +data "local_sensitive_file" "ssh_public_key" { + filename = "${path.module}/../../key/-rsa_ssh_key.pub" } diff --git a/module/move/willbe/template/deploy/deploy/hetzner/templates/cloud-init.tpl b/module/move/willbe/template/deploy/deploy/cloud-init.tpl.hbs similarity index 65% rename from module/move/willbe/template/deploy/deploy/hetzner/templates/cloud-init.tpl rename to module/move/willbe/template/deploy/deploy/cloud-init.tpl.hbs index 37cb18d6e9..ce5dcfc9e2 100644 --- a/module/move/willbe/template/deploy/deploy/hetzner/templates/cloud-init.tpl +++ b/module/move/willbe/template/deploy/deploy/cloud-init.tpl.hbs @@ -1,18 +1,6 @@ #cloud-config write_files: -- path: /etc/systemd/system/${image_name}.service - permissions: 0644 - owner: root - content: | - [Unit] - Description=Start ${image_name} docker container. Build: ${timestamp} - Wants=network-online.target - After=network-online.target - - [Service] - Environment="HOME=/root" - ExecStart=/usr/bin/docker run -d -p 80:80 --name=${image_name} ${location}-docker.pkg.dev/${project_id}/${repo_name}/${image_name} - path: /root/service_account.json permissions: 0600 owner: root @@ -22,6 +10,10 @@ write_files: permissions: 0700 owner: root content: | + # Configure env for redeploy script + echo "DOCKER_IMAGE=${location}-docker.pkg.dev/${project_id}/${repo_name}/${image_name}" >> /etc/environment + echo "DOCKER_IMAGE_NAME=${image_name}" >> /etc/environment + # Install docker apt update apt install apt-transport-https ca-certificates curl software-properties-common -y @@ -37,10 +29,7 @@ write_files: # Configure docker with gcloud gcloud auth activate-service-account --key-file=/root/service_account.json gcloud auth configure-docker ${location}-docker.pkg.dev --quiet - # Start docker container - systemctl daemon-reload - systemctl start ${image_name}.service runcmd: -- nohup /root/init.sh > /var/log/uaconf-instance-init.log 2>&1 & +- nohup /root/init.sh > /var/log/deploy-init.log 2>&1 & diff --git a/module/move/willbe/template/deploy/deploy/gar/main.tf b/module/move/willbe/template/deploy/deploy/gar/main.tf.hbs similarity index 82% rename from module/move/willbe/template/deploy/deploy/gar/main.tf rename to module/move/willbe/template/deploy/deploy/gar/main.tf.hbs index 77709d13e6..920cd1db1e 100644 --- a/module/move/willbe/template/deploy/deploy/gar/main.tf +++ b/module/move/willbe/template/deploy/deploy/gar/main.tf.hbs @@ -9,7 +9,7 @@ resource "google_artifact_registry_repository" "container-images-repo" { location = var.REGION project = var.PROJECT_ID repository_id = var.REPO_NAME - description = "Docker image registry for the Learn Together web-site" + description = "Docker image registry for the {{docker_image_name}} deployments" # Format of the repository. We are using Docker. format = "DOCKER" } diff --git a/module/move/willbe/template/deploy/deploy/gce/main.tf b/module/move/willbe/template/deploy/deploy/gce/main.tf.hbs similarity index 62% rename from module/move/willbe/template/deploy/deploy/gce/main.tf rename to module/move/willbe/template/deploy/deploy/gce/main.tf.hbs index 9e74a148e1..f2cb1598d0 100644 --- a/module/move/willbe/template/deploy/deploy/gce/main.tf +++ b/module/move/willbe/template/deploy/deploy/gce/main.tf.hbs @@ -1,9 +1,8 @@ locals { # Helper var for formatting docker image name - image_name = format("%s-docker.pkg.dev/%s/%s/%s", var.REGION, var.PROJECT_ID, var.REPO_NAME, var.IMAGE_NAME) + image_name = format("%s-docker.pkg.dev/%s/%s/%s", var.REGION, var.PROJECT_ID, var.REPO_NAME, var.IMAGE_NAME) # Helper var for formatting subnetwork for our instance - subnetwork = format("projects/%s/regions/%s/subnetworks/default", var.PROJECT_ID, var.REGION) - instance_name = format("ltsite-%s", formatdate("YYYYMMDDhhmmss", timestamp())) + subnetwork = format("projects/%s/regions/%s/subnetworks/default", var.PROJECT_ID, var.REGION) } # Provider for resource creation @@ -18,10 +17,10 @@ resource "google_compute_address" "default" { } # GCE instance block. -resource "google_compute_instance" "lts-container-vm" { - project = var.PROJECT_ID +resource "google_compute_instance" "{{docker_image_name}}" { + project = var.PROJECT_ID # Instance name - name = local.instance_name + name = "{{docker_image_name}}" # Instance size. e2-micro is 0.25-2 vCPU & 1GB RAM machine_type = "e2-micro" zone = var.ZONE @@ -29,12 +28,12 @@ resource "google_compute_instance" "lts-container-vm" { # Main disk options boot_disk { initialize_params { - # Disk image name. We're using Container-optimised OS (COS). - image = "projects/cos-cloud/global/images/cos-stable-109-17800-147-15" + # Disk image name. We're using Ubuntu 24.04 distro. + image = "projects/ubuntu-os-cloud/global/images/ubuntu-2404-noble-amd64-v20241004" # Disk size in GB. 10GB is allowed minimum. - size = 10 + size = 10 # Disk type. Possible values: pd-standard, pd-ssd, or pd-balanced. - type = "pd-balanced" + type = "pd-balanced" } } @@ -52,19 +51,20 @@ resource "google_compute_instance" "lts-container-vm" { metadata = { # Cloud-init startup script for configuring the instance with our docker container. user-data = "${data.cloudinit_config.conf.rendered}" + ssh-keys = "root:${data.local_sensitive_file.ssh_public_key.content}" } allow_stopping_for_update = true scheduling { # Restart on failure. - automatic_restart = true + automatic_restart = true # Describes maintenance behavior for the instance. Possible values: MIGRATE or TERMINATE. on_host_maintenance = "MIGRATE" # Configures whether to allow stopping instance at any moment for reduced cost. - preemptible = false + preemptible = false # Configures spot instance. Possible values: SPOT or STANDARD. - provisioning_model = "STANDARD" + provisioning_model = "STANDARD" } # Configues service account scopes. @@ -86,3 +86,29 @@ resource "google_compute_instance" "lts-container-vm" { # Use `https-server` for https traffic on port 443. tags = ["http-server"] } + +resource "terraform_data" "redeploy" { + triggers_replace = timestamp() + + connection { + type = "ssh" + user = "root" + private_key = data.local_sensitive_file.ssh_private_key.content + host = google_compute_instance.{{docker_image_name}}.network_interface[0].access_config[0].nat_ip + } + + provisioner "file" { + source = "${path.module}/../redeploy.sh" + destination = "/tmp/redeploy.sh" + } + + provisioner "remote-exec" { + inline = [ + "#!/bin/bash", + "( tail -f -n1 /var/log/deploy-init.log & ) | grep -q 'Docker configuration file updated.'", + "source /etc/environment", + "chmod +x /tmp/redeploy.sh", + "/tmp/redeploy.sh" + ] + } +} diff --git a/module/move/willbe/template/deploy/deploy/gce/outputs.tf b/module/move/willbe/template/deploy/deploy/gce/outputs.tf.hbs similarity index 79% rename from module/move/willbe/template/deploy/deploy/gce/outputs.tf rename to module/move/willbe/template/deploy/deploy/gce/outputs.tf.hbs index 9228e2fa83..58b076f05b 100644 --- a/module/move/willbe/template/deploy/deploy/gce/outputs.tf +++ b/module/move/willbe/template/deploy/deploy/gce/outputs.tf.hbs @@ -1,5 +1,5 @@ locals { - ip = google_compute_instance.lts-container-vm.network_interface[0].access_config[0].nat_ip + ip = google_compute_instance.{{docker_image_name}}.network_interface[0].access_config[0].nat_ip } # Output that we get after applying. diff --git a/module/move/willbe/template/deploy/deploy/gce/templates/cloud-init.tpl b/module/move/willbe/template/deploy/deploy/gce/templates/cloud-init.tpl deleted file mode 100644 index 5c465968d9..0000000000 --- a/module/move/willbe/template/deploy/deploy/gce/templates/cloud-init.tpl +++ /dev/null @@ -1,24 +0,0 @@ -#cloud-config - -users: -- name: ${image_name} - uid: 2000 - -write_files: -- path: /etc/systemd/system/${image_name}.service - permissions: 0644 - owner: root - content: | - [Unit] - Description=Start the Learn Together ${image_name} docker container - Wants=gcr-online.target - After=gcr-online.target - - [Service] - Environment="HOME=/home/${image_name}" - ExecStartPre=/usr/bin/docker-credential-gcr configure-docker --registries=${location}-docker.pkg.dev - ExecStart=/usr/bin/docker run -d -p 80:80 --name=${image_name} ${location}-docker.pkg.dev/${project_id}/${repo_name}/${image_name} - -runcmd: -- systemctl daemon-reload -- systemctl start ${image_name}.service \ No newline at end of file diff --git a/module/move/willbe/template/deploy/deploy/hetzner/main.tf b/module/move/willbe/template/deploy/deploy/hetzner/main.tf deleted file mode 100644 index da3118ecef..0000000000 --- a/module/move/willbe/template/deploy/deploy/hetzner/main.tf +++ /dev/null @@ -1,49 +0,0 @@ -terraform { - # Specifies terraform API provider to use for `hcloud` - required_providers { - hcloud = { - source = "hetznercloud/hcloud" - version = "1.45.0" - } - } -} - -# Configures hcloud provider for deploy -provider "hcloud" { - # Hetzner API token - token = var.HCLOUD_TOKEN -} - -# Static IP for the instance -resource "hcloud_primary_ip" "primary_ip" { - name = "uaconf-2024-ip" - datacenter = "hel1-dc2" - type = "ipv4" - assignee_type = "server" - auto_delete = false -} - -# Hetzner instance itself -resource "hcloud_server" "uaconf" { - name = "uaconf-2024" - image = "ubuntu-22.04" - server_type = "cx11" - datacenter = "hel1-dc2" - - public_net { - ipv4_enabled = true - ipv4 = hcloud_primary_ip.primary_ip.id - ipv6_enabled = false - } - - # Startup script for the instance - # Installs docker, gcloud CLI, downloads docker images and starts the container - user_data = templatefile("${path.module}/templates/cloud-init.tpl", { - location = "${var.REGION}" - project_id = "${var.PROJECT_ID}" - repo_name = "${var.REPO_NAME}" - image_name = "${var.IMAGE_NAME}" - service_account_creds = "${replace(data.local_sensitive_file.service_account_creds.content, "\n", "")}" - timestamp = "${timestamp()}" - }) -} diff --git a/module/move/willbe/template/deploy/deploy/hetzner/main.tf.hbs b/module/move/willbe/template/deploy/deploy/hetzner/main.tf.hbs new file mode 100644 index 0000000000..7f4ff24645 --- /dev/null +++ b/module/move/willbe/template/deploy/deploy/hetzner/main.tf.hbs @@ -0,0 +1,82 @@ +terraform { + # Specifies terraform API provider to use for `hcloud` + required_providers { + hcloud = { + source = "hetznercloud/hcloud" + version = "1.45.0" + } + } +} + +# Configures hcloud provider for deploy +provider "hcloud" { + # Hetzner API token + token = var.HCLOUD_TOKEN +} + +# Creates an SSH key used for redeploy +resource "hcloud_ssh_key" "redeploy" { + name = "{{docker_image_name}} redeploy key" + public_key = data.local_sensitive_file.ssh_public_key.content +} + +# Static IP for the instance +resource "hcloud_primary_ip" "primary_ip" { + name = "{{gcp_artifact_repo_name}}-ip" + datacenter = "hel1-dc2" + type = "ipv4" + assignee_type = "server" + auto_delete = false +} + +# Hetzner instance itself +resource "hcloud_server" "{{docker_image_name}}" { + name = "{{gcp_artifact_repo_name}}" + image = "ubuntu-22.04" + server_type = "cx22" + datacenter = "hel1-dc2" + + public_net { + ipv4_enabled = true + ipv4 = hcloud_primary_ip.primary_ip.id + ipv6_enabled = false + } + + ssh_keys = [ hcloud_ssh_key.redeploy.name ] + + # Startup script for the instance + # Installs docker, gcloud CLI, downloads docker images and starts the container + user_data = templatefile("${path.module}/../cloud-init.tpl", { + location = "${var.REGION}" + project_id = "${var.PROJECT_ID}" + repo_name = "${var.REPO_NAME}" + image_name = "${var.IMAGE_NAME}" + service_account_creds = "${replace(data.local_sensitive_file.service_account_creds.content, "\n", "")}" + }) +} + +resource "terraform_data" "redeploy" { + triggers_replace = timestamp() + + connection { + type = "ssh" + user = "root" + private_key = data.local_sensitive_file.ssh_private_key.content + host = hcloud_primary_ip.primary_ip.ip_address + } + + provisioner "file" { + source = "${path.module}/../redeploy.sh" + destination = "/tmp/redeploy.sh" + } + + provisioner "remote-exec" { + inline = [ + "#!/bin/bash", + "( tail -f -n1 /var/log/deploy-init.log & ) | grep -q 'Docker configuration file updated.'", + "source /etc/environment", + "chmod +x /tmp/redeploy.sh", + "/tmp/redeploy.sh" + ] + } +} diff --git a/module/move/willbe/template/deploy/deploy/hetzner/outputs.tf b/module/move/willbe/template/deploy/deploy/hetzner/outputs.tf.hbs similarity index 86% rename from module/move/willbe/template/deploy/deploy/hetzner/outputs.tf rename to module/move/willbe/template/deploy/deploy/hetzner/outputs.tf.hbs index f6d2ebd5e8..ba994920c7 100644 --- a/module/move/willbe/template/deploy/deploy/hetzner/outputs.tf +++ b/module/move/willbe/template/deploy/deploy/hetzner/outputs.tf.hbs @@ -1,5 +1,5 @@ locals { - ip = hcloud_server.uaconf.ipv4_address + ip = hcloud_server.{{docker_image_name}}.ipv4_address } # Output that we get after applying. diff --git a/module/move/willbe/template/deploy/deploy/hetzner/variables.tf b/module/move/willbe/template/deploy/deploy/hetzner/variables.tf index 92e5e44421..a6c27db413 100644 --- a/module/move/willbe/template/deploy/deploy/hetzner/variables.tf +++ b/module/move/willbe/template/deploy/deploy/hetzner/variables.tf @@ -25,5 +25,15 @@ variable "IMAGE_NAME" { # Google Cloud Platform credentials data "local_sensitive_file" "service_account_creds" { - filename = "${path.module}/../../key/service_account.json" + filename = "${path.module}/../../key/-service_account.json" +} + +# Private key for SSH connection +data "local_sensitive_file" "ssh_private_key" { + filename = "${path.module}/../../key/-rsa_ssh_key" +} + +# Public key for SSH connection +data "local_sensitive_file" "ssh_public_key" { + filename = "${path.module}/../../key/-rsa_ssh_key.pub" } diff --git a/module/move/willbe/template/deploy/deploy/redeploy.sh b/module/move/willbe/template/deploy/deploy/redeploy.sh new file mode 100644 index 0000000000..2ffd279cb8 --- /dev/null +++ b/module/move/willbe/template/deploy/deploy/redeploy.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +docker rmi ${DOCKER_IMAGE}_1 +docker rmi ${DOCKER_IMAGE}_2 + +docker pull ${DOCKER_IMAGE}_1 +docker pull ${DOCKER_IMAGE}_2 + +echo -n " +services: + one: + image: ${DOCKER_IMAGE}_1 + ports: + - "80:80" + two: + image: ${DOCKER_IMAGE}_2 + ports: + - "8080:8080" +" > docker-compose.yml + +docker compose up -d diff --git a/module/move/willbe/template/deploy/key/.gitignore b/module/move/willbe/template/deploy/key/.gitignore index 38b7807347..96870e1f6b 100644 --- a/module/move/willbe/template/deploy/key/.gitignore +++ b/module/move/willbe/template/deploy/key/.gitignore @@ -2,3 +2,4 @@ !.gitignore !*.md !pack.sh +-* diff --git a/module/move/willbe/template/deploy/key/Readme.md b/module/move/willbe/template/deploy/key/Readme.md index 53c085c1cd..c2d4e2551c 100644 --- a/module/move/willbe/template/deploy/key/Readme.md +++ b/module/move/willbe/template/deploy/key/Readme.md @@ -1,48 +1,82 @@ -# Deploy credentials +# Keys -A list of all keys you'd need to deploy your project on different hosts. +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. -- [Deploy credentials](#deploy-credentials) - - [Files](#files) - - [Env vars](#env-vars) +- [Keys](#keys) + - [Examples](#examples) + - [`-gcp.sh`](#-gcpsh) + - [`-hetzner.sh`](#-hetznersh) + - [`-aws.sh`](#-awssh) + - [How to Run](#how-to-run) - [Retrieving keys](#retrieving-keys) - [How to get `service_account.json`](#how-to-get-service_accountjson) - [How to get `SECRET_STATE_ARCHIVE_KEY`](#how-to-get-secret_state_archive_key) - [How to get `SECRET_CSP_HETZNER`](#how-to-get-secret_csp_hetzner) - [How to get `SECRET_AWS_ACCESS_KEY_ID` and `SECRET_AWS_ACCESS_KEY`](#how-to-get-secret_aws_access_key_id-and-secret_aws_access_key) -## Files -All secrets can be provided as files in current directory: +## Examples -- [service_account.json](./service_account.json) - default credentials for the service account to use in deployment. -- [`SECRET_STATE_ARCHIVE_KEY`](./SECRET_STATE_ARCHIVE_KEY) - [📃] base64 encoded AES256 key to encrypt and decrypt .tfstate files. -- [`SECRET_CSP_HETZNER`](./SECRET_CSP_HETZNER) - [📃] Hetzner token for deploying a server. -- [`SECRET_AWS_ACCESS_KEY_ID`](./SECRET_AWS_ACCESS_KEY_ID) - [📃] Access Key ID from AWS Credentials. Created at the same time as the Access Key itself. -- [`SECRET_AWS_ACCESS_KEY`](./SECRET_AWS_ACCESS_KEY) - [📃] Access Key for AWS API. Has to be accompanied with respectful Access Key ID. +### `-gcp.sh` -## Env vars +Contents example for the file `-gcp.sh`. This is a required configuration for all deploy targets. -Some secrets can be presented as an env var: +```bash +#!/bin/bash +CSP=gce +SECRET_STATE_ARCHIVE_KEY=qK1/4m60aZvclYi4bZFeBl8GxpyWcJ2iEevHN+uMy7w= -- [`SECRET_STATE_ARCHIVE_KEY`](./SECRET_STATE_ARCHIVE_KEY) - [📃] base64 encoded AES256 key to encrypt and decrypt .tfstate files. -- [`SECRET_CSP_HETZNER`](./SECRET_CSP_HETZNER) - [📃] Hetzner token for deploying a server. -- [`SECRET_AWS_ACCESS_KEY_ID`](./SECRET_AWS_ACCESS_KEY_ID) - [📃] Access Key ID from AWS Credentials. Created at the same time as the Access Key itself. -- [`SECRET_AWS_ACCESS_KEY`](./SECRET_AWS_ACCESS_KEY) - [📃] Access Key for AWS API. Has to be accompanied with respectful Access Key ID. +FILE_PATH="$( realpath -qms "${BASH_SOURCE[0]:-$PWD}" )" +DIR_PATH="${FILE_PATH%/*}" +head -c -1 << EOF > ${DIR_PATH}/-service_account.json +{ + // Your service_account information +} +EOF +``` -Env vars have a higher priority then the files. +- `CSP`: (Optional) Specifies deployment to GCE. +- `SECRET_STATE_ARCHIVE_KEY`: Base64 encoded AES256 key to encrypt and decrypt .tfstate files. +- `-service_account.json`: Default credentials for the service account to use in deployment. -For ENV [📃] secrets values can be placed in files in this directory for automatic exporting to env during deployment. +### `-hetzner.sh` -Example of a file that will be pulled to env vars: +Contents example for the file `-hetzner.sh`: -File name: `SECRET_CSP_HETZNER` -File contents: +```bash +CSP=hetzner +SECRET_CSP_HETZNER=your_token_here ``` -hetzner_token_123 + +- `CSP`: Specifies deployment to Hetzner. +- `SECRET_CSP_HETZNER`: Hetzner token for deploying a server. + +### `-aws.sh` + +Contents example for the file `-aws.sh`: + +```bash +CSP=aws +SECRET_AWS_ACCESS_KEY_ID=aws_credentials_here +SECRET_AWS_ACCESS_KEY=aws_credentials_here ``` -Will export a variable to env like so `SECRET_CSP_HETZNER=hetzner_token_123` +- `CSP`: Specifies deployment to AWS. +- `SECRET_AWS_ACCESS_KEY_ID`: Access Key ID from AWS Credentials. Created at the same time as the Access Key itself. +- `SECRET_AWS_ACCESS_KEY`: Access Key for AWS API. Has to be accompanied with respectful Access Key ID. + +## How to Run + +To apply these variables to your current shell session, use: + +```bash +. ./key/-gcp.sh +. ./key/-hetzner.sh +``` + +This command sources the script, making the variables available in your current session and allowing deployment to Hetzner. +Ensure `-env.sh` is in the `key` directory relative to your current location. ## Retrieving keys @@ -54,6 +88,8 @@ You can put your service account keys here for them to be used in deployment. Get your key from GCP panel at https://console.cloud.google.com/iam-admin/serviceaccounts +Created services account must have access to create, read, update, and delete Artifact Registry and Buckets services. + Service Account -> Keys -> Add Key -> Create new key -> JSON Default key name is `service_account.json`, this can be modified in the [Makefile](../Makefile). diff --git a/module/move/willbe/template/deploy/key/pack.sh b/module/move/willbe/template/deploy/key/pack.sh old mode 100755 new mode 100644 diff --git a/module/move/willbe/template/workflow/standard_rust_push.yml b/module/move/willbe/template/workflow/standard_rust_push.yml index d2fd96bae2..a243d1affe 100644 --- a/module/move/willbe/template/workflow/standard_rust_push.yml +++ b/module/move/willbe/template/workflow/standard_rust_push.yml @@ -64,20 +64,32 @@ jobs : - name: Build module run: cd ${{ steps.rootpath.outputs.path }} && cargo build && cd - - name: Audit the modules + id: audit run: make audit continue-on-error: true - name: Generate documentation for the modules + id: documentation run: make doc open=no manifest_path=${{ inputs.manifest_path }} continue-on-error: true - name: Lint the modules + id: lint run: make lint manifest_path=${{ inputs.manifest_path }} warnings=no continue-on-error: true - name: Check the modules + id: check run: make check manifest_path=${{ inputs.manifest_path }} continue-on-error: true - name: Check the modules dependencies + id: udeps run: cargo +nightly udeps --all-targets --manifest-path ${{ inputs.manifest_path }} continue-on-error: true + # Added IDs for each step in the test job: This allows referencing the result of each step later. + # + # "Check for errors" step: Now checks the outcome status for each step. + # If any of them have a value of 'failure', this step will fail the job by returning exit 1. + - name: Check for errors + if: steps.audit.outcome != 'success' || steps.documentation.outcome != 'success' || steps.lint.outcome != 'success' || steps.check.outcome != 'success' || steps.udeps.outcome != 'success' + run: exit 1 # release: # if: contains( inputs.commit_message, '+test' ) || contains( inputs.commit_message, 'merge' ) @@ -125,6 +137,9 @@ jobs : # run: cargo miri test --manifest-path ${{ inputs.manifest_path }} will_test : +# This section ensures that `job` `will_test` will only be executed after `checkmate` and if `checkmate` fails, no tests will be run. + needs : + - checkmate if : contains( inputs.commit_message, '+test' ) || inputs.commiter_username == 'web-flow' || startsWith( inputs.commit_message, 'merge' ) concurrency : group : standard_rust_push_${{ inputs.module_name }}_${{ github.ref }}_${{ matrix.os }} diff --git a/module/move/willbe/tests/asset/single_module/Cargo.toml b/module/move/willbe/tests/asset/single_module/Cargo.toml index 7e5912d446..a78145c170 100644 --- a/module/move/willbe/tests/asset/single_module/Cargo.toml +++ b/module/move/willbe/tests/asset/single_module/Cargo.toml @@ -5,6 +5,7 @@ members = [ ] [workspace.metadata] +workspace_name = "single_module" master_branch = "test_branch" project_name = "test" repo_url = "https://github.com/Username/test" diff --git a/module/move/willbe/tests/inc/action_tests/cicd_renew.rs b/module/move/willbe/tests/inc/action_tests/cicd_renew.rs index ffbbe5b570..ee2bb2848a 100644 --- a/module/move/willbe/tests/inc/action_tests/cicd_renew.rs +++ b/module/move/willbe/tests/inc/action_tests/cicd_renew.rs @@ -90,7 +90,7 @@ fn default_case() }; // Act - _ = action::cicd_renew( &temp ).unwrap(); + () = action::cicd_renew::action( &temp ).unwrap(); dbg!( &file_path ); // Assert diff --git a/module/move/willbe/tests/inc/action_tests/crate_doc_test.rs b/module/move/willbe/tests/inc/action_tests/crate_doc_test.rs new file mode 100644 index 0000000000..5ecc5638c5 --- /dev/null +++ b/module/move/willbe/tests/inc/action_tests/crate_doc_test.rs @@ -0,0 +1,160 @@ +// module/move/willbe/tests/inc/action_tests/crate_doc_test.rs +use super::*; +use crate::the_module::{ action, CrateDir, path::AbsolutePath, action::CrateDocError, Workspace }; +use crate::inc::helper::ProjectBuilder; +use assert_fs::prelude::*; +use predicates::prelude::*; +use std:: +{ + path::PathBuf, + fs as std_fs, + env, // Import env to get current_dir +}; + +#[ test ] +fn basic_test() +{ + // Arrange + let temp = assert_fs::TempDir::new().unwrap(); + let crate_name = "dummy_crate"; + let project = ProjectBuilder::new( crate_name ) + .toml_file( "" ) + .lib_file( "/// A dummy function.\npub fn dummy() {}" ) + .build( &temp ) + .unwrap(); + + let crate_dir = CrateDir::try_from( project.as_path() ) + .expect( "Failed to create CrateDir" ); + let workspace = Workspace::try_from( crate_dir.clone() ) + .expect( "Failed to load workspace" ); + // Expected output is now in workspace target/doc + let expected_output_path = workspace + .target_directory() + .join( "doc" ) + .join( format!( "{}_doc.md", crate_name ) ); + + // Act + let result = action::crate_doc::doc( &workspace, &crate_dir, None ); + + // Assert + assert!( result.is_ok(), "Action failed: {:?}", result.err() ); + let report = result.unwrap(); + + assert!( report.status.contains( "successfully" ), "Report status is not successful: {}", report.status ); + assert_eq!( report.crate_dir.as_ref(), Some( &crate_dir ) ); + assert_eq!( report.output_path.as_ref(), Some( &expected_output_path ) ); + + // Check file existence and content in the workspace target dir + assert!( expected_output_path.is_file(), "Output file not found at expected location: {}", expected_output_path.display() ); + let content = std_fs::read_to_string( &expected_output_path ).expect( "Failed to read output file" ); + + assert!( !content.is_empty(), "Output file is empty" ); + assert!( content.contains( "# Crate Documentation" ), "Output file missing main header" ); + assert!( content.contains( "# Module `dummy_crate`" ), "Output file missing module header" ); + assert!( content.contains( "## Functions" ), "Output file missing Functions section" ); + assert!( content.contains( "### Function `dummy`" ), "Output file missing function header" ); + assert!( content.contains( "A dummy function." ), "Output file missing function doc comment" ); +} + +#[ test ] +fn output_option_test() +{ + // Arrange + let temp = assert_fs::TempDir::new().unwrap(); + let crate_name = "output_option_crate"; + let project = ProjectBuilder::new( crate_name ) + .toml_file( "" ) + .lib_file( "/// Another function.\npub fn another() {}" ) + .build( &temp ) + .unwrap(); + + let crate_dir = CrateDir::try_from( project.as_path() ) + .expect( "Failed to create CrateDir" ); + let workspace = Workspace::try_from( crate_dir.clone() ) + .expect( "Failed to load workspace" ); + // Define a custom output path relative to the CWD + let custom_output_rel_path = PathBuf::from( "docs/custom_doc.md" ); + // Expected path is resolved relative to CWD where the test runs + let expected_output_abs_path = env::current_dir().unwrap().join( &custom_output_rel_path ); + // Ensure the target directory exists for the test assertion later + std_fs::create_dir_all( expected_output_abs_path.parent().unwrap() ).unwrap(); + + + // Act + let result = action::crate_doc::doc( &workspace, &crate_dir, Some( custom_output_rel_path.clone() ) ); + + // Assert + assert!( result.is_ok(), "Action failed: {:?}", result.err() ); + let report = result.unwrap(); + + assert!( report.status.contains( "successfully" ), "Report status is not successful: {}", report.status ); + assert_eq!( report.crate_dir.as_ref(), Some( &crate_dir ) ); + // Check if the report contains the correct absolute output path resolved from CWD + assert_eq!( report.output_path.as_ref(), Some( &expected_output_abs_path ) ); + + // Check file existence at the custom path (relative to CWD) and content + assert!( expected_output_abs_path.is_file(), "Output file not found at expected location: {}", expected_output_abs_path.display() ); + let content = std_fs::read_to_string( &expected_output_abs_path ).expect( "Failed to read output file" ); + assert!( !content.is_empty(), "Output file is empty" ); + assert!( content.contains( "# Crate Documentation" ), "Output file missing main header" ); + assert!( content.contains( &format!( "# Module `{}`", crate_name ) ), "Output file missing module header" ); + assert!( content.contains( "### Function `another`" ), "Output file missing function header" ); + assert!( content.contains( "Another function." ), "Output file missing function doc comment" ); + + // Ensure the default file (in target/doc) was NOT created + assert!( !workspace.target_directory().join("doc").join(format!( "{}_doc.md", crate_name )).exists() ); + + // Clean up the created file/directory relative to CWD + if expected_output_abs_path.exists() { std_fs::remove_file( &expected_output_abs_path ).unwrap(); } + if expected_output_abs_path.parent().unwrap().read_dir().unwrap().next().is_none() + { + std_fs::remove_dir( expected_output_abs_path.parent().unwrap() ).unwrap(); + } +} + +#[ test ] +fn non_crate_dir_test() +{ + // Arrange + let temp = assert_fs::TempDir::new().unwrap(); + temp.child( "not_a_dir" ).touch().unwrap(); + let empty_dir_path = temp.path().join("empty_dir"); + std_fs::create_dir( &empty_dir_path ).unwrap(); + + // Attempt to create CrateDir from the empty directory path + let crate_dir_result = CrateDir::try_from( empty_dir_path.as_path() ); + assert!( crate_dir_result.is_err(), "CrateDir::try_from should fail for a directory without Cargo.toml" ); +} + +#[ test ] +fn cargo_doc_fail_test() +{ + // Arrange + let temp = assert_fs::TempDir::new().unwrap(); + let crate_name = "fail_crate"; + let project = ProjectBuilder::new( crate_name ) + .toml_file( "" ) + .lib_file( "pub fn bad_code() -> { }" ) // Syntax error + .build( &temp ) + .unwrap(); + + let crate_dir = CrateDir::try_from( project.as_path() ) + .expect( "Failed to create CrateDir" ); + let workspace = Workspace::try_from( crate_dir.clone() ) + .expect( "Failed to load workspace" ); + + // Act + let result = action::crate_doc::doc( &workspace, &crate_dir, None ); + + // Assert + assert!( result.is_err(), "Action should fail when cargo doc fails" ); + let ( report, error ) = result.err().unwrap(); + + assert!( matches!( error, CrateDocError::Command( _ ) ), "Expected Command error, got {:?}", error ); + assert!( report.status.contains( &format!( "Failed during `cargo doc` execution for `{}`.", crate_name ) ), "Report status mismatch: {}", report.status ); + assert!( report.cargo_doc_report.is_some() ); + assert!( report.cargo_doc_report.unwrap().error.is_err(), "Cargo doc report should indicate an error" ); + + // Check that no output file was created (check default location) + assert!( !workspace.target_directory().join("doc").join(format!( "{}_doc.md", crate_name )).exists() ); +} \ No newline at end of file diff --git a/module/move/willbe/tests/inc/action_tests/features.rs b/module/move/willbe/tests/inc/action_tests/features.rs index 37a4b63cae..ea0e4e80a0 100644 --- a/module/move/willbe/tests/inc/action_tests/features.rs +++ b/module/move/willbe/tests/inc/action_tests/features.rs @@ -24,7 +24,7 @@ fn package_no_features() .form(); // Act - let report = willbe::action::features( options ).unwrap().to_string(); + let report = willbe::action::features::orphan::features( options ).unwrap().to_string(); // Assert assert!( report.contains( @@ -43,7 +43,7 @@ fn package_features() .form(); // Act - let report = willbe::action::features( options ).unwrap().to_string(); + let report = willbe::action::features::orphan::features( options ).unwrap().to_string(); // Assert assert!( report.contains( @@ -66,7 +66,7 @@ fn package_features_with_features_deps() .form(); // Act - let report = willbe::action::features( options ).unwrap().to_string(); + let report = willbe::action::features::orphan::features( options ).unwrap().to_string(); // Assert assert!( report.contains( @@ -89,7 +89,7 @@ fn workspace_no_features() .form(); // Act - let report = willbe::action::features( options ).unwrap().to_string(); + let report = willbe::action::features::orphan::features( options ).unwrap().to_string(); // Assert assert!( report.contains( @@ -118,7 +118,7 @@ fn workspace_features() .form(); // Act - let report = willbe::action::features( options ).unwrap().to_string(); + let report = willbe::action::features::orphan::features( options ).unwrap().to_string(); // Assert assert!( report.contains( @@ -156,7 +156,7 @@ fn workspace_features_with_features_deps() .form(); // Act - let report = willbe::action::features( options ).unwrap().to_string(); + let report = willbe::action::features::orphan::features( options ).unwrap().to_string(); // Assert assert!( report.contains( diff --git a/module/move/willbe/tests/inc/action_tests/list/data.rs b/module/move/willbe/tests/inc/action_tests/list/data.rs index 423baf654c..9100cf5dfb 100644 --- a/module/move/willbe/tests/inc/action_tests/list/data.rs +++ b/module/move/willbe/tests/inc/action_tests/list/data.rs @@ -44,7 +44,7 @@ mod chain_of_three_packages .form(); // Act - let output = action::list( args ).unwrap(); + let output = action::list_all( args ).unwrap(); // Assert let ListReport::Tree( trees ) = &output else { panic!( "Expected `Tree` format, but found another" ) }; @@ -85,7 +85,7 @@ mod chain_of_three_packages .form(); // Act - let output = action::list( args ).unwrap(); + let output = action::list_all( args ).unwrap(); // Assert let ListReport::List( names ) = &output else { panic!("Expected `Topological` format, but found another") }; @@ -106,7 +106,7 @@ mod chain_of_three_packages .form(); // Act - let output = action::list( args ).unwrap(); + let output = action::list_all( args ).unwrap(); // Assert let ListReport::List( names ) = &output else { panic!( "Expected `Topological` format, but found another" ) }; @@ -145,7 +145,7 @@ mod package_with_remote_dependency .form(); // Act - let output = action::list( args ).unwrap(); + let output = action::list_all( args ).unwrap(); // Assert let ListReport::Tree( trees ) = &output else { panic!( "Expected `Tree` format, but found another" ) }; @@ -183,7 +183,7 @@ mod package_with_remote_dependency .form(); // Act - let output = action::list( args ).unwrap(); + let output = action::list_all( args ).unwrap(); // Assert let ListReport::List( names ) = &output else { panic!( "Expected `Topological` format, but found another" ) }; @@ -208,7 +208,7 @@ mod package_with_remote_dependency .form(); // Act - let output = action::list( args ).unwrap(); + let output = action::list_all( args ).unwrap(); // Assert let ListReport::List( names ) = &output else { panic!( "Expected `Topological` format, but found another" ) }; @@ -242,7 +242,7 @@ mod workspace_with_cyclic_dependency .form(); // Act - let output = action::list( args ).unwrap(); + let output = action::list_all( args ).unwrap(); // Assert let ListReport::Tree( trees ) = &output else { panic!( "Expected `Tree` format, but found another" ) }; @@ -304,7 +304,7 @@ mod workspace_with_cyclic_dependency .form(); // Act - let output = action::list( args ); + let output = action::list_all( args ); // Assert diff --git a/module/move/willbe/tests/inc/action_tests/list/format.rs b/module/move/willbe/tests/inc/action_tests/list/format.rs index 84b7f32a96..62fbd21924 100644 --- a/module/move/willbe/tests/inc/action_tests/list/format.rs +++ b/module/move/willbe/tests/inc/action_tests/list/format.rs @@ -56,13 +56,13 @@ fn node_with_depth_two_leaves_stop_spacer() dev_dependencies : vec![], build_dependencies : vec![], }; - let expected = r#" + let expected = r" node ├─ sub_node1 │ └─ sub_sub_node1 └─ sub_node2 └─ sub_sub_node2 -"#.trim(); +".trim(); let printer = TreePrinter::new( &node ); let actual = printer.display_with_spacer( "" ).unwrap(); @@ -116,12 +116,12 @@ fn node_with_depth_two_leaves() dev_dependencies : vec![], build_dependencies : vec![], }; - let expected = r#" + let expected = r" node ├─ sub_node1 │ └─ sub_sub_node └─ sub_node2 -"#.trim(); +".trim(); let printer = TreePrinter::new( &node ); let actual = printer.display_with_spacer( "" ).unwrap(); @@ -162,11 +162,11 @@ fn node_with_depth_one_leaf() dev_dependencies : vec![], build_dependencies : vec![], }; - let expected = r#" + let expected = r" node └─ sub_node └─ sub_sub_node -"#.trim(); +".trim(); let printer = TreePrinter::new( &node ); let actual = printer.display_with_spacer( "" ).unwrap(); @@ -211,12 +211,12 @@ fn node_with_build_dependencies_tree_with_two_leaves() } ], }; - let expected = r#" + let expected = r" node [build-dependencies] ├─ build_sub_node1 └─ build_sub_node2 -"#.trim(); +".trim(); let printer = TreePrinter::new( &node ); let actual = printer.display_with_spacer( "" ).unwrap(); @@ -250,11 +250,11 @@ fn node_with_build_dependencies_tree_with_one_leaf() } ], }; - let expected = r#" + let expected = r" node [build-dependencies] └─ build_sub_node -"#.trim(); +".trim(); let printer = TreePrinter::new( &node ); let actual = printer.display_with_spacer( "" ).unwrap(); @@ -299,12 +299,12 @@ fn node_with_dev_dependencies_tree_with_two_leaves() ], build_dependencies : vec![], }; - let expected = r#" + let expected = r" node [dev-dependencies] ├─ dev_sub_node1 └─ dev_sub_node2 -"#.trim(); +".trim(); let printer = TreePrinter::new( &node ); let actual = printer.display_with_spacer( "" ).unwrap(); @@ -338,11 +338,11 @@ fn node_with_dev_dependencies_tree_with_one_leaf() ], build_dependencies : vec![], }; - let expected = r#" + let expected = r" node [dev-dependencies] └─ dev_sub_node -"#.trim(); +".trim(); let printer = TreePrinter::new( &node ); let actual = printer.display_with_spacer( "" ).unwrap(); @@ -387,11 +387,11 @@ fn node_with_dependencies_tree_with_two_leaves() dev_dependencies : vec![], build_dependencies : vec![], }; - let expected = r#" + let expected = r" node ├─ sub_node1 └─ sub_node2 -"#.trim(); +".trim(); let printer = TreePrinter::new( &node ); let actual = printer.display_with_spacer( "" ).unwrap(); @@ -423,10 +423,10 @@ fn node_with_dependency_tree_with_one_leaf() dev_dependencies : vec![], build_dependencies : vec![], }; - let expected = r#" + let expected = r" node └─ sub_node -"#.trim(); +".trim(); let printer = TreePrinter::new( &node ); let actual = printer.display_with_spacer( "" ).unwrap(); diff --git a/module/move/willbe/tests/inc/action_tests/main_header.rs b/module/move/willbe/tests/inc/action_tests/main_header.rs index 82f1b89fba..6f65b44495 100644 --- a/module/move/willbe/tests/inc/action_tests/main_header.rs +++ b/module/move/willbe/tests/inc/action_tests/main_header.rs @@ -25,7 +25,7 @@ fn tag_shout_stay() let temp = arrange( "single_module" ); // Act - _ = action::readme_header_renew( AbsolutePath::try_from( temp.path() ).unwrap() ).unwrap(); + _ = action::readme_header_renew::orphan::readme_header_renew( AbsolutePath::try_from( temp.path() ).unwrap() ).unwrap(); let mut file = std::fs::File::open( temp.path().join( "Readme.md" ) ).unwrap(); @@ -45,7 +45,7 @@ fn branch_cell() let temp = arrange( "single_module" ); // Act - _ = action::readme_header_renew( AbsolutePath::try_from( temp.path() ).unwrap() ).unwrap(); + _ = action::readme_header_renew::orphan::readme_header_renew( AbsolutePath::try_from( temp.path() ).unwrap() ).unwrap(); let mut file = std::fs::File::open( temp.path().join( "Readme.md" ) ).unwrap(); @@ -64,7 +64,7 @@ fn discord_cell() let temp = arrange( "single_module" ); // Act - _ = action::readme_header_renew( AbsolutePath::try_from( temp.path() ).unwrap() ).unwrap(); + _ = action::readme_header_renew::orphan::readme_header_renew( AbsolutePath::try_from( temp.path() ).unwrap() ).unwrap(); let mut file = std::fs::File::open( temp.path().join( "Readme.md" ) ).unwrap(); @@ -83,7 +83,7 @@ fn gitpod_cell() let temp = arrange( "single_module" ); // Act - _ = action::readme_header_renew( AbsolutePath::try_from( temp.path() ).unwrap() ).unwrap(); + _ = action::readme_header_renew::orphan::readme_header_renew( AbsolutePath::try_from( temp.path() ).unwrap() ).unwrap(); let mut file = std::fs::File::open( temp.path().join( "Readme.md" ) ).unwrap(); @@ -102,7 +102,7 @@ fn docs_cell() let temp = arrange( "single_module" ); // Act - _ = action::readme_header_renew( AbsolutePath::try_from( temp.path() ).unwrap() ).unwrap(); + _ = action::readme_header_renew::orphan::readme_header_renew( AbsolutePath::try_from( temp.path() ).unwrap() ).unwrap(); let mut file = std::fs::File::open( temp.path().join( "Readme.md" ) ).unwrap(); @@ -121,7 +121,7 @@ fn without_fool_config() let temp = arrange( "single_module_without_master_branch_and_discord" ); // Act - _ = action::readme_header_renew( AbsolutePath::try_from( temp.path() ).unwrap() ).unwrap(); + _ = action::readme_header_renew::orphan::readme_header_renew( AbsolutePath::try_from( temp.path() ).unwrap() ).unwrap(); let mut file = std::fs::File::open( temp.path().join( "Readme.md" ) ).unwrap(); @@ -141,13 +141,13 @@ fn idempotency() let temp = arrange( "single_module" ); // Act - _ = action::readme_header_renew( AbsolutePath::try_from( temp.path() ).unwrap() ).unwrap(); + _ = action::readme_header_renew::orphan::readme_header_renew( AbsolutePath::try_from( temp.path() ).unwrap() ).unwrap(); let mut file = std::fs::File::open( temp.path().join( "Readme.md" ) ).unwrap(); let mut actual1 = String::new(); _ = file.read_to_string( &mut actual1 ).unwrap(); drop( file ); - _ = action::readme_header_renew( AbsolutePath::try_from( temp.path() ).unwrap() ).unwrap(); + _ = action::readme_header_renew::orphan::readme_header_renew( AbsolutePath::try_from( temp.path() ).unwrap() ).unwrap(); let mut file = std::fs::File::open( temp.path().join( "Readme.md" ) ).unwrap(); let mut actual2 = String::new(); _ = file.read_to_string( &mut actual2 ).unwrap(); @@ -164,5 +164,5 @@ fn without_needed_config() // Arrange let temp = arrange( "variadic_tag_configurations" ); // Act - _ = action::readme_header_renew( AbsolutePath::try_from( temp.path() ).unwrap() ).unwrap(); + _ = action::readme_header_renew::orphan::readme_header_renew( AbsolutePath::try_from( temp.path() ).unwrap() ).unwrap(); } \ No newline at end of file diff --git a/module/move/willbe/tests/inc/action_tests/mod.rs b/module/move/willbe/tests/inc/action_tests/mod.rs index ae10e6d259..072ea810f8 100644 --- a/module/move/willbe/tests/inc/action_tests/mod.rs +++ b/module/move/willbe/tests/inc/action_tests/mod.rs @@ -6,7 +6,9 @@ pub mod readme_health_table_renew; pub mod readme_modules_headers_renew; pub mod test; pub mod cicd_renew; +pub mod crate_doc_test; pub mod workspace_renew; // aaa : for Petro : sort -// aaa : sorted & renamed \ No newline at end of file +// aaa : sorted & renamed +// qqq : ??? : it's not sorted! diff --git a/module/move/willbe/tests/inc/action_tests/readme_health_table_renew.rs b/module/move/willbe/tests/inc/action_tests/readme_health_table_renew.rs index cce1e9065a..de6057a8ba 100644 --- a/module/move/willbe/tests/inc/action_tests/readme_health_table_renew.rs +++ b/module/move/willbe/tests/inc/action_tests/readme_health_table_renew.rs @@ -23,7 +23,7 @@ fn without_any_toml_configurations_test() // Arrange let temp = arrange( "without_any_toml_configurations" ); // Act - _ = action::readme_health_table_renew( &temp ).unwrap(); + () = action::readme_health_table_renew::orphan::readme_health_table_renew( &temp ).unwrap(); } #[ test ] @@ -33,7 +33,7 @@ fn tags_should_stay() let temp = arrange( "without_module_toml_configurations" ); // Act - _ = action::readme_health_table_renew( &temp ).unwrap(); + () = action::readme_health_table_renew::orphan::readme_health_table_renew( &temp ).unwrap(); // Assert let mut file = std::fs::File::open( temp.path().join( "readme.md" ) ).unwrap(); @@ -52,7 +52,7 @@ fn stability_experimental_by_default() let temp = arrange( "without_module_toml_configurations" ); // Act - _ = action::readme_health_table_renew( &temp ).unwrap(); + () = action::readme_health_table_renew::orphan::readme_health_table_renew( &temp ).unwrap(); // Assert let mut file = std::fs::File::open( temp.path().join( "readme.md" ) ).unwrap(); @@ -70,7 +70,7 @@ fn stability_and_repository_from_module_toml() let temp = arrange( "without_workspace_toml_configurations" ); // Act - _ = action::readme_health_table_renew( &temp ).unwrap(); + () = action::readme_health_table_renew::orphan::readme_health_table_renew( &temp ).unwrap(); // Assert let mut file = std::fs::File::open( temp.path().join( "readme.md" ) ).unwrap(); @@ -97,17 +97,17 @@ fn variadic_tag_configuration_test() let with_gitpod_only = "-->\r| Module | Sample |\n|--------|:------:|\n"; - let expected = vec![ explicit_all_true_flag, all_true_flag, with_stability_only, with_branches_only, with_docs_only, with_gitpod_only ]; + let expected = [explicit_all_true_flag, all_true_flag, with_stability_only, with_branches_only, with_docs_only, with_gitpod_only]; let temp = arrange( "variadic_tag_configurations" ); // Act - _ = action::readme_health_table_renew( &temp ).unwrap(); + () = action::readme_health_table_renew::orphan::readme_health_table_renew( &temp ).unwrap(); // Assert let mut file = std::fs::File::open( temp.path().join( "readme.md" ) ).unwrap(); let mut content = String::new(); _ = file.read_to_string( &mut content ).unwrap(); - for ( index, actual ) in content.split( "###" ).into_iter().enumerate() + for ( index, actual ) in content.split( "###" ).enumerate() { assert!( actual.trim().contains( expected[ index ] ) ); } @@ -121,7 +121,7 @@ fn module_cell() let temp = arrange( "full_config" ); // Act - _ = action::readme_health_table_renew( &temp ).unwrap(); + () = action::readme_health_table_renew::orphan::readme_health_table_renew( &temp ).unwrap(); // Assert let mut file = std::fs::File::open( temp.path().join( "readme.md" ) ).unwrap(); @@ -139,7 +139,7 @@ fn stability_cell() let temp = arrange( "full_config" ); // Act - _ = action::readme_health_table_renew( &temp ).unwrap(); + () = action::readme_health_table_renew::orphan::readme_health_table_renew( &temp ).unwrap(); // Assert let mut file = std::fs::File::open( temp.path().join( "readme.md" ) ).unwrap(); @@ -157,7 +157,7 @@ fn branches_cell() let temp = arrange( "full_config" ); // Act - _ = action::readme_health_table_renew( &temp ).unwrap(); + () = action::readme_health_table_renew::orphan::readme_health_table_renew( &temp ).unwrap(); // Assert let mut file = std::fs::File::open( temp.path().join( "readme.md" ) ).unwrap(); @@ -175,7 +175,7 @@ fn docs_cell() let temp = arrange( "full_config" ); // Act - _ = action::readme_health_table_renew( &temp ).unwrap(); + () = action::readme_health_table_renew::orphan::readme_health_table_renew( &temp ).unwrap(); // Assert let mut file = std::fs::File::open( temp.path().join( "readme.md" ) ).unwrap(); @@ -193,7 +193,7 @@ fn sample_cell() let temp = arrange( "full_config" ); // Act - _ = action::readme_health_table_renew( &temp ).unwrap(); + () = action::readme_health_table_renew::orphan::readme_health_table_renew( &temp ).unwrap(); // Assert let mut file = std::fs::File::open( temp.path().join( "readme.md" ) ).unwrap(); diff --git a/module/move/willbe/tests/inc/action_tests/readme_modules_headers_renew.rs b/module/move/willbe/tests/inc/action_tests/readme_modules_headers_renew.rs index db82f365ba..63c9ef91e4 100644 --- a/module/move/willbe/tests/inc/action_tests/readme_modules_headers_renew.rs +++ b/module/move/willbe/tests/inc/action_tests/readme_modules_headers_renew.rs @@ -32,7 +32,9 @@ fn tags_should_stay() let temp = arrange( "single_module" ); // Act - _ = action::readme_modules_headers_renew( CrateDir::try_from( temp.path() ).unwrap() ).unwrap(); + _ = action::readme_modules_headers_renew::readme_modules_headers_renew( CrateDir::try_from( temp.path() ).unwrap() ).unwrap(); + // _ = action::main_header::action( CrateDir::try_from( temp.path() ).unwrap() ).unwrap(); + let mut file = std::fs::File::open( temp.path().join( "test_module" ).join( "Readme.md" ) ).unwrap(); let mut actual = String::new(); @@ -51,7 +53,7 @@ fn default_stability() let temp = arrange( "single_module" ); // Act - _ = action::readme_modules_headers_renew( CrateDir::try_from( temp.path() ).unwrap() ).unwrap(); + _ = action::readme_modules_headers_renew::readme_modules_headers_renew( CrateDir::try_from( temp.path() ).unwrap() ).unwrap(); let mut file = std::fs::File::open( temp.path().join( "test_module" ).join( "Readme.md" ) ).unwrap(); let mut actual = String::new(); @@ -60,7 +62,7 @@ fn default_stability() // Assert assert!( actual.contains( "[![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)" ) ); - assert!( !actual.contains( "|" ) ); + assert!( !actual.contains( '|' ) ); // fix clippy } #[ test ] @@ -70,7 +72,7 @@ fn docs() let temp = arrange( "single_module" ); // Act - _ = action::readme_modules_headers_renew( CrateDir::try_from( temp.path() ).unwrap() ).unwrap(); + _ = action::readme_modules_headers_renew::readme_modules_headers_renew( CrateDir::try_from( temp.path() ).unwrap() ).unwrap(); let mut file = std::fs::File::open( temp.path().join( "test_module" ).join( "Readme.md" ) ).unwrap(); let mut actual = String::new(); @@ -88,7 +90,7 @@ fn no_gitpod() let temp = arrange("single_module"); // Act - _ = action::readme_modules_headers_renew( CrateDir::try_from( temp.path() ).unwrap() ).unwrap(); + _ = action::readme_modules_headers_renew::readme_modules_headers_renew( CrateDir::try_from( temp.path() ).unwrap() ).unwrap(); let mut file = std::fs::File::open(temp.path().join("test_module").join("Readme.md")).unwrap(); let mut actual = String::new(); @@ -105,7 +107,7 @@ fn with_gitpod() let temp = arrange( "single_module_with_example" ); // Act - _ = action::readme_modules_headers_renew( CrateDir::try_from( temp.path() ).unwrap() ).unwrap(); + _ = action::readme_modules_headers_renew::readme_modules_headers_renew( CrateDir::try_from( temp.path() ).unwrap() ).unwrap(); let mut file = std::fs::File::open( temp.path().join( "module" ).join( "test_module" ).join( "Readme.md" ) ).unwrap(); let mut actual = String::new(); @@ -123,7 +125,7 @@ fn discord() let temp = arrange( "single_module" ); // Act - _ = action::readme_modules_headers_renew( CrateDir::try_from( temp.path() ).unwrap() ).unwrap(); + _ = action::readme_modules_headers_renew::readme_modules_headers_renew(CrateDir::try_from(temp.path()).unwrap()).unwrap(); let mut file = std::fs::File::open( temp.path().join( "test_module" ).join( "Readme.md" ) ).unwrap(); let mut actual = String::new(); @@ -141,7 +143,7 @@ fn status() let temp = arrange( "single_module" ); // Act - _ = action::readme_modules_headers_renew( CrateDir::try_from( temp.path() ).unwrap() ).unwrap(); + _ = action::readme_modules_headers_renew::readme_modules_headers_renew( CrateDir::try_from( temp.path() ).unwrap() ).unwrap(); let mut file = std::fs::File::open( temp.path().join( "test_module" ).join( "Readme.md" ) ).unwrap(); let mut actual = String::new(); @@ -159,13 +161,13 @@ fn idempotency() let temp = arrange( "single_module" ); // Act - _ = action::readme_modules_headers_renew( CrateDir::try_from( temp.path() ).unwrap() ).unwrap(); + _ = action::readme_modules_headers_renew::readme_modules_headers_renew( CrateDir::try_from( temp.path() ).unwrap() ).unwrap(); let mut file = std::fs::File::open( temp.path().join( "test_module" ).join( "Readme.md" ) ).unwrap(); let mut actual1 = String::new(); _ = file.read_to_string( &mut actual1 ).unwrap(); drop( file ); - _ = action::readme_modules_headers_renew( CrateDir::try_from( temp.path() ).unwrap() ).unwrap(); + _ = action::readme_modules_headers_renew::readme_modules_headers_renew( CrateDir::try_from( temp.path() ).unwrap() ).unwrap(); let mut file = std::fs::File::open( temp.path().join( "test_module" ).join( "Readme.md" ) ).unwrap(); let mut actual2 = String::new(); _ = file.read_to_string( &mut actual2 ).unwrap(); @@ -180,7 +182,7 @@ fn with_many_members_and_varius_config() { let temp = arrange( "three_packages" ); - _ = action::readme_modules_headers_renew( CrateDir::try_from( temp.path() ).unwrap() ).unwrap(); + _ = action::readme_modules_headers_renew::readme_modules_headers_renew( CrateDir::try_from( temp.path() ).unwrap() ).unwrap(); let mut file_b = std::fs::File::open( temp.path().join( "b" ).join( "Readme.md" ) ).unwrap(); let mut file_c = std::fs::File::open( temp.path().join( "c" ).join( "Readme.md" ) ).unwrap(); @@ -207,5 +209,5 @@ fn without_needed_config() let temp = arrange( "variadic_tag_configurations" ); // Act - _ = action::readme_modules_headers_renew( CrateDir::try_from( temp.path() ).unwrap() ).unwrap(); + _ = action::main_header::action( CrateDir::try_from( temp.path() ).unwrap() ).unwrap(); } diff --git a/module/move/willbe/tests/inc/action_tests/test.rs b/module/move/willbe/tests/inc/action_tests/test.rs index 6d7d7898d9..476f92f918 100644 --- a/module/move/willbe/tests/inc/action_tests/test.rs +++ b/module/move/willbe/tests/inc/action_tests/test.rs @@ -1,28 +1,24 @@ use super::*; -use the_module::*; +// qqq : for Bohdan : bad. don't import the_module::* use inc::helper:: { ProjectBuilder, WorkspaceBuilder, - // BINARY_NAME, }; use collection::BTreeSet; -// use std:: -// { -// fs::{ self, File }, -// io::Write, -// }; -// use path::{ Path, PathBuf }; use assert_fs::TempDir; -use action::test::{ test, TestsCommandOptions }; -use channel::*; -use optimization::*; +use the_module::action::test::{ test, TestsCommandOptions }; +use the_module::channel::*; +// use the_module::optimization::*; +use the_module::optimization::{ self, Optimization }; +use the_module::AbsolutePath; // qqq : for Petro : no astersisks import use willbe::test::TestVariant; + #[ test ] // if the test fails => the report is returned as an error ( Err(Report) ) fn fail_test() @@ -32,13 +28,13 @@ fn fail_test() let project = ProjectBuilder::new( "fail_test" ) .toml_file( "[features]\nenabled = []" ) - .test_file( r#" - #[test] + .test_file( r" + #[ test ] fn should_fail() { panic!() } - "#) + ") .build( temp ) .unwrap(); let abs = AbsolutePath::try_from( project ).unwrap(); @@ -51,7 +47,7 @@ fn fail_test() .form(); let rep = test( args, false ).unwrap_err().0; - println!( "========= OUTPUT =========\n{}\n==========================", rep ); + println!( "========= OUTPUT =========\n{rep}\n==========================" ); let no_features = rep .failure_reports[ 0 ] @@ -72,12 +68,12 @@ fn fail_build() let project = ProjectBuilder::new( "fail_build" ) .lib_file( "compile_error!( \"achtung\" );" ) .toml_file( "[features]\nenabled = []" ) - .test_file( r#" - #[test] + .test_file( r" + #[ test ] fn should_pass() { assert!(true); } - "#) + ") .build( temp ) .unwrap(); let abs = AbsolutePath::try_from( project ).unwrap(); @@ -90,7 +86,7 @@ fn fail_build() .form(); let rep = test( args, false ).unwrap_err().0; - println!( "========= OUTPUT =========\n{}\n==========================", rep ); + println!( "========= OUTPUT =========\n{rep}\n==========================" ); let no_features = rep .failure_reports[ 0 ] @@ -109,30 +105,30 @@ fn call_from_workspace_root() let fail_project = ProjectBuilder::new( "fail_test" ) .toml_file( "[features]\nenabled = []" ) - .test_file( r#" - #[test] + .test_file( r" + #[ test ] fn should_fail123() { panic!() } - "#); + "); let pass_project = ProjectBuilder::new( "apass_test" ) .toml_file( "[features]\nenabled = []" ) - .test_file( r#" - #[test] + .test_file( r" + #[ test ] fn should_pass() { assert_eq!(1,1); } - "#); + "); let pass_project2 = ProjectBuilder::new( "pass_test2" ) .toml_file( "[features]\nenabled = []" ) - .test_file( r#" - #[test] + .test_file( r" + #[ test ] fn should_pass() { assert_eq!(1,1); } - "#); + "); let workspace = WorkspaceBuilder::new() .member( fail_project ) @@ -168,12 +164,12 @@ fn plan() let project = ProjectBuilder::new( "plan_test" ) .toml_file( "[features]\nenabled = []" ) - .test_file( r#" - #[test] + .test_file( r" + #[ test ] fn should_pass() { assert!(true); } - "#) + ") .build( temp ) .unwrap(); let abs = AbsolutePath::try_from( project ).unwrap(); @@ -202,12 +198,12 @@ fn backtrace_should_be() let project = ProjectBuilder::new( "fail_build" ) .toml_file( "[features]\nenabled = []" ) - .test_file( r#" - #[test] + .test_file( r" + #[ test ] fn fail() { assert!(false); } - "#) + ") .build( temp ) .unwrap(); let abs = AbsolutePath::try_from( project ).unwrap(); @@ -220,7 +216,7 @@ fn backtrace_should_be() .form(); let rep = test( args, false ).unwrap_err().0; - println!( "========= OUTPUT =========\n{}\n==========================", rep ); + println!( "========= OUTPUT =========\n{rep}\n==========================" ); let no_features = rep .failure_reports[ 0 ] diff --git a/module/move/willbe/tests/inc/action_tests/workspace_renew.rs b/module/move/willbe/tests/inc/action_tests/workspace_renew.rs index 9dbfcea23d..19861082e3 100644 --- a/module/move/willbe/tests/inc/action_tests/workspace_renew.rs +++ b/module/move/willbe/tests/inc/action_tests/workspace_renew.rs @@ -26,7 +26,7 @@ fn default_case() create_dir(temp.join("test_project_name" )).unwrap(); // Act - _ = workspace_renew( &temp.path().join( "test_project_name" ), WorkspaceTemplate::default(), "https://github.con/Username/TestRepository".to_string(), vec![ "master".to_string() ] ).unwrap(); + () = workspace_renew::action( &temp.path().join( "test_project_name" ), WorkspaceTemplate::default(), "https://github.con/Username/TestRepository".to_string(), vec![ "master".to_string() ] ).unwrap(); // Assets assert!( temp_path.join( "module" ).exists() ); @@ -41,9 +41,9 @@ fn default_case() let name = "project_name = \"test_project_name\""; let repo_url = "repo_url = \"https://github.con/Username/TestRepository\""; let branches = "branches = [\"master\"]"; - assert!( actual.contains( &name) ); - assert!( actual.contains( &repo_url) ); - assert!( actual.contains( &branches) ); + assert!( actual.contains( name) ); + assert!( actual.contains( repo_url) ); + assert!( actual.contains( branches) ); assert!( temp_path.join( "Makefile" ).exists() ); assert!( temp_path.join( ".cargo" ).exists() ); @@ -57,7 +57,7 @@ fn non_empty_dir() let temp = arrange( "single_module" ); // Act - let r = workspace_renew( temp.path(), WorkspaceTemplate::default(), "".to_string(), vec![] ); + let r = workspace_renew::action( temp.path(), WorkspaceTemplate::default(), String::new(), vec![] ); // fix clippy // Assert assert!( r.is_err() ); diff --git a/module/move/willbe/tests/inc/command/tests_run.rs b/module/move/willbe/tests/inc/command/tests_run.rs index 67f6b97c9c..5b3c31620a 100644 --- a/module/move/willbe/tests/inc/command/tests_run.rs +++ b/module/move/willbe/tests/inc/command/tests_run.rs @@ -18,12 +18,12 @@ fn status_code_1_on_failure() let project = ProjectBuilder::new( "status_code" ) .toml_file( "" ) - .test_file( r#" - #[test] + .test_file( r" + #[ test ] fn should_fail() { panic!(); } - "#) + ") .build( temp ) .unwrap(); @@ -42,12 +42,12 @@ fn status_code_not_zero_on_failure() let project = ProjectBuilder::new( "status_code" ) .toml_file( "" ) - .test_file( r#" - #[test] + .test_file( r" + #[ test ] fn should_fail() { panic!(); } - "#) + ") .build( temp ) .unwrap(); @@ -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/entity/diff.rs b/module/move/willbe/tests/inc/entity/diff.rs index 9c84aa6cc1..ec8af5b6e6 100644 --- a/module/move/willbe/tests/inc/entity/diff.rs +++ b/module/move/willbe/tests/inc/entity/diff.rs @@ -24,7 +24,7 @@ fn no_changes() let right_crate = crate_file_path( &right ); let right_archive = CrateArchive::read( &right_crate ).unwrap(); - let has_changes = crate_diff( &left_archive, &right_archive ).exclude( diff::PUBLISH_IGNORE_LIST ).has_changes(); + let has_changes = crate_diff( &left_archive, &right_archive, false ).exclude( diff::PUBLISH_IGNORE_LIST ).has_changes(); assert!( !has_changes ); } @@ -65,7 +65,7 @@ fn with_changes() CrateArchive::read( &right_crate ).unwrap() }; - let has_changes = crate_diff( &left, &right ).exclude( diff::PUBLISH_IGNORE_LIST ).has_changes(); + let has_changes = crate_diff( &left, &right, false ).exclude( diff::PUBLISH_IGNORE_LIST ).has_changes(); assert!( has_changes ); } diff --git a/module/move/willbe/tests/inc/entity/features.rs b/module/move/willbe/tests/inc/entity/features.rs index 14cd845879..2b4afacb88 100644 --- a/module/move/willbe/tests/inc/entity/features.rs +++ b/module/move/willbe/tests/inc/entity/features.rs @@ -26,7 +26,7 @@ fn mock_package( features : Vec< ( &str, Vec< &str > ) > ) -> cargo_metadata::Pa "dependencies" : [], "targets" : [], "features" : features_map, - "manifest_path" : "".to_string(), + "manifest_path" : String::new(), // fix clippy "authors" : [], "categories" : [], "keywords" : [], diff --git a/module/move/willbe/tests/inc/entity/version.rs b/module/move/willbe/tests/inc/entity/version.rs index 3251fce27c..840c342b4e 100644 --- a/module/move/willbe/tests/inc/entity/version.rs +++ b/module/move/willbe/tests/inc/entity/version.rs @@ -1,7 +1,7 @@ use crate::*; use std::path::{ Path, PathBuf }; -use std::str::FromStr; +use core::str::FromStr; use std::io::Write; use assert_fs::prelude::*; use the_module:: @@ -167,7 +167,7 @@ default-features = true let mut root_manifest = Manifest::try_from( root_manifest_dir_absolute_path ).unwrap(); // root_manifest.load().unwrap(); let data = root_manifest.data(); - let current_version_item = data.get( "workspace" ).and_then( | w | w.get( "dependencies" ) ).and_then( | d | d.get( &name ) ).and_then( | p | p.get( "version" ) ).unwrap(); + let current_version_item = data.get( "workspace" ).and_then( | w | w.get( "dependencies" ) ).and_then( | d | d.get( name ) ).and_then( | p | p.get( "version" ) ).unwrap(); // fix clippy let current_version = current_version_item.as_str().unwrap(); assert_eq!( &bumped_version.to_string(), current_version ); } @@ -223,7 +223,7 @@ default-features = true let mut root_manifest = Manifest::try_from( root_manifest_dir_absolute_path ).unwrap(); // root_manifest.load().unwrap(); let data = root_manifest.data(); - let current_version_item = data.get( "workspace" ).and_then( | w | w.get( "dependencies" ) ).and_then( | d | d.get( &name ) ).and_then( | p | p.get( "version" ) ).unwrap(); + let current_version_item = data.get( "workspace" ).and_then( | w | w.get( "dependencies" ) ).and_then( | d | d.get( name ) ).and_then( | p | p.get( "version" ) ).unwrap(); let current_version = current_version_item.as_str().unwrap(); assert_eq!( &version.to_string(), current_version ); } diff --git a/module/move/willbe/tests/inc/helper.rs b/module/move/willbe/tests/inc/helper.rs index 2efa341f9d..a4413e0746 100644 --- a/module/move/willbe/tests/inc/helper.rs +++ b/module/move/willbe/tests/inc/helper.rs @@ -7,7 +7,7 @@ use std:: io::Write, }; -pub const BINARY_NAME : &'static str = "will"; +pub const BINARY_NAME : &str = "will"; // fix clippy #[ derive( Debug ) ] pub struct ProjectBuilder @@ -59,19 +59,19 @@ impl ProjectBuilder if let Some( content ) = &self.toml_content { let mut file = File::create( project_path.join( "Cargo.toml" ) )?; - write!( file, "{}", content )?; + write!( file, "{content}" )?; // fix clippy } let mut file = File::create( project_path.join( "src/lib.rs" ) )?; if let Some( content ) = &self.lib_content { - write!( file, "{}", content )?; + write!( file, "{content}" )?; // fix clippy } if let Some( content ) = &self.test_content { let mut file = File::create( project_path.join( "tests/tests.rs" ) )?; - write!( file, "{}", content )?; + write!( file, "{content}" )?; // fix clippy } Ok( project_path.to_path_buf() ) diff --git a/module/move/willbe/tests/inc/mod.rs b/module/move/willbe/tests/inc/mod.rs index ca9dbda05d..be920a6b41 100644 --- a/module/move/willbe/tests/inc/mod.rs +++ b/module/move/willbe/tests/inc/mod.rs @@ -1,4 +1,5 @@ use super::*; +use test_tools::exposed::*; /// Entities of which spaces consists of. mod entity; @@ -14,6 +15,8 @@ mod action_tests; mod helper; +mod package; + // aaa : for Petro : for Bohdan : for Nikita : sort out test files to be consistent with src files // sorted diff --git a/module/move/willbe/tests/inc/package.rs b/module/move/willbe/tests/inc/package.rs index 8a5fb2a2f0..c9b84d5a89 100644 --- a/module/move/willbe/tests/inc/package.rs +++ b/module/move/willbe/tests/inc/package.rs @@ -1,3 +1,310 @@ +use std::*; +use std::io::Write; + +use crate::the_module::{ action, channel, package }; + +enum Dependency +{ + Normal { name: String, path: Option< path::PathBuf >, is_macro: bool }, + Dev { name: String, path: Option< path::PathBuf >, is_macro: bool }, +} + +impl Dependency +{ + fn as_toml( &self ) -> String + { + match self + { + Dependency::Normal { name, path, is_macro } if !is_macro => + if let Some( path ) = path + { + format!( "[dependencies.{name}]\npath = \"../{}\"", path.display().to_string().replace( '\\', "/" ) ) // fix clippy + } + else + { + format!( "[dependencies.{name}]\nversion = \"*\"" ) + } + Dependency::Normal { name, .. } => format!( "[dependencies.{name}]\nworkspace = true" ), + Dependency::Dev { name, path, is_macro } if !is_macro => + if let Some( path ) = path + { + format!( "[dev-dependencies.{name}]\npath = \"../{}\"", path.display().to_string().replace( '\\', "/" ) ) // fix clippy + } + else + { + format!( "[dev-dependencies.{name}]\nversion = \"*\"" ) + } + Dependency::Dev { name, .. } => format!( "[dev-dependencies.{name}]\nworkspace = true" ), + } + } +} + +struct TestPackage +{ + name: String, + dependencies: Vec< Dependency >, + path: Option< path::PathBuf >, +} + +impl TestPackage +{ + pub fn new( name: impl Into< String > ) -> Self + { + Self { name: name.into(), dependencies: vec![], path: None } + } + + pub fn dependency( mut self, name: impl Into< String > ) -> Self + { + self.dependencies.push( Dependency::Normal { name: name.into(), path: None, is_macro: false } ); + self + } + // never used + pub fn _macro_dependency( mut self, name: impl Into< String > ) -> Self + { + self.dependencies.push( Dependency::Normal { name: name.into(), path: None, is_macro: true } ); + self + } + // never used + pub fn _dev_dependency( mut self, name: impl Into< String > ) -> Self + { + self.dependencies.push( Dependency::Dev { name: name.into(), path: None, is_macro: false } ); + self + } + + pub fn macro_dev_dependency( mut self, name: impl Into< String > ) -> Self + { + self.dependencies.push( Dependency::Dev { name: name.into(), path: None, is_macro: true } ); + self + } + + pub fn create( &mut self, path: impl AsRef< path::Path > ) -> io::Result< () > + { + let path = path.as_ref().join( &self.name ); + + () = fs::create_dir_all( path.join( "src" ) )?; + () = fs::write( path.join( "src" ).join( "lib.rs" ), [] )?; + + let cargo = format! + ( + r#"[package] +name = "{}" +version = "0.1.0" +edition = "2021" +{}"#, + self.name, + self.dependencies.iter().map( Dependency::as_toml ).fold( String::new(), | acc, d | + { + format!( "{acc}\n\n{d}" ) + }) + ); + () = fs::write( path.join( "Cargo.toml" ), cargo.as_bytes() )?; + + self.path = Some( path ); + + Ok( () ) + } +} + +impl Drop for TestPackage +{ + fn drop( &mut self ) + { + if let Some( path ) = &self.path + { + _ = fs::remove_dir_all( path ).ok(); + } + } +} + +struct TestWorkspace +{ + packages: Vec< TestPackage >, + path: path::PathBuf, +} + +impl TestWorkspace +{ + fn new( path: impl AsRef< path::Path > ) -> io::Result< Self > + { + let path = path.as_ref(); + () = fs::create_dir_all( path )?; + + let cargo = r#"[workspace] +resolver = "2" +members = [ + "members/*", +] +"#; + () = fs::write( path.join( "Cargo.toml" ), cargo.as_bytes() )?; + + Ok(Self { packages: vec![], path: path.into() }) + } + + fn find( &self, package_name: impl AsRef< str > ) -> Option< &TestPackage > + { + let name = package_name.as_ref(); + self.packages.iter().find( | p | p.name == name ) + } + + fn with_package( mut self, mut package: TestPackage ) -> io::Result< Self > + { + let mut macro_deps = collections::HashMap::new(); + for dep in &mut package.dependencies + { + match dep + { + Dependency::Normal { name, is_macro, .. } if *is_macro => + { + if let Some( package ) = self.find( &name ) + { + if let Some( path ) = &package.path + { + macro_deps.insert( name.clone(), path.clone() ); + continue; + } + } + eprintln!( "macro dependency {} not found. required for {}", name, package.name ); + } + Dependency::Normal { name, path, .. } => + { + if let Some( package ) = self.find( &name ) + { + if let Some( real_path ) = &package.path + { + let real_path = real_path.strip_prefix( self.path.join( "members" ) ).unwrap_or( real_path ); + *path = Some( real_path.into() ); + } + } + } + Dependency::Dev { name, is_macro, .. } if *is_macro => + { + if let Some( package ) = self.find( &name ) + { + if let Some( path ) = &package.path + { + macro_deps.insert( name.clone(), path.clone() ); + continue; + } + } + eprintln!( "macro dev-dependency {} not found. required for {}", name, package.name ); + } + Dependency::Dev { name, path, .. } => + { + if let Some( package ) = self.find( &name ) + { + if let Some( real_path ) = &package.path + { + let real_path = real_path.strip_prefix( self.path.join( "members" ) ).unwrap_or( real_path ); + *path = Some( real_path.into() ); + } + } + } + } + } + let mut cargo = fs::OpenOptions::new().append( true ).open( self.path.join( "Cargo.toml" ) )?; + for ( name, _ ) in macro_deps + { + writeln!( cargo, + r#"[workspace.dependencies.{name}] +version = "*" +path = "members/{name}""#, + )?; + } + package.create( self.path.join( "members" ) )?; + self.packages.push( package ); + + Ok( self ) + } + + fn with_packages( mut self, packages: impl IntoIterator< Item = TestPackage > ) -> io::Result< Self > + { + for package in packages { self = self.with_package( package )?; } + + Ok( self ) + } +} + +impl Drop for TestWorkspace +{ + fn drop( &mut self ) + { + _ = fs::remove_dir_all( &self.path ).ok(); + } +} + +#[ test ] +fn kos_plan() +{ + let tmp_folder = env::temp_dir().join( "publish_plan_kos_plan" ); + _ = fs::remove_dir_all( &tmp_folder ).ok(); + + let workspace = TestWorkspace::new( tmp_folder ).unwrap() + .with_packages( + [ + TestPackage::new( "a" ), + TestPackage::new( "b" ).dependency( "a" ), + TestPackage::new( "c" ).dependency( "a" ), + TestPackage::new( "d" ).dependency( "a" ), + TestPackage::new( "e" ).dependency( "b" ).macro_dev_dependency( "c" ),//.macro_dependency( "c" ), + ]).unwrap(); + let the_patterns: Vec< String > = workspace + .packages + .iter() + .filter_map( | p | p.path.as_ref().map( | p | p.to_string_lossy().into_owned() ) ) // fix clippy + .collect(); + dbg!(&the_patterns); + + let plan = action::publish_plan + ( + the_patterns, + channel::Channel::Stable, + false, + false, + true, + false, + ) + .unwrap(); + + let queue: Vec< &package::PackageName > = plan.plans.iter().map( | i | &i.package_name ).collect(); + dbg!(&queue); + + // We don’t consider dev dependencies when constructing the project graph, which results in this number of variations. + // If you'd like to modify this behavior, please check `entity/workspace_graph.rs` in the `module_dependency_filter`. + let expected_one_of= + [ + [ "a", "b", "d", "c", "e" ], + [ "a", "b", "c", "d", "e" ], + [ "a", "d", "b", "c", "e" ], + [ "a", "c", "b", "d", "e" ], + [ "a", "d", "c", "b", "e" ], + [ "a", "c", "d", "b", "e" ], + [ "a", "b", "d", "e", "c" ], + [ "a", "d", "b", "e", "c" ], + [ "a", "b", "e", "d", "c" ], + [ "a", "e", "b", "d", "c" ], + [ "a", "d", "e", "b", "c" ], + [ "a", "e", "d", "b", "c" ], + [ "a", "b", "c", "e", "d" ], + [ "a", "c", "b", "e", "d" ], + [ "a", "b", "e", "c", "d" ], + [ "a", "e", "b", "c", "d" ], + [ "a", "c", "e", "b", "d" ], + [ "a", "e", "c", "b", "d" ], + ]; + + let mut fail = true; + 'sequences: for sequence in expected_one_of + { + for index in 0 .. 5 + { + if *queue[ index ] != sequence[ index ].to_string().into() { continue 'sequences; } + } + fail = false; + break; + } + assert!( !fail ); +} + // use super::*; // use the_module:: // { diff --git a/module/move/willbe/tests/inc/tool/graph_test.rs b/module/move/willbe/tests/inc/tool/graph_test.rs index d6f5c38986..d2a195c3a6 100644 --- a/module/move/willbe/tests/inc/tool/graph_test.rs +++ b/module/move/willbe/tests/inc/tool/graph_test.rs @@ -1,8 +1,9 @@ use super::*; -use the_module::*; -use graph::toposort; -use collection::HashMap; +// qqq : for Bohdan : bad. don't import the_module::* +// use the_module::*; +use the_module::graph::toposort; +use test_tools::collection::HashMap; use petgraph::Graph; use willbe::graph::topological_sort_with_grouping; @@ -10,11 +11,11 @@ struct IndexMap< T >( HashMap< T, usize > ); impl< T > IndexMap< T > where - T : std::hash::Hash + Eq, + T : core::hash::Hash + Eq, // fix clippy { pub fn new( elements : Vec< T > ) -> Self { - let index_map = elements.into_iter().enumerate().map( |( index, value )| ( value, index ) ).collect(); + let index_map = elements.into_iter().enumerate().map( | ( index, value ) | ( value, index ) ).collect(); Self( index_map ) } @@ -141,7 +142,7 @@ fn simple_case() let groups = topological_sort_with_grouping( graph ); assert_eq!( groups[ 0 ], vec![ "A" ] ); - assert_eq!( groups[1].len(), 2 ); + assert_eq!( groups[ 1 ].len(), 2 ); assert!( groups[ 1 ].contains( &"C" ) ); assert!( groups[ 1 ].contains( &"B" ) ); } @@ -197,18 +198,18 @@ fn complicated_test() let groups = topological_sort_with_grouping( graph ); - dbg!(&groups); + dbg!( &groups ); assert_eq!( groups[ 0 ], vec![ "0" ] ); - assert_eq!( groups[1].len(), 3 ); + assert_eq!( groups[ 1 ].len(), 3 ); assert!( groups[ 1 ].contains( &"6" ) ); assert!( groups[ 1 ].contains( &"5" ) ); assert!( groups[ 1 ].contains( &"4" ) ); assert_eq!( groups[ 2 ], vec![ "3" ] ); - assert_eq!( groups[3].len(), 3 ); + assert_eq!( groups[ 3 ].len(), 3 ); assert!( groups[ 3 ].contains( &"1" ) ); assert!( groups[ 3 ].contains( &"2" ) ); assert!( groups[ 3 ].contains( &"7" ) ); diff --git a/module/move/willbe/tests/inc/tool/query_test.rs b/module/move/willbe/tests/inc/tool/query_test.rs index fa98f5fab1..c5de225dbf 100644 --- a/module/move/willbe/tests/inc/tool/query_test.rs +++ b/module/move/willbe/tests/inc/tool/query_test.rs @@ -6,7 +6,7 @@ use the_module::query:: Value, }; use the_module::collection::HashMap; -use std::str::FromStr; +use core::str::FromStr; #[ test ] fn value_from_str() @@ -19,11 +19,11 @@ fn value_from_str() #[ test ] fn bool_from_value() { - assert_eq!( bool::from( &Value::Bool( true ) ), true ); - assert_eq!( bool::from( &Value::String( "true".to_string() ) ), true ); - assert_eq!( bool::from( &Value::Int( 1 ) ), true ); - assert_eq!( bool::from( &Value::Int( 0 ) ), false); - assert_eq!( bool::from( &Value::String( "test".to_string() ) ), false); + assert!( bool::from( &Value::Bool( true ) ) ); + assert!( bool::from( &Value::String( "true".to_string() ) ) ); + assert!( bool::from( &Value::Int( 1 ) ) ); + assert!( !bool::from( &Value::Int( 0 ) ) ); + assert!( !bool::from( &Value::String( "test".to_string() ) ) ); } #[ test ] @@ -37,9 +37,9 @@ fn parse_result_convert() let mixed_map = result.clone().into_map( vec![ "var0".into() ] ); let vec = result.into_vec(); - assert_eq!( HashMap::from( [( "var0".to_string(),Value::Int( 1 )), ( "var1".to_string(),Value::Int( 2 )), ( "var2".to_string(),Value::Int( 3 )) ]), named_map ); - assert_eq!( HashMap::from( [( "1".to_string(),Value::Int( 1 )), ( "2".to_string(),Value::Int( 2 )), ( "3".to_string(),Value::Int( 3 )) ]), unnamed_map ); - assert_eq!( HashMap::from( [( "var0".to_string(),Value::Int( 1 )), ( "1".to_string(),Value::Int( 2 )), ( "2".to_string(),Value::Int( 3 )) ]), mixed_map ); + assert_eq!( HashMap::from( [ ( "var0".to_string(),Value::Int( 1 ) ), ( "var1".to_string(),Value::Int( 2 ) ), ( "var2".to_string(),Value::Int( 3 ) ) ] ), named_map ); + assert_eq!( HashMap::from( [ ( "1".to_string(),Value::Int( 1 ) ), ( "2".to_string(),Value::Int( 2 ) ), ( "3".to_string(),Value::Int( 3 ) ) ] ), unnamed_map ); + assert_eq!( HashMap::from( [ ( "var0".to_string(),Value::Int( 1 ) ), ( "1".to_string(),Value::Int( 2 ) ), ( "2".to_string(),Value::Int( 3 ) ) ] ), mixed_map ); assert_eq!( vec![ Value::Int( 1 ), Value::Int( 2 ), Value::Int( 3 ) ], vec ); } @@ -49,12 +49,12 @@ fn parse_empty_string() assert_eq!( parse( "()" ).unwrap().into_vec(), vec![] ); } -#[test] +#[ test ] fn parse_single_value() { let mut expected_map = HashMap::new(); expected_map.insert( "1".to_string(), Value::String( "test/test".to_string() ) ); - assert_eq!( parse( "('test/test')" ).unwrap().into_map(vec![]), expected_map ); + assert_eq!( parse( "('test/test')" ).unwrap().into_map( vec![] ), expected_map ); } #[ test ] @@ -63,7 +63,7 @@ fn parse_multiple_values() let mut expected_map = HashMap::new(); expected_map.insert( "key1".to_string(), Value::Int( 123 ) ); expected_map.insert( "key2".to_string(), Value::Bool( true ) ); - assert_eq!( parse( "{key1 : 123, key2 : true}" ).unwrap().into_map(vec![]), expected_map ); + assert_eq!( parse( "{key1 : 123, key2 : true}" ).unwrap().into_map( vec![] ), expected_map ); } #[ test ] @@ -71,7 +71,7 @@ fn parse_with_quotes() { let mut expected_map = HashMap::new(); expected_map.insert( "key".to_string(), Value::String( "hello world".to_string() ) ); - assert_eq!( parse( "{key : 'hello world'}" ).unwrap().into_map(vec![]), expected_map ); + assert_eq!( parse( "{key : 'hello world'}" ).unwrap().into_map( vec![] ), expected_map ); } #[ test ] @@ -79,7 +79,7 @@ fn parse_with_special_characters() { let mut expected_map = HashMap::new(); expected_map.insert( "key".to_string(), Value::String( "!@#$%^&*(),".to_string() ) ); - assert_eq!( parse( "{key : '!@#$%^&*(),'}" ).unwrap().into_map(vec![]), expected_map ); + assert_eq!( parse( "{key : '!@#$%^&*(),'}" ).unwrap().into_map( vec![] ), expected_map ); } @@ -88,7 +88,7 @@ fn parse_with_colon_in_value() { let mut expected_map = HashMap::new(); expected_map.insert( "key".to_string(), Value::String( "hello :world".to_string() ) ); - assert_eq!( parse( "{key : 'hello :world'}" ).unwrap().into_map(vec![]), expected_map ); + assert_eq!( parse( "{key : 'hello :world'}" ).unwrap().into_map( vec![] ), expected_map ); } #[ test ] @@ -96,15 +96,15 @@ fn with_comma_in_value() { let mut expected_map = HashMap::new(); expected_map.insert( "key".to_string(), Value::String( "hello,world".to_string() ) ); - assert_eq!( parse( "{key : 'hello,world'}" ).unwrap().into_map(vec![]), expected_map ); + assert_eq!( parse( "{key : 'hello,world'}" ).unwrap().into_map( vec![] ), expected_map ); } #[ test ] fn with_single_quote_escape() { let mut expected_map = HashMap::new(); - expected_map.insert( "key".to_string(), Value::String( r#"hello\'test\'test"#.into() ) ); - assert_eq!( parse( r#"{ key : 'hello\'test\'test' }"# ).unwrap().into_map(vec![]), expected_map ); + expected_map.insert( "key".to_string(), Value::String( r"hello\'test\'test".into() ) ); + assert_eq!( parse( r"{ key : 'hello\'test\'test' }" ).unwrap().into_map( vec![] ), expected_map ); } #[ test ] @@ -113,7 +113,7 @@ fn with_multiple_spaces() let mut expected_map = HashMap::new(); expected_map.insert( "key".to_string(), Value::String( "test ".into() ) ); expected_map.insert( "key2".to_string(), Value::String( "test".into() ) ); - assert_eq!( parse( r#"{ key : 'test ', key2 : test }"# ).unwrap().into_map(vec![]), expected_map ); + assert_eq!( parse( r"{ key : 'test ', key2 : test }" ).unwrap().into_map( vec![] ), expected_map ); } #[ test ] @@ -124,7 +124,7 @@ fn many_unnamed() ( "1".to_string(), Value::Int( 123 ) ), ( "2".to_string(), Value::String( "test_aboba".to_string() ) ), ] ); - assert_eq!( parse( "( 123, 'test_aboba' )").unwrap().into_map(vec![]), expected ); + assert_eq!( parse( "( 123, 'test_aboba' )").unwrap().into_map( vec![] ), expected ); } #[ test ] @@ -136,5 +136,5 @@ fn named_and_unnamed() ( "2".to_string(), Value::String( "test_aboba".to_string() ) ), ( "3".to_string(), Value::String("test : true".to_string())) ] ); - assert_eq!( parse( r#"(123, 'test_aboba', test : true)"#).unwrap().into_map(vec![]), expected ); + assert_eq!( parse( r"(123, 'test_aboba', test : true)").unwrap().into_map( vec![] ), expected ); } diff --git a/module/move/willbe/tests/smoke_test.rs b/module/move/willbe/tests/smoke_test.rs index dd681c20c1..c9b1b4daae 100644 --- a/module/move/willbe/tests/smoke_test.rs +++ b/module/move/willbe/tests/smoke_test.rs @@ -1,14 +1,13 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() { - ::test_tools::smoke_test_for_local_run(); + ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { - ::test_tools::smoke_test_for_published_run(); + ::test_tools::smoke_test_for_published_run(); } diff --git a/module/move/willbe/tests/tests.rs b/module/move/willbe/tests/tests.rs index 87a862274b..cefd199e28 100644 --- a/module/move/willbe/tests/tests.rs +++ b/module/move/willbe/tests/tests.rs @@ -1,11 +1,11 @@ +//! All tests. +#![ allow( unused_imports ) ] include!( "../../../../module/step/meta/src/module/terminal.rs" ); -#[ allow( unused_imports ) ] +/// System under test. use willbe as the_module; -#[ allow( unused_imports ) ] -use test_tools::exposed::*; - +/// asset path pub const ASSET_PATH : &str = "tests/asset"; mod inc; diff --git a/module/move/wplot/License b/module/move/wplot/License index 6d5ef8559f..72c80c1308 100644 --- a/module/move/wplot/License +++ b/module/move/wplot/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/wplot/src/plot/abs/change.rs b/module/move/wplot/src/plot/abs/change.rs index 660a54e108..ad2a0219a2 100644 --- a/module/move/wplot/src/plot/abs/change.rs +++ b/module/move/wplot/src/plot/abs/change.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { // use crate::own::*; diff --git a/module/move/wplot/src/plot/abs/changer.rs b/module/move/wplot/src/plot/abs/changer.rs index 3315e82b38..9c91ad95c2 100644 --- a/module/move/wplot/src/plot/abs/changer.rs +++ b/module/move/wplot/src/plot/abs/changer.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { // use crate::own::*; diff --git a/module/move/wplot/src/plot/abs/context.rs b/module/move/wplot/src/plot/abs/context.rs index c666e3edca..a27efc6748 100644 --- a/module/move/wplot/src/plot/abs/context.rs +++ b/module/move/wplot/src/plot/abs/context.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. #[ cfg( not( feature = "no_std" ) ) ] mod private { diff --git a/module/move/wplot/src/plot/abs/identity.rs b/module/move/wplot/src/plot/abs/identity.rs index 48d9c0426a..9abecc7727 100644 --- a/module/move/wplot/src/plot/abs/identity.rs +++ b/module/move/wplot/src/plot/abs/identity.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. #[ cfg( not( feature = "no_std" ) ) ] mod private { diff --git a/module/move/wplot/src/plot/abs/registry.rs b/module/move/wplot/src/plot/abs/registry.rs index 026c0f5c20..c69f775dca 100644 --- a/module/move/wplot/src/plot/abs/registry.rs +++ b/module/move/wplot/src/plot/abs/registry.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. #[ cfg( not( feature = "no_std" ) ) ] mod private { diff --git a/module/move/wplot/src/plot/color.rs b/module/move/wplot/src/plot/color.rs index 5cf94b95c8..3ae327c824 100644 --- a/module/move/wplot/src/plot/color.rs +++ b/module/move/wplot/src/plot/color.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { // use crate::own::*; diff --git a/module/move/wplot/src/plot/sys/context.rs b/module/move/wplot/src/plot/sys/context.rs index a59ae6343d..19bd3ce2a9 100644 --- a/module/move/wplot/src/plot/sys/context.rs +++ b/module/move/wplot/src/plot/sys/context.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::abs::registry::private::Registry; diff --git a/module/move/wplot/src/plot/sys/context_changer.rs b/module/move/wplot/src/plot/sys/context_changer.rs index e6a91ca8e5..c0f1df3442 100644 --- a/module/move/wplot/src/plot/sys/context_changer.rs +++ b/module/move/wplot/src/plot/sys/context_changer.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { // use crate::own::*; diff --git a/module/move/wplot/src/plot/sys/drawing.rs b/module/move/wplot/src/plot/sys/drawing.rs index 673fd1fa74..9e668966be 100644 --- a/module/move/wplot/src/plot/sys/drawing.rs +++ b/module/move/wplot/src/plot/sys/drawing.rs @@ -1,6 +1,6 @@ pub(crate) mod changer; -/// Internal namespace. +/// Define a private namespace for all its items. mod private { // use crate::own::*; diff --git a/module/move/wplot/src/plot/sys/drawing/change_new.rs b/module/move/wplot/src/plot/sys/drawing/change_new.rs index ab075de7fa..f7628c2566 100644 --- a/module/move/wplot/src/plot/sys/drawing/change_new.rs +++ b/module/move/wplot/src/plot/sys/drawing/change_new.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { // use crate::own::*; diff --git a/module/move/wplot/src/plot/sys/drawing/changer.rs b/module/move/wplot/src/plot/sys/drawing/changer.rs index a7ba4c1b67..84c69db2c3 100644 --- a/module/move/wplot/src/plot/sys/drawing/changer.rs +++ b/module/move/wplot/src/plot/sys/drawing/changer.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { // use crate::own::*; diff --git a/module/move/wplot/src/plot/sys/drawing/command.rs b/module/move/wplot/src/plot/sys/drawing/command.rs index f98cedfd22..998272ee16 100644 --- a/module/move/wplot/src/plot/sys/drawing/command.rs +++ b/module/move/wplot/src/plot/sys/drawing/command.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { // use crate::own::*; diff --git a/module/move/wplot/src/plot/sys/drawing/queue.rs b/module/move/wplot/src/plot/sys/drawing/queue.rs index c68de594ba..c3148011bb 100644 --- a/module/move/wplot/src/plot/sys/drawing/queue.rs +++ b/module/move/wplot/src/plot/sys/drawing/queue.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { // use crate::own::*; diff --git a/module/move/wplot/src/plot/sys/drawing/rect_change_new.rs b/module/move/wplot/src/plot/sys/drawing/rect_change_new.rs index 212ffb82c1..b682c0ead8 100644 --- a/module/move/wplot/src/plot/sys/drawing/rect_change_new.rs +++ b/module/move/wplot/src/plot/sys/drawing/rect_change_new.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { // use crate::own::*; diff --git a/module/move/wplot/src/plot/sys/drawing/rect_change_region.rs b/module/move/wplot/src/plot/sys/drawing/rect_change_region.rs index 463259b6cf..29b6885e63 100644 --- a/module/move/wplot/src/plot/sys/drawing/rect_change_region.rs +++ b/module/move/wplot/src/plot/sys/drawing/rect_change_region.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::own::*; diff --git a/module/move/wplot/src/plot/sys/drawing/rect_changer.rs b/module/move/wplot/src/plot/sys/drawing/rect_changer.rs index bb25c465aa..7e39fb06fc 100644 --- a/module/move/wplot/src/plot/sys/drawing/rect_changer.rs +++ b/module/move/wplot/src/plot/sys/drawing/rect_changer.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::own::*; diff --git a/module/move/wplot/src/plot/sys/stroke_brush.rs b/module/move/wplot/src/plot/sys/stroke_brush.rs index 78ad289dc7..9f52539630 100644 --- a/module/move/wplot/src/plot/sys/stroke_brush.rs +++ b/module/move/wplot/src/plot/sys/stroke_brush.rs @@ -1,7 +1,7 @@ mod change_width; mod change_new; -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::own::*; diff --git a/module/move/wplot/src/plot/sys/stroke_brush/change_color.rs b/module/move/wplot/src/plot/sys/stroke_brush/change_color.rs index ae615f89a4..76bd951613 100644 --- a/module/move/wplot/src/plot/sys/stroke_brush/change_color.rs +++ b/module/move/wplot/src/plot/sys/stroke_brush/change_color.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::own::*; diff --git a/module/move/wplot/src/plot/sys/stroke_brush/change_new.rs b/module/move/wplot/src/plot/sys/stroke_brush/change_new.rs index 077f20f6ba..4e70ba7ee7 100644 --- a/module/move/wplot/src/plot/sys/stroke_brush/change_new.rs +++ b/module/move/wplot/src/plot/sys/stroke_brush/change_new.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { // use crate::own::*; diff --git a/module/move/wplot/src/plot/sys/stroke_brush/change_width.rs b/module/move/wplot/src/plot/sys/stroke_brush/change_width.rs index cf5a548778..a7fcecdcb8 100644 --- a/module/move/wplot/src/plot/sys/stroke_brush/change_width.rs +++ b/module/move/wplot/src/plot/sys/stroke_brush/change_width.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { // use crate::own::*; diff --git a/module/move/wplot/src/plot/sys/stroke_brush/changer.rs b/module/move/wplot/src/plot/sys/stroke_brush/changer.rs index 407b234fac..152dfebaab 100644 --- a/module/move/wplot/src/plot/sys/stroke_brush/changer.rs +++ b/module/move/wplot/src/plot/sys/stroke_brush/changer.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::own::*; diff --git a/module/move/wplot/src/plot/sys/target.rs b/module/move/wplot/src/plot/sys/target.rs index 58634c4e36..95d123186b 100644 --- a/module/move/wplot/src/plot/sys/target.rs +++ b/module/move/wplot/src/plot/sys/target.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { // use crate::prelude::*; diff --git a/module/move/wplot/tests/smoke_test.rs b/module/move/wplot/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/move/wplot/tests/smoke_test.rs +++ b/module/move/wplot/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/postponed/_video_experiment/License b/module/postponed/_video_experiment/License index 6d5ef8559f..72c80c1308 100644 --- a/module/postponed/_video_experiment/License +++ b/module/postponed/_video_experiment/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/tests/smoke_test.rs b/module/postponed/_video_experiment/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/postponed/_video_experiment/tests/smoke_test.rs +++ b/module/postponed/_video_experiment/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/postponed/automata_tools/License b/module/postponed/automata_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/postponed/automata_tools/License +++ b/module/postponed/automata_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/tests/smoke_test.rs b/module/postponed/automata_tools/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/postponed/automata_tools/tests/smoke_test.rs +++ b/module/postponed/automata_tools/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/postponed/non_std/Cargo.toml b/module/postponed/non_std/Cargo.toml index 516d197d99..1f684f13cb 100644 --- a/module/postponed/non_std/Cargo.toml +++ b/module/postponed/non_std/Cargo.toml @@ -72,7 +72,7 @@ meta_default = [ "meta_mod_interface", # "meta_former", # "meta_options", - "meta_constructors", + #"meta_constructors", "meta_idents_concat", ] meta_full = [ @@ -82,7 +82,7 @@ meta_full = [ "meta_mod_interface", # "meta_former", # "meta_options", - "meta_constructors", + #"meta_constructors", "meta_idents_concat", ] meta_no_std = [ "wtools/meta_no_std" ] diff --git a/module/postponed/non_std/License b/module/postponed/non_std/License index 6d5ef8559f..72c80c1308 100644 --- a/module/postponed/non_std/License +++ b/module/postponed/non_std/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/tests/smoke_test.rs b/module/postponed/non_std/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/postponed/non_std/tests/smoke_test.rs +++ b/module/postponed/non_std/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/postponed/std_tools/Cargo.toml b/module/postponed/std_tools/Cargo.toml index 44d29afa00..928fe93678 100644 --- a/module/postponed/std_tools/Cargo.toml +++ b/module/postponed/std_tools/Cargo.toml @@ -73,7 +73,7 @@ meta_default = [ "meta_mod_interface", # "meta_former", # "meta_options", - "meta_constructors", + #"meta_constructors", "meta_idents_concat", ] meta_full = [ @@ -83,7 +83,7 @@ meta_full = [ "meta_mod_interface", # "meta_former", # "meta_options", - "meta_constructors", + #"meta_constructors", "meta_idents_concat", ] meta_no_std = [ "wtools/meta_no_std" ] diff --git a/module/postponed/std_tools/License b/module/postponed/std_tools/License index 6d5ef8559f..72c80c1308 100644 --- a/module/postponed/std_tools/License +++ b/module/postponed/std_tools/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/tests/smoke_test.rs b/module/postponed/std_tools/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/postponed/std_tools/tests/smoke_test.rs +++ b/module/postponed/std_tools/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/postponed/std_x/Cargo.toml b/module/postponed/std_x/Cargo.toml index 5693aa40a1..2ee0a6f55c 100644 --- a/module/postponed/std_x/Cargo.toml +++ b/module/postponed/std_x/Cargo.toml @@ -75,7 +75,7 @@ meta_default = [ "meta_mod_interface", # "meta_former", # "meta_options", - "meta_constructors", + #"meta_constructors", "meta_idents_concat", ] meta_full = [ @@ -85,7 +85,7 @@ meta_full = [ "meta_mod_interface", # "meta_former", # "meta_options", - "meta_constructors", + #"meta_constructors", "meta_idents_concat", ] meta_no_std = [ "wtools/meta_no_std" ] diff --git a/module/postponed/std_x/License b/module/postponed/std_x/License index 6d5ef8559f..72c80c1308 100644 --- a/module/postponed/std_x/License +++ b/module/postponed/std_x/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/tests/smoke_test.rs b/module/postponed/std_x/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/postponed/std_x/tests/smoke_test.rs +++ b/module/postponed/std_x/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/postponed/type_constructor/License b/module/postponed/type_constructor/License index 6d5ef8559f..72c80c1308 100644 --- a/module/postponed/type_constructor/License +++ b/module/postponed/type_constructor/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/src/type_constuctor/enumerable.rs b/module/postponed/type_constructor/src/type_constuctor/enumerable.rs index e17553d21c..fdfa45fb97 100644 --- a/module/postponed/type_constructor/src/type_constuctor/enumerable.rs +++ b/module/postponed/type_constructor/src/type_constuctor/enumerable.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { diff --git a/module/postponed/type_constructor/src/type_constuctor/helper.rs b/module/postponed/type_constructor/src/type_constuctor/helper.rs index 57c9986a69..a4dcf9011f 100644 --- a/module/postponed/type_constructor/src/type_constuctor/helper.rs +++ b/module/postponed/type_constructor/src/type_constuctor/helper.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::exposed::*; diff --git a/module/postponed/type_constructor/src/type_constuctor/make.rs b/module/postponed/type_constructor/src/type_constuctor/make.rs index 807974a4ca..2cdb6d6973 100644 --- a/module/postponed/type_constructor/src/type_constuctor/make.rs +++ b/module/postponed/type_constructor/src/type_constuctor/make.rs @@ -1,4 +1,4 @@ -// /// Internal namespace. +// /// Define a private namespace for all its items. // #[ cfg( feature = "make" ) ] // mod private // { diff --git a/module/postponed/type_constructor/src/type_constuctor/many.rs b/module/postponed/type_constructor/src/type_constuctor/many.rs index 0c81d87180..3ded63125c 100644 --- a/module/postponed/type_constructor/src/type_constuctor/many.rs +++ b/module/postponed/type_constructor/src/type_constuctor/many.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::exposed::*; diff --git a/module/postponed/type_constructor/src/type_constuctor/no_many.rs b/module/postponed/type_constructor/src/type_constuctor/no_many.rs index a36e9829ef..d810f74d08 100644 --- a/module/postponed/type_constructor/src/type_constuctor/no_many.rs +++ b/module/postponed/type_constructor/src/type_constuctor/no_many.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { diff --git a/module/postponed/type_constructor/src/type_constuctor/pair.rs b/module/postponed/type_constructor/src/type_constuctor/pair.rs index 090428e500..56b71bc2ff 100644 --- a/module/postponed/type_constructor/src/type_constuctor/pair.rs +++ b/module/postponed/type_constructor/src/type_constuctor/pair.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::exposed::*; diff --git a/module/postponed/type_constructor/src/type_constuctor/single.rs b/module/postponed/type_constructor/src/type_constuctor/single.rs index 7fcf8642f4..2fd3637235 100644 --- a/module/postponed/type_constructor/src/type_constuctor/single.rs +++ b/module/postponed/type_constructor/src/type_constuctor/single.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::exposed::*; diff --git a/module/postponed/type_constructor/src/type_constuctor/traits.rs b/module/postponed/type_constructor/src/type_constuctor/traits.rs index cd11c438ee..cf4838bee3 100644 --- a/module/postponed/type_constructor/src/type_constuctor/traits.rs +++ b/module/postponed/type_constructor/src/type_constuctor/traits.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { diff --git a/module/postponed/type_constructor/src/type_constuctor/types.rs b/module/postponed/type_constructor/src/type_constuctor/types.rs index 151b33ae42..d9d1de235a 100644 --- a/module/postponed/type_constructor/src/type_constuctor/types.rs +++ b/module/postponed/type_constructor/src/type_constuctor/types.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::exposed::*; diff --git a/module/postponed/type_constructor/src/type_constuctor/vectorized_from.rs b/module/postponed/type_constructor/src/type_constuctor/vectorized_from.rs index c7e366142a..c145e31404 100644 --- a/module/postponed/type_constructor/src/type_constuctor/vectorized_from.rs +++ b/module/postponed/type_constructor/src/type_constuctor/vectorized_from.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { diff --git a/module/postponed/type_constructor/tests/inc/mod.rs b/module/postponed/type_constructor/tests/inc/mod.rs index 199b42411a..da4554b46e 100644 --- a/module/postponed/type_constructor/tests/inc/mod.rs +++ b/module/postponed/type_constructor/tests/inc/mod.rs @@ -8,8 +8,8 @@ use super::*; // mod type_constructor; #[ cfg( feature = "enabled" ) ] -#[ cfg( any( feature = "prelude", feature = "dt_prelude" ) ) ] -mod prelude_test; +// #[ cfg( any( feature = "prelude", feature = "dt_prelude" ) ) ] +// mod prelude_test; // #[ allow( unused_imports ) ] // use super::*; diff --git a/module/postponed/type_constructor/tests/inc/prelude_test.rs b/module/postponed/type_constructor/tests/inc/prelude_test.rs index 4c6ace0d4b..1699fe6b0e 100644 --- a/module/postponed/type_constructor/tests/inc/prelude_test.rs +++ b/module/postponed/type_constructor/tests/inc/prelude_test.rs @@ -1,68 +1,68 @@ -#[ allow( unused_imports ) ] -use super::*; - -// - -#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] -#[ cfg( feature = "prelude" ) ] -tests_impls! -{ - fn basic() - { - use the_module::prelude::*; - - /* test.case( "Vec" ) */ - let src = Vec::< i32 >::new(); - a_true!( src.is_empty() ); - - /* test.case( "DynList" ) */ - let src = DynList::< i32 >::new(); - a_true!( src.is_empty() ); - - /* test.case( "HashMap" ) */ - let src = HashMap::< i32, i32 >::new(); - a_true!( src.is_empty() ); - - /* test.case( "Map" ) */ - let src = Map::< i32, i32 >::new(); - a_true!( src.is_empty() ); - - /* test.case( "HashSet" ) */ - let src = HashSet::< i32 >::new(); - a_true!( src.is_empty() ); - - /* test.case( "Set" ) */ - let src = Set::< i32 >::new(); - a_true!( src.is_empty() ); - - /* test.case( "BTreeMap" ) */ - let src = BTreeMap::< i32, i32 >::new(); - a_true!( src.is_empty() ); - - /* test.case( "BTreeSet" ) */ - let src = BTreeSet::< i32 >::new(); - a_true!( src.is_empty() ); - - /* test.case( "BinaryHeap" ) */ - let src = BinaryHeap::< i32 >::new(); - a_true!( src.is_empty() ); - - /* test.case( "LinkedList" ) */ - let src = LinkedList::< i32 >::new(); - a_true!( src.is_empty() ); - - /* test.case( "VecDeque" ) */ - let src = VecDeque::< i32 >::new(); - a_true!( src.is_empty() ); - - } -} - -// - -#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] -#[ cfg( feature = "prelude" ) ] -tests_index! -{ - basic, -} +// #[ allow( unused_imports ) ] +// use super::*; +// +// // +// +// #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +// #[ cfg( feature = "prelude" ) ] +// tests_impls! +// { +// fn basic() +// { +// use the_module::prelude::*; +// +// /* test.case( "Vec" ) */ +// let src = Vec::< i32 >::new(); +// a_true!( src.is_empty() ); +// +// /* test.case( "DynList" ) */ +// let src = DynList::< i32 >::new(); +// a_true!( src.is_empty() ); +// +// /* test.case( "HashMap" ) */ +// let src = HashMap::< i32, i32 >::new(); +// a_true!( src.is_empty() ); +// +// /* test.case( "Map" ) */ +// let src = Map::< i32, i32 >::new(); +// a_true!( src.is_empty() ); +// +// /* test.case( "HashSet" ) */ +// let src = HashSet::< i32 >::new(); +// a_true!( src.is_empty() ); +// +// /* test.case( "Set" ) */ +// let src = Set::< i32 >::new(); +// a_true!( src.is_empty() ); +// +// /* test.case( "BTreeMap" ) */ +// let src = BTreeMap::< i32, i32 >::new(); +// a_true!( src.is_empty() ); +// +// /* test.case( "BTreeSet" ) */ +// let src = BTreeSet::< i32 >::new(); +// a_true!( src.is_empty() ); +// +// /* test.case( "BinaryHeap" ) */ +// let src = BinaryHeap::< i32 >::new(); +// a_true!( src.is_empty() ); +// +// /* test.case( "LinkedList" ) */ +// let src = LinkedList::< i32 >::new(); +// a_true!( src.is_empty() ); +// +// /* test.case( "VecDeque" ) */ +// let src = VecDeque::< i32 >::new(); +// a_true!( src.is_empty() ); +// +// } +// } +// +// // +// +// #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +// #[ cfg( feature = "prelude" ) ] +// tests_index! +// { +// basic, +// } diff --git a/module/postponed/type_constructor/tests/smoke_test.rs b/module/postponed/type_constructor/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/postponed/type_constructor/tests/smoke_test.rs +++ b/module/postponed/type_constructor/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/postponed/wautomata/License b/module/postponed/wautomata/License index 6d5ef8559f..72c80c1308 100644 --- a/module/postponed/wautomata/License +++ b/module/postponed/wautomata/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/src/graph/abs/edge.rs b/module/postponed/wautomata/src/graph/abs/edge.rs index 550a350efb..214f8f10d9 100644 --- a/module/postponed/wautomata/src/graph/abs/edge.rs +++ b/module/postponed/wautomata/src/graph/abs/edge.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::prelude::*; diff --git a/module/postponed/wautomata/src/graph/abs/factory.rs b/module/postponed/wautomata/src/graph/abs/factory.rs index 737cbfdf5c..ddf6012168 100644 --- a/module/postponed/wautomata/src/graph/abs/factory.rs +++ b/module/postponed/wautomata/src/graph/abs/factory.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::prelude::*; diff --git a/module/postponed/wautomata/src/graph/abs/id_generator.rs b/module/postponed/wautomata/src/graph/abs/id_generator.rs index 943315c041..2090439804 100644 --- a/module/postponed/wautomata/src/graph/abs/id_generator.rs +++ b/module/postponed/wautomata/src/graph/abs/id_generator.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { // use crate::prelude::*; diff --git a/module/postponed/wautomata/src/graph/abs/identity.rs b/module/postponed/wautomata/src/graph/abs/identity.rs index c7fdcb3797..1e9c21d2f9 100644 --- a/module/postponed/wautomata/src/graph/abs/identity.rs +++ b/module/postponed/wautomata/src/graph/abs/identity.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { // use crate::prelude::*; diff --git a/module/postponed/wautomata/src/graph/abs/node.rs b/module/postponed/wautomata/src/graph/abs/node.rs index b227581718..703bd0893d 100644 --- a/module/postponed/wautomata/src/graph/abs/node.rs +++ b/module/postponed/wautomata/src/graph/abs/node.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::prelude::*; diff --git a/module/postponed/wautomata/src/graph/algo/dfs.rs b/module/postponed/wautomata/src/graph/algo/dfs.rs index 0a75884e2c..13e7c81e84 100644 --- a/module/postponed/wautomata/src/graph/algo/dfs.rs +++ b/module/postponed/wautomata/src/graph/algo/dfs.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::prelude::*; diff --git a/module/postponed/wautomata/src/graph/canonical/edge.rs b/module/postponed/wautomata/src/graph/canonical/edge.rs index 3bf782aaee..4d02b207d4 100644 --- a/module/postponed/wautomata/src/graph/canonical/edge.rs +++ b/module/postponed/wautomata/src/graph/canonical/edge.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::prelude::*; diff --git a/module/postponed/wautomata/src/graph/canonical/factory_generative.rs b/module/postponed/wautomata/src/graph/canonical/factory_generative.rs index 766002bfb3..0548aa26c5 100644 --- a/module/postponed/wautomata/src/graph/canonical/factory_generative.rs +++ b/module/postponed/wautomata/src/graph/canonical/factory_generative.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::prelude::*; diff --git a/module/postponed/wautomata/src/graph/canonical/factory_readable.rs b/module/postponed/wautomata/src/graph/canonical/factory_readable.rs index d9505b7819..1cad2804dd 100644 --- a/module/postponed/wautomata/src/graph/canonical/factory_readable.rs +++ b/module/postponed/wautomata/src/graph/canonical/factory_readable.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::prelude::*; diff --git a/module/postponed/wautomata/src/graph/canonical/identity.rs b/module/postponed/wautomata/src/graph/canonical/identity.rs index 497da5ff54..6680ead861 100644 --- a/module/postponed/wautomata/src/graph/canonical/identity.rs +++ b/module/postponed/wautomata/src/graph/canonical/identity.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::prelude::*; diff --git a/module/postponed/wautomata/src/graph/canonical/node.rs b/module/postponed/wautomata/src/graph/canonical/node.rs index 94d7f7d313..ce0aa547bd 100644 --- a/module/postponed/wautomata/src/graph/canonical/node.rs +++ b/module/postponed/wautomata/src/graph/canonical/node.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use crate::prelude::*; diff --git a/module/postponed/wautomata/tests/smoke_test.rs b/module/postponed/wautomata/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/postponed/wautomata/tests/smoke_test.rs +++ b/module/postponed/wautomata/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/postponed/wpublisher/License b/module/postponed/wpublisher/License index 6d5ef8559f..72c80c1308 100644 --- a/module/postponed/wpublisher/License +++ b/module/postponed/wpublisher/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/tests/smoke_test.rs b/module/postponed/wpublisher/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/postponed/wpublisher/tests/smoke_test.rs +++ b/module/postponed/wpublisher/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/step/meta/src/module/terminal.rs b/module/step/meta/src/module/terminal.rs index 93289921c5..4a88acf6a9 100644 --- a/module/step/meta/src/module/terminal.rs +++ b/module/step/meta/src/module/terminal.rs @@ -1,4 +1,6 @@ +/// Mechanism to include tests only to terminal crate. +/// It exclude code in terminal module ( crate ), but include for aggregating module ( crate ). #[ macro_export ] macro_rules! only_for_terminal_module { @@ -8,6 +10,8 @@ macro_rules! only_for_terminal_module }; } +/// Mechanism to include tests only to aggregating crate. +/// It exclude code in terminal module ( crate ), but include for aggregating module ( crate ). #[ macro_export ] macro_rules! only_for_aggregating_module { diff --git a/module/step/meta/tests/_conditional/local_module.rs b/module/step/meta/tests/_conditional/local_module.rs index 93289921c5..4a88acf6a9 100644 --- a/module/step/meta/tests/_conditional/local_module.rs +++ b/module/step/meta/tests/_conditional/local_module.rs @@ -1,4 +1,6 @@ +/// Mechanism to include tests only to terminal crate. +/// It exclude code in terminal module ( crate ), but include for aggregating module ( crate ). #[ macro_export ] macro_rules! only_for_terminal_module { @@ -8,6 +10,8 @@ macro_rules! only_for_terminal_module }; } +/// Mechanism to include tests only to aggregating crate. +/// It exclude code in terminal module ( crate ), but include for aggregating module ( crate ). #[ macro_export ] macro_rules! only_for_aggregating_module { diff --git a/module/step/meta/tests/_conditional/wtools.rs b/module/step/meta/tests/_conditional/wtools.rs index e6bb553f35..ba669c790d 100644 --- a/module/step/meta/tests/_conditional/wtools.rs +++ b/module/step/meta/tests/_conditional/wtools.rs @@ -1,4 +1,6 @@ +/// Mechanism to include tests only to terminal crate. +/// It exclude code in terminal module ( crate ), but include for aggregating module ( crate ). #[ macro_export ] macro_rules! only_for_terminal_module { @@ -7,6 +9,8 @@ macro_rules! only_for_terminal_module } } +/// Mechanism to include tests only to aggregating crate. +/// It exclude code in terminal module ( crate ), but include for aggregating module ( crate ). #[ macro_export ] macro_rules! only_for_aggregating_module { diff --git a/module/step/meta/tests/smoke_test.rs b/module/step/meta/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/step/meta/tests/smoke_test.rs +++ b/module/step/meta/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/template/layer/layer.rs b/module/template/layer/layer.rs index 45d766e897..b4b8322d92 100644 --- a/module/template/layer/layer.rs +++ b/module/template/layer/layer.rs @@ -1,4 +1,4 @@ -/// Internal namespace. +/// Define a private namespace for all its items. mod private { use super::super::*; diff --git a/module/template/template_alias/License b/module/template/template_alias/License index 6d5ef8559f..72c80c1308 100644 --- a/module/template/template_alias/License +++ b/module/template/template_alias/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/tests/smoke_test.rs b/module/template/template_alias/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/template/template_alias/tests/smoke_test.rs +++ b/module/template/template_alias/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/template/template_blank/License b/module/template/template_blank/License index 6d5ef8559f..72c80c1308 100644 --- a/module/template/template_blank/License +++ b/module/template/template_blank/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/tests/inc/mod.rs b/module/template/template_blank/tests/inc/mod.rs index dde9de6f94..7c40be710f 100644 --- a/module/template/template_blank/tests/inc/mod.rs +++ b/module/template/template_blank/tests/inc/mod.rs @@ -1,4 +1,4 @@ -#[ allow( unused_imports ) ] use super::*; +use test_tools::exposed::*; mod basic_test; diff --git a/module/template/template_procedural_macro/License b/module/template/template_procedural_macro/License index e3e9e057cf..a23529f45b 100644 --- a/module/template/template_procedural_macro/License +++ b/module/template/template_procedural_macro/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/tests/smoke_test.rs b/module/template/template_procedural_macro/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/template/template_procedural_macro/tests/smoke_test.rs +++ b/module/template/template_procedural_macro/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/template/template_procedural_macro_meta/License b/module/template/template_procedural_macro_meta/License index e3e9e057cf..a23529f45b 100644 --- a/module/template/template_procedural_macro_meta/License +++ b/module/template/template_procedural_macro_meta/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/tests/smoke_test.rs b/module/template/template_procedural_macro_meta/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/template/template_procedural_macro_meta/tests/smoke_test.rs +++ b/module/template/template_procedural_macro_meta/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/module/template/template_procedural_macro_runtime/License b/module/template/template_procedural_macro_runtime/License index e3e9e057cf..a23529f45b 100644 --- a/module/template/template_procedural_macro_runtime/License +++ b/module/template/template_procedural_macro_runtime/License @@ -1,4 +1,4 @@ -Copyright Kostiantyn W and Out of the Box Systems (c) 2013-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/tests/smoke_test.rs b/module/template/template_procedural_macro_runtime/tests/smoke_test.rs index 828e9b016b..c9b1b4daae 100644 --- a/module/template/template_procedural_macro_runtime/tests/smoke_test.rs +++ b/module/template/template_procedural_macro_runtime/tests/smoke_test.rs @@ -1,4 +1,4 @@ - +//! Smoke testing of the package. #[ test ] fn local_smoke_test() @@ -6,7 +6,6 @@ fn local_smoke_test() ::test_tools::smoke_test_for_local_run(); } - #[ test ] fn published_smoke_test() { diff --git a/step/Cargo.toml b/step/Cargo.toml index 70f00c8c96..6e37d39bd0 100644 --- a/step/Cargo.toml +++ b/step/Cargo.toml @@ -21,4 +21,4 @@ all-features = false willbe = { workspace = true, features = [ "full" ] } [dev-dependencies] -test_tools = { workspace = true } +# test_tools = { workspace = true }