From f07045399ba278176f98464e84b4ba79e61d48de Mon Sep 17 00:00:00 2001 From: Dei Vilkinsons <6226576+nonparibus@users.noreply.github.com> Date: Tue, 9 Apr 2024 09:47:33 +0200 Subject: [PATCH] Add Turbine from BP incubator --- .cargo/config.toml | 99 ++ .gitignore | 17 +- libs/turbine-transformer/.DS_Store | Bin 0 -> 6148 bytes libs/turbine-transformer/.cargo/config.toml | 99 ++ libs/turbine-transformer/Cargo.toml | 17 + libs/turbine-transformer/README.md | 75 ++ libs/turbine-transformer/rust-toolchain.toml | 3 + libs/turbine-transformer/rustfmt.toml | 30 + libs/turbine-transformer/src/.DS_Store | Bin 0 -> 6148 bytes libs/turbine-transformer/src/lib.rs | 182 ++++ libs/turbine-transformer/src/path.rs | 168 ++++ libs/turbine-transformer/src/property.rs | 5 + .../src/property/select.rs | 265 ++++++ .../src/property/update.rs | 161 ++++ libs/turbine-transformer/src/reachable.rs | 110 +++ libs/turbine-transformer/src/select.rs | 254 ++++++ libs/turbine-transformer/src/select/clause.rs | 67 ++ .../turbine-transformer/src/select/dynamic.rs | 46 + .../src/select/property.rs | 324 +++++++ libs/turbine-transformer/src/select/type_.rs | 83 ++ libs/turbine-transformer/src/value.rs | 306 +++++++ libs/turbine/.DS_Store | Bin 0 -> 6148 bytes libs/turbine/.cargo/config.toml | 99 ++ libs/turbine/Cargo.toml | 20 + libs/turbine/README.md | 59 ++ libs/turbine/bin/cli/Cargo.toml | 23 + libs/turbine/bin/cli/src/cmd_lib.rs | 369 ++++++++ libs/turbine/bin/cli/src/main.rs | 31 + libs/turbine/lib/.DS_Store | Bin 0 -> 6148 bytes libs/turbine/lib/codegen/Cargo.toml | 31 + libs/turbine/lib/codegen/clippy.toml | 4 + libs/turbine/lib/codegen/src/analysis.rs | 397 +++++++++ .../turbine/lib/codegen/src/analysis/facts.rs | 25 + .../turbine/lib/codegen/src/analysis/unify.rs | 295 ++++++ libs/turbine/lib/codegen/src/data.rs | 69 ++ libs/turbine/lib/codegen/src/entity.rs | 786 ++++++++++++++++ libs/turbine/lib/codegen/src/error.rs | 41 + libs/turbine/lib/codegen/src/graph.rs | 350 ++++++++ libs/turbine/lib/codegen/src/lib.rs | 247 +++++ libs/turbine/lib/codegen/src/name.rs | 681 ++++++++++++++ libs/turbine/lib/codegen/src/property.rs | 395 ++++++++ .../turbine/lib/codegen/src/property/inner.rs | 180 ++++ .../codegen/src/property/property_value.rs | 625 +++++++++++++ .../turbine/lib/codegen/src/property/type_.rs | 288 ++++++ libs/turbine/lib/codegen/src/shared.rs | 599 +++++++++++++ libs/turbine/lib/codegen/src/utilities.rs | 40 + libs/turbine/lib/codegen/tests/clippy.toml | 4 + .../tests/snapshots/01-property-type-ref.json | 21 + .../snapshots/01-property-type-ref.stdout | 101 +++ .../02-property-type-oneOf-data-type.json | 32 + .../02-property-type-oneOf-data-type.stdout | 196 ++++ .../snapshots/03-property-type-object.json | 57 ++ .../snapshots/03-property-type-object.stdout | 481 ++++++++++ .../snapshots/04-property-type-array.json | 29 + .../snapshots/04-property-type-array.stdout | 194 ++++ .../05-property-type-oneOf-array.json | 32 + .../05-property-type-oneOf-array.stdout | 290 ++++++ .../06-property-type-self-referential.json | 44 + .../06-property-type-self-referential.stdout | 391 ++++++++ .../07-property-type-object-nested.json | 71 ++ .../07-property-type-object-nested.stdout | 738 +++++++++++++++ .../tests/snapshots/08-entity-type-empty.json | 14 + .../snapshots/08-entity-type-empty.stdout | 210 +++++ .../09-entity-type-single-property.json | 40 + .../09-entity-type-single-property.stdout | 388 ++++++++ ...-entity-type-single-property-optional.json | 38 + ...ntity-type-single-property-optional.stdout | 391 ++++++++ .../11-entity-type-multiple-properties.json | 56 ++ .../11-entity-type-multiple-properties.stdout | 557 ++++++++++++ .../12-entity-multiple-versions.json | 74 ++ .../12-entity-multiple-versions.stdout | 843 ++++++++++++++++++ .../13-entity-duplicate-identifier.json | 56 ++ .../13-entity-duplicate-identifier.stdout | 552 ++++++++++++ .../snapshots/14-entity-properties-clash.json | 40 + .../14-entity-properties-clash.stdout | 391 ++++++++ .../tests/snapshots/15-entity-link.json | 18 + .../tests/snapshots/15-entity-link.stdout | 238 +++++ .../tests/snapshots/16-data-type-custom.json | 10 + .../snapshots/16-data-type-custom.stdout | 3 + .../17-property-type-multiple-versions.json | 40 + .../17-property-type-multiple-versions.stdout | 206 +++++ .../18-property-type-data-type-clash.json | 40 + .../18-property-type-data-type-clash.stdout | 380 ++++++++ .../19-property-type-inner-type-name.json | 39 + .../19-property-type-inner-type-name.stdout | 295 ++++++ .../lib/codegen/tests/test_snapshots.rs | 98 ++ libs/turbine/lib/skeletor/Cargo.toml | 26 + libs/turbine/lib/skeletor/README.md | 29 + libs/turbine/lib/skeletor/src/cargo.rs | 259 ++++++ libs/turbine/lib/skeletor/src/lib.rs | 183 ++++ libs/turbine/lib/skeletor/src/vfs.rs | 259 ++++++ .../lib/skeletor/templates/Cargo.toml.askama | 13 + libs/turbine/lib/turbine/Cargo.toml | 17 + libs/turbine/lib/turbine/src/entity.rs | 251 ++++++ .../lib/turbine/src/entity/interval.rs | 44 + libs/turbine/lib/turbine/src/error.rs | 33 + libs/turbine/lib/turbine/src/hierarchy.rs | 69 ++ libs/turbine/lib/turbine/src/lib.rs | 295 ++++++ libs/turbine/lib/turbine/src/path.rs | 114 +++ libs/turbine/lib/turbine/src/polyfill.rs | 115 +++ libs/turbine/lib/turbine/src/serialize.rs | 62 ++ libs/turbine/lib/turbine/src/types.rs | 1 + libs/turbine/lib/turbine/src/types/data.rs | 23 + .../lib/turbine/src/types/data/boolean.rs | 157 ++++ .../lib/turbine/src/types/data/empty_list.rs | 120 +++ .../lib/turbine/src/types/data/null.rs | 95 ++ .../lib/turbine/src/types/data/number.rs | 174 ++++ .../lib/turbine/src/types/data/object.rs | 179 ++++ .../lib/turbine/src/types/data/text.rs | 187 ++++ libs/turbine/rust-toolchain.toml | 3 + libs/turbine/rustfmt.toml | 30 + 111 files changed, 18400 insertions(+), 1 deletion(-) create mode 100644 .cargo/config.toml create mode 100644 libs/turbine-transformer/.DS_Store create mode 100644 libs/turbine-transformer/.cargo/config.toml create mode 100644 libs/turbine-transformer/Cargo.toml create mode 100644 libs/turbine-transformer/README.md create mode 100644 libs/turbine-transformer/rust-toolchain.toml create mode 100644 libs/turbine-transformer/rustfmt.toml create mode 100644 libs/turbine-transformer/src/.DS_Store create mode 100644 libs/turbine-transformer/src/lib.rs create mode 100644 libs/turbine-transformer/src/path.rs create mode 100644 libs/turbine-transformer/src/property.rs create mode 100644 libs/turbine-transformer/src/property/select.rs create mode 100644 libs/turbine-transformer/src/property/update.rs create mode 100644 libs/turbine-transformer/src/reachable.rs create mode 100644 libs/turbine-transformer/src/select.rs create mode 100644 libs/turbine-transformer/src/select/clause.rs create mode 100644 libs/turbine-transformer/src/select/dynamic.rs create mode 100644 libs/turbine-transformer/src/select/property.rs create mode 100644 libs/turbine-transformer/src/select/type_.rs create mode 100644 libs/turbine-transformer/src/value.rs create mode 100644 libs/turbine/.DS_Store create mode 100644 libs/turbine/.cargo/config.toml create mode 100644 libs/turbine/Cargo.toml create mode 100644 libs/turbine/README.md create mode 100644 libs/turbine/bin/cli/Cargo.toml create mode 100644 libs/turbine/bin/cli/src/cmd_lib.rs create mode 100644 libs/turbine/bin/cli/src/main.rs create mode 100644 libs/turbine/lib/.DS_Store create mode 100644 libs/turbine/lib/codegen/Cargo.toml create mode 100644 libs/turbine/lib/codegen/clippy.toml create mode 100644 libs/turbine/lib/codegen/src/analysis.rs create mode 100644 libs/turbine/lib/codegen/src/analysis/facts.rs create mode 100644 libs/turbine/lib/codegen/src/analysis/unify.rs create mode 100644 libs/turbine/lib/codegen/src/data.rs create mode 100644 libs/turbine/lib/codegen/src/entity.rs create mode 100644 libs/turbine/lib/codegen/src/error.rs create mode 100644 libs/turbine/lib/codegen/src/graph.rs create mode 100644 libs/turbine/lib/codegen/src/lib.rs create mode 100644 libs/turbine/lib/codegen/src/name.rs create mode 100644 libs/turbine/lib/codegen/src/property.rs create mode 100644 libs/turbine/lib/codegen/src/property/inner.rs create mode 100644 libs/turbine/lib/codegen/src/property/property_value.rs create mode 100644 libs/turbine/lib/codegen/src/property/type_.rs create mode 100644 libs/turbine/lib/codegen/src/shared.rs create mode 100644 libs/turbine/lib/codegen/src/utilities.rs create mode 100644 libs/turbine/lib/codegen/tests/clippy.toml create mode 100644 libs/turbine/lib/codegen/tests/snapshots/01-property-type-ref.json create mode 100644 libs/turbine/lib/codegen/tests/snapshots/01-property-type-ref.stdout create mode 100644 libs/turbine/lib/codegen/tests/snapshots/02-property-type-oneOf-data-type.json create mode 100644 libs/turbine/lib/codegen/tests/snapshots/02-property-type-oneOf-data-type.stdout create mode 100644 libs/turbine/lib/codegen/tests/snapshots/03-property-type-object.json create mode 100644 libs/turbine/lib/codegen/tests/snapshots/03-property-type-object.stdout create mode 100644 libs/turbine/lib/codegen/tests/snapshots/04-property-type-array.json create mode 100644 libs/turbine/lib/codegen/tests/snapshots/04-property-type-array.stdout create mode 100644 libs/turbine/lib/codegen/tests/snapshots/05-property-type-oneOf-array.json create mode 100644 libs/turbine/lib/codegen/tests/snapshots/05-property-type-oneOf-array.stdout create mode 100644 libs/turbine/lib/codegen/tests/snapshots/06-property-type-self-referential.json create mode 100644 libs/turbine/lib/codegen/tests/snapshots/06-property-type-self-referential.stdout create mode 100644 libs/turbine/lib/codegen/tests/snapshots/07-property-type-object-nested.json create mode 100644 libs/turbine/lib/codegen/tests/snapshots/07-property-type-object-nested.stdout create mode 100644 libs/turbine/lib/codegen/tests/snapshots/08-entity-type-empty.json create mode 100644 libs/turbine/lib/codegen/tests/snapshots/08-entity-type-empty.stdout create mode 100644 libs/turbine/lib/codegen/tests/snapshots/09-entity-type-single-property.json create mode 100644 libs/turbine/lib/codegen/tests/snapshots/09-entity-type-single-property.stdout create mode 100644 libs/turbine/lib/codegen/tests/snapshots/10-entity-type-single-property-optional.json create mode 100644 libs/turbine/lib/codegen/tests/snapshots/10-entity-type-single-property-optional.stdout create mode 100644 libs/turbine/lib/codegen/tests/snapshots/11-entity-type-multiple-properties.json create mode 100644 libs/turbine/lib/codegen/tests/snapshots/11-entity-type-multiple-properties.stdout create mode 100644 libs/turbine/lib/codegen/tests/snapshots/12-entity-multiple-versions.json create mode 100644 libs/turbine/lib/codegen/tests/snapshots/12-entity-multiple-versions.stdout create mode 100644 libs/turbine/lib/codegen/tests/snapshots/13-entity-duplicate-identifier.json create mode 100644 libs/turbine/lib/codegen/tests/snapshots/13-entity-duplicate-identifier.stdout create mode 100644 libs/turbine/lib/codegen/tests/snapshots/14-entity-properties-clash.json create mode 100644 libs/turbine/lib/codegen/tests/snapshots/14-entity-properties-clash.stdout create mode 100644 libs/turbine/lib/codegen/tests/snapshots/15-entity-link.json create mode 100644 libs/turbine/lib/codegen/tests/snapshots/15-entity-link.stdout create mode 100644 libs/turbine/lib/codegen/tests/snapshots/16-data-type-custom.json create mode 100644 libs/turbine/lib/codegen/tests/snapshots/16-data-type-custom.stdout create mode 100644 libs/turbine/lib/codegen/tests/snapshots/17-property-type-multiple-versions.json create mode 100644 libs/turbine/lib/codegen/tests/snapshots/17-property-type-multiple-versions.stdout create mode 100644 libs/turbine/lib/codegen/tests/snapshots/18-property-type-data-type-clash.json create mode 100644 libs/turbine/lib/codegen/tests/snapshots/18-property-type-data-type-clash.stdout create mode 100644 libs/turbine/lib/codegen/tests/snapshots/19-property-type-inner-type-name.json create mode 100644 libs/turbine/lib/codegen/tests/snapshots/19-property-type-inner-type-name.stdout create mode 100644 libs/turbine/lib/codegen/tests/test_snapshots.rs create mode 100644 libs/turbine/lib/skeletor/Cargo.toml create mode 100644 libs/turbine/lib/skeletor/README.md create mode 100644 libs/turbine/lib/skeletor/src/cargo.rs create mode 100644 libs/turbine/lib/skeletor/src/lib.rs create mode 100644 libs/turbine/lib/skeletor/src/vfs.rs create mode 100644 libs/turbine/lib/skeletor/templates/Cargo.toml.askama create mode 100644 libs/turbine/lib/turbine/Cargo.toml create mode 100644 libs/turbine/lib/turbine/src/entity.rs create mode 100644 libs/turbine/lib/turbine/src/entity/interval.rs create mode 100644 libs/turbine/lib/turbine/src/error.rs create mode 100644 libs/turbine/lib/turbine/src/hierarchy.rs create mode 100644 libs/turbine/lib/turbine/src/lib.rs create mode 100644 libs/turbine/lib/turbine/src/path.rs create mode 100644 libs/turbine/lib/turbine/src/polyfill.rs create mode 100644 libs/turbine/lib/turbine/src/serialize.rs create mode 100644 libs/turbine/lib/turbine/src/types.rs create mode 100644 libs/turbine/lib/turbine/src/types/data.rs create mode 100644 libs/turbine/lib/turbine/src/types/data/boolean.rs create mode 100644 libs/turbine/lib/turbine/src/types/data/empty_list.rs create mode 100644 libs/turbine/lib/turbine/src/types/data/null.rs create mode 100644 libs/turbine/lib/turbine/src/types/data/number.rs create mode 100644 libs/turbine/lib/turbine/src/types/data/object.rs create mode 100644 libs/turbine/lib/turbine/src/types/data/text.rs create mode 100644 libs/turbine/rust-toolchain.toml create mode 100644 libs/turbine/rustfmt.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..581e605 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,99 @@ +[target.'cfg(all())'] +rustflags = [ + "-Wclippy::all", + "-Wclippy::as_underscore", + "-Wclippy::await_holding_lock", + "-Wclippy::char_lit_as_u8", + "-Wclippy::checked_conversions", + "-Wclippy::clone_on_ref_ptr", + "-Wclippy::create_dir", + "-Wclippy::dbg_macro", + "-Wclippy::debug_assert_with_mut_call", + "-Wclippy::default_union_representation", + "-Wclippy::deref_by_slicing", + "-Wclippy::doc_markdown", + "-Wclippy::empty_enum", + "-Wclippy::empty_structs_with_brackets", + "-Wclippy::enum_glob_use", + "-Wclippy::exit", + "-Wclippy::expl_impl_clone_on_copy", + "-Wclippy::explicit_deref_methods", + "-Wclippy::explicit_into_iter_loop", + "-Wclippy::fallible_impl_from", + "-Wclippy::filetype_is_file", + "-Wclippy::filter_map_next", + "-Wclippy::flat_map_option", + "-Wclippy::float_cmp_const", + "-Wclippy::fn_params_excessive_bools", + "-Wclippy::from_iter_instead_of_collect", + "-Wclippy::get_unwrap", + "-Wclippy::if_let_mutex", + "-Wclippy::if_then_some_else_none", + "-Wclippy::implicit_clone", + "-Wclippy::imprecise_flops", + "-Wclippy::inefficient_to_string", + "-Wclippy::invalid_upcast_comparisons", + "-Wclippy::large_digit_groups", + "-Wclippy::large_stack_arrays", + "-Wclippy::large_types_passed_by_value", + "-Wclippy::let_unit_value", + "-Wclippy::linkedlist", + "-Wclippy::lossy_float_literal", + "-Wclippy::macro_use_imports", + "-Wclippy::manual_ok_or", + "-Wclippy::map_err_ignore", + "-Wclippy::map_flatten", + "-Wclippy::map_unwrap_or", + "-Wclippy::match_on_vec_items", + "-Wclippy::match_same_arms", + "-Wclippy::match_wild_err_arm", + "-Wclippy::match_wildcard_for_single_variants", + "-Wclippy::mem_forget", + "-Wclippy::mismatched_target_os", + "-Wclippy::missing_enforced_import_renames", + "-Wclippy::mod_module_files", + "-Wclippy::mut_mut", + "-Wclippy::mutex_integer", + "-Wclippy::needless_borrow", + "-Wclippy::needless_continue", + "-Wclippy::needless_for_each", + "-Wclippy::nursery", + "-Wclippy::option_option", + "-Wclippy::path_buf_push_overwrite", + "-Wclippy::pedantic", + "-Wclippy::print_stderr", + "-Wclippy::print_stdout", + "-Wclippy::ptr_as_ptr", + "-Wclippy::rc_buffer", + "-Wclippy::rc_mutex", + "-Wclippy::ref_option_ref", + "-Wclippy::rest_pat_in_fully_bound_structs", + "-Wclippy::same_functions_in_if_condition", + "-Wclippy::same_name_method", + "-Wclippy::semicolon_if_nothing_returned", + "-Wclippy::single_match_else", + "-Wclippy::str_to_string", + "-Wclippy::string_add", + "-Wclippy::string_add_assign", + "-Wclippy::string_lit_as_bytes", + "-Wclippy::string_slice", + "-Wclippy::string_to_string", + "-Wclippy::todo", + "-Wclippy::trait_duplication_in_bounds", + "-Wclippy::try_err", + "-Wclippy::undocumented_unsafe_blocks", + "-Wclippy::unnecessary_self_imports", + "-Wclippy::unnested_or_patterns", + "-Wclippy::unused_self", + "-Wclippy::unwrap_used", + "-Wclippy::use_debug", + "-Wclippy::useless_transmute", + "-Wclippy::verbose_file_reads", + "-Wclippy::zero_sized_map_values", + "-Wfuture_incompatible", + "-Wnonstandard_style", + "-Wunreachable_pub", + "-Aclippy::module_name_repetitions", + "-Aclippy::redundant_pub_crate", + "-Amissing_docs", +] diff --git a/.gitignore b/.gitignore index ebf9c4e..f107017 100644 --- a/.gitignore +++ b/.gitignore @@ -11,5 +11,20 @@ .config/**/*.log dist/ node_modules/ -target/ test_artifacts/ + +### Rust template +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/libs/turbine-transformer/.DS_Store b/libs/turbine-transformer/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..f00ba4ae60175dc0f5d0d2bdc367b86a19657d6b GIT binary patch literal 6148 zcmeHKy-ve05I)lgMS`Itqj>?OPONDao}d$Afu>R!8l@`g?uX!&*mwZM$Xmd~cR$cJ ziNuCbxs&YAz8|0SN%7f46egSbh-gSeIT~Z&L65My*K}mgYz(r-f|})GG_9I?)w9rF zRPo-A>4GY{qcwH*fBMn3*G*Z@mdyI0OHa0n}`k{6x`vXTTY72EG~K^C6-! z7KTYtjt+G41OR4Gi(o8wSzu%ZurN%D&_IHQ0yQ+)6C-Fi^wInZ!=$L;6eKg&vCLnO zmmt}pkK#^#(^Ve4cLv%F3{)}X`hSIAruUKG4)VD(;0zoT10gG?KIyzresJBI8JE literal 0 HcmV?d00001 diff --git a/libs/turbine-transformer/.cargo/config.toml b/libs/turbine-transformer/.cargo/config.toml new file mode 100644 index 0000000..581e605 --- /dev/null +++ b/libs/turbine-transformer/.cargo/config.toml @@ -0,0 +1,99 @@ +[target.'cfg(all())'] +rustflags = [ + "-Wclippy::all", + "-Wclippy::as_underscore", + "-Wclippy::await_holding_lock", + "-Wclippy::char_lit_as_u8", + "-Wclippy::checked_conversions", + "-Wclippy::clone_on_ref_ptr", + "-Wclippy::create_dir", + "-Wclippy::dbg_macro", + "-Wclippy::debug_assert_with_mut_call", + "-Wclippy::default_union_representation", + "-Wclippy::deref_by_slicing", + "-Wclippy::doc_markdown", + "-Wclippy::empty_enum", + "-Wclippy::empty_structs_with_brackets", + "-Wclippy::enum_glob_use", + "-Wclippy::exit", + "-Wclippy::expl_impl_clone_on_copy", + "-Wclippy::explicit_deref_methods", + "-Wclippy::explicit_into_iter_loop", + "-Wclippy::fallible_impl_from", + "-Wclippy::filetype_is_file", + "-Wclippy::filter_map_next", + "-Wclippy::flat_map_option", + "-Wclippy::float_cmp_const", + "-Wclippy::fn_params_excessive_bools", + "-Wclippy::from_iter_instead_of_collect", + "-Wclippy::get_unwrap", + "-Wclippy::if_let_mutex", + "-Wclippy::if_then_some_else_none", + "-Wclippy::implicit_clone", + "-Wclippy::imprecise_flops", + "-Wclippy::inefficient_to_string", + "-Wclippy::invalid_upcast_comparisons", + "-Wclippy::large_digit_groups", + "-Wclippy::large_stack_arrays", + "-Wclippy::large_types_passed_by_value", + "-Wclippy::let_unit_value", + "-Wclippy::linkedlist", + "-Wclippy::lossy_float_literal", + "-Wclippy::macro_use_imports", + "-Wclippy::manual_ok_or", + "-Wclippy::map_err_ignore", + "-Wclippy::map_flatten", + "-Wclippy::map_unwrap_or", + "-Wclippy::match_on_vec_items", + "-Wclippy::match_same_arms", + "-Wclippy::match_wild_err_arm", + "-Wclippy::match_wildcard_for_single_variants", + "-Wclippy::mem_forget", + "-Wclippy::mismatched_target_os", + "-Wclippy::missing_enforced_import_renames", + "-Wclippy::mod_module_files", + "-Wclippy::mut_mut", + "-Wclippy::mutex_integer", + "-Wclippy::needless_borrow", + "-Wclippy::needless_continue", + "-Wclippy::needless_for_each", + "-Wclippy::nursery", + "-Wclippy::option_option", + "-Wclippy::path_buf_push_overwrite", + "-Wclippy::pedantic", + "-Wclippy::print_stderr", + "-Wclippy::print_stdout", + "-Wclippy::ptr_as_ptr", + "-Wclippy::rc_buffer", + "-Wclippy::rc_mutex", + "-Wclippy::ref_option_ref", + "-Wclippy::rest_pat_in_fully_bound_structs", + "-Wclippy::same_functions_in_if_condition", + "-Wclippy::same_name_method", + "-Wclippy::semicolon_if_nothing_returned", + "-Wclippy::single_match_else", + "-Wclippy::str_to_string", + "-Wclippy::string_add", + "-Wclippy::string_add_assign", + "-Wclippy::string_lit_as_bytes", + "-Wclippy::string_slice", + "-Wclippy::string_to_string", + "-Wclippy::todo", + "-Wclippy::trait_duplication_in_bounds", + "-Wclippy::try_err", + "-Wclippy::undocumented_unsafe_blocks", + "-Wclippy::unnecessary_self_imports", + "-Wclippy::unnested_or_patterns", + "-Wclippy::unused_self", + "-Wclippy::unwrap_used", + "-Wclippy::use_debug", + "-Wclippy::useless_transmute", + "-Wclippy::verbose_file_reads", + "-Wclippy::zero_sized_map_values", + "-Wfuture_incompatible", + "-Wnonstandard_style", + "-Wunreachable_pub", + "-Aclippy::module_name_repetitions", + "-Aclippy::redundant_pub_crate", + "-Amissing_docs", +] diff --git a/libs/turbine-transformer/Cargo.toml b/libs/turbine-transformer/Cargo.toml new file mode 100644 index 0000000..d926999 --- /dev/null +++ b/libs/turbine-transformer/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "turbine-transformer" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +petgraph = "0.6.3" +error-stack = { version = "0.4.1", default-features = false } +funty = "3.0.0-rc2" +serde_json = "1.0.96" +ordered-float = "3.7.0" +paste = "1.0.12" +hashbrown = "0.13.2" + +turbine = { path = "../turbine/lib/turbine" } diff --git a/libs/turbine-transformer/README.md b/libs/turbine-transformer/README.md new file mode 100644 index 0000000..f64d97c --- /dev/null +++ b/libs/turbine-transformer/README.md @@ -0,0 +1,75 @@ +# `turbine-transformer` + +> Origin of the name: A wind turbine uses wind to generate electricity through rotation. The transformer is used to +> transform the voltage. This project aims to take the input from the turbine and transform it, by applying a set of +> instructions. + + +The goal of the project is to supplement the HASH REST-APIs query abilities with a more powerful query language. This +has some trade-offs, you will still need to load in all entities from HASH, but you can then filter them down to the +ones you want. + +This is _very_ early in development, and is not ready for production use. Tests are missing, and the API is not stable. +Especially the names of the different types are likely to change. Do not expect this to be usable in production, over +the next weeks and months I will be working on this project to make it more stable and usable. + +## Examples + +```rust +use turbine_transformer::View; + +fn main() { + let view = View::new(&mut subgraph.entities); + let always_include = /* entity id */; + + view.select(vec![ + Statement::type_() + .or_id(always_include) + .or_type::() + .or_type::() + .or_inherits_from::() + .and( + PropertyMatch::equals( + JsonPath::new().then::(), + "John Doe" + ) + ) + .with_links() + .with_left( + TypeMatch::new() + .or_type::() + .or_type::() + ) + .with_right( + TypeMatch::new() + .or_type::() + .or_type::() + ) + ]); + + // You can also update selected entities + view.select_properties(vec![ + Select::new(TypeMatch::new().or_type::(), Action::Exclude) + .do_(StaticAction::new::()) + .do_(StaticAction::new::()) + .do_(StaticAction::new::()) + ]); + + // ... or change the value of specific properties + view.update_properties(vec![ + Update::new(TypeMatch::new().or_type::()) + .do_(StaticUpdate::new::("John Doe")) + ]); + + // or remap a specific user name to a different value + view.update_properties(vec![ + Update::new(TypeMatch::new().or_type::().or( + PropertyMatch::equals( + JsonPath::new().then::(), + "John Doe" + ) + )) + .do_(StaticUpdate::new::("Doe John")) + ]); +} +``` diff --git a/libs/turbine-transformer/rust-toolchain.toml b/libs/turbine-transformer/rust-toolchain.toml new file mode 100644 index 0000000..8183ca1 --- /dev/null +++ b/libs/turbine-transformer/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly-2023-08-28" +components = ["cargo", "clippy", "rustfmt", "rust-std", "rust-src"] diff --git a/libs/turbine-transformer/rustfmt.toml b/libs/turbine-transformer/rustfmt.toml new file mode 100644 index 0000000..1e35be7 --- /dev/null +++ b/libs/turbine-transformer/rustfmt.toml @@ -0,0 +1,30 @@ +# General +edition = "2021" # Default: "2015" +unstable_features = true # Default: false +version = "Two" # Default: "One" + +# Settings +condense_wildcard_suffixes = true # Default: false +overflow_delimited_expr = true # Default: false +reorder_impl_items = true # Default: false +use_field_init_shorthand = true # Default: false +use_try_shorthand = true # Default: false +wrap_comments = true # Default: false + +# Parameters +comment_width = 100 # Default: 80 +hex_literal_case = "Upper" # Default: "Preserve" + +# Areas +format_code_in_doc_comments = true # Default: false +format_generated_files = true # Default: false +format_macro_matchers = true # Default: false +format_macro_bodies = true # Default: false +format_strings = true # Default: false + +# Imports +imports_granularity = "Crate" # Default: "Preserve" +group_imports = "StdExternalCrate" # Default: "Preserve" + +# Diagnostics +error_on_unformatted = true # Default: false diff --git a/libs/turbine-transformer/src/.DS_Store b/libs/turbine-transformer/src/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..baa29cd81665a3f1fefdc316b78b6cb86f9768de GIT binary patch literal 6148 zcmeHKu};H44E2==1+i3OVZ51%Y>c5wzaR^UrD}^{NOTdDfh~W)!jB;F3;Y8!U&8a* zY8ui?ObAuBKLEkfN$!e=kQ6xN}9TrOy%F<=bz890!~G1vd;?*6|YWKYI`F|bz* zxTGkH30_HS>)_?M)_Uj+%EErF;5r18Sc>7xrT7#Y1?!$Cz+ABv#0tdz2t*oeFb4jV Ffp01-P+tH5 literal 0 HcmV?d00001 diff --git a/libs/turbine-transformer/src/lib.rs b/libs/turbine-transformer/src/lib.rs new file mode 100644 index 0000000..5f16fbc --- /dev/null +++ b/libs/turbine-transformer/src/lib.rs @@ -0,0 +1,182 @@ +#![no_std] +#![feature(error_in_core)] +#![feature(impl_trait_in_assoc_type)] + +mod path; +pub mod property; +mod reachable; +pub mod select; +mod value; + +extern crate alloc; + +use alloc::collections::{BTreeMap, BTreeSet}; + +use petgraph::{graph::NodeIndex, Graph}; +use turbine::{ + entity::{Entity, EntityId, LinkData}, + VersionedUrl, VersionedUrlRef, +}; + +const fn no_lookup(_: VersionedUrlRef) -> BTreeSet> { + BTreeSet::new() +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum LinkEdge { + Left, + Right, +} + +pub struct View<'a> { + graph: Graph, + entities: &'a mut [Entity], + + exclude: BTreeSet, + + lookup: BTreeMap, + lookup_index: BTreeMap, + lookup_inherits_from: fn(VersionedUrlRef) -> BTreeSet>, +} + +impl<'a> View<'a> { + fn empty() -> Self { + Self { + graph: Graph::new(), + entities: &mut [], + + exclude: BTreeSet::new(), + + lookup: BTreeMap::new(), + lookup_index: BTreeMap::new(), + lookup_inherits_from: no_lookup, + } + } + + fn prepare(&mut self, entities: &[Entity]) { + for (index, entity) in entities.iter().enumerate() { + self.lookup_index + .insert(entity.metadata.record_id.entity_id, index); + } + } + + fn get_or_create(&mut self, id: EntityId) -> NodeIndex { + if let Some(node) = self.lookup.get(&id) { + return *node; + } + + let node = self.graph.add_node(id); + self.lookup.insert(id, node); + + node + } + + fn exclude_complement(&mut self, nodes: &BTreeSet) { + let indices: BTreeSet<_> = self.graph.node_indices().collect(); + + let complement = &indices - nodes; + self.exclude = &complement | &self.exclude; + } + + fn exclude(&mut self, nodes: &BTreeSet) { + self.exclude = nodes | &self.exclude; + } + + #[must_use] + pub fn new(entities: &'a mut [Entity]) -> Self { + let mut this = Self::empty(); + this.prepare(entities); + + for (index, entity) in entities.iter().enumerate() { + let node = this.get_or_create(entity.metadata.record_id.entity_id); + this.lookup_index + .insert(entity.metadata.record_id.entity_id, index); + + if let Some(link_data) = entity.link_data { + let lhs = this.get_or_create(link_data.left_entity_id); + let rhs = this.get_or_create(link_data.right_entity_id); + + this.graph.add_edge(lhs, node, LinkEdge::Left); + this.graph.add_edge(node, rhs, LinkEdge::Right); + } + } + + this.entities = entities; + this + } + + #[must_use] + pub const fn entities(&self) -> &[Entity] { + self.entities + } + + #[must_use] + pub fn entity(&self, id: EntityId) -> Option<&Entity> { + let index = *self.lookup_index.get(&id)?; + + self.entities.get(index) + } + + #[must_use] + fn entity_type(&self, id: EntityId) -> Option { + let index = *self.lookup_index.get(&id)?; + let entity = self.entities.get(index)?; + + Some(VersionedUrlRef::from(&entity.metadata.entity_type_id)) + } + + #[must_use] + fn entity_link(&self, id: EntityId) -> Option { + let index = *self.lookup_index.get(&id)?; + let entity = self.entities.get(index)?; + + entity.link_data + } + + #[must_use] + pub const fn graph(&self) -> &Graph { + &self.graph + } + + #[must_use] + pub const fn excluded(&self) -> &BTreeSet { + &self.exclude + } + + #[must_use] + pub const fn lookup(&self) -> &BTreeMap { + &self.lookup + } + + #[must_use] + pub fn with_inherits_from( + mut self, + lookup_inherits_from: fn(VersionedUrlRef) -> BTreeSet>, + ) -> Self { + self.lookup_inherits_from = lookup_inherits_from; + + self + } +} + +impl<'a> IntoIterator for View<'a> { + type Item = &'a Entity; + + type IntoIter = impl Iterator; + + fn into_iter(self) -> Self::IntoIter { + self.entities.iter().filter(move |entity| { + let Some(node) = self.lookup.get(&entity.metadata.record_id.entity_id) else { + return false; + }; + + !self.exclude.contains(node) + }) + } +} + +#[cfg(test)] +mod tests { + #[test] + fn compile() {} +} diff --git a/libs/turbine-transformer/src/path.rs b/libs/turbine-transformer/src/path.rs new file mode 100644 index 0000000..477857f --- /dev/null +++ b/libs/turbine-transformer/src/path.rs @@ -0,0 +1,168 @@ +use alloc::{ + borrow::{Cow, ToOwned}, + vec::Vec, +}; + +use turbine::{entity::Entity, BaseUrl, BaseUrlRef, TypeUrl}; + +use crate::value::{Object, Value}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Segment<'a> { + Field(Cow<'a, str>), + Index(usize), +} + +impl<'a> From> for Segment<'a> { + fn from(value: BaseUrlRef<'a>) -> Self { + Self::Field(Cow::Borrowed(value.as_str())) + } +} + +impl<'a> From for Segment<'a> { + fn from(value: BaseUrl) -> Self { + Self::Field(Cow::Owned(value.as_str().to_owned())) + } +} + +impl<'a> From for Segment<'a> { + fn from(value: usize) -> Self { + Self::Index(value) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct JsonPath<'a>(Cow<'a, [Segment<'a>]>); + +impl<'a> JsonPath<'a> { + #[must_use] + pub const fn new() -> Self { + Self(Cow::Owned(Vec::new())) + } + + #[must_use] + pub const fn from_slice(segments: &'a [Segment<'a>]) -> Self { + Self(Cow::Borrowed(segments)) + } + + #[must_use] + pub fn then(mut self) -> Self { + self.0.to_mut().push(T::ID.base().into()); + self + } + + #[must_use] + pub fn then_field(mut self, field: impl Into>) -> Self { + self.0.to_mut().push(Segment::Field(field.into())); + self + } + + #[must_use] + pub fn then_index(mut self, index: usize) -> Self { + self.0.to_mut().push(Segment::Index(index)); + self + } + + pub(crate) fn segments(&self) -> &[Segment] { + &self.0 + } + + pub(crate) fn traverse_entity<'b>(&self, entity: &'b Entity) -> Option> { + let value = entity.properties.properties(); + + if self.0.is_empty() { + return Some( + value + .iter() + .map(|(key, value)| (Value::from(key.as_str()), Value::from(value))) + .collect::() + .into(), + ); + } + + let (first, rest) = self.0.split_first()?; + + let value = match first { + Segment::Field(field) => value.get(field.as_ref())?, + Segment::Index(_) => { + return None; + } + }; + + JsonPath(Cow::Borrowed(rest)).traverse(value) + } + + fn traverse<'b>(&self, value: &'b serde_json::Value) -> Option> { + let mut value = value; + + for segment in self.0.iter() { + match segment { + Segment::Field(field) => { + value = value.get(field.as_ref())?; + } + Segment::Index(index) => { + value = value.get(index)?; + } + } + } + + Some(value.into()) + } + + pub(crate) fn set(&self, target: &mut serde_json::Value, value: Value<'a>) { + if self.0.is_empty() { + *target = value.into(); + return; + } + + let (first, rest) = self.0.split_first().expect("infallible"); + + let target = match first { + Segment::Field(field) => { + if let serde_json::Value::Object(object) = target { + object.get_mut(field.as_ref()) + } else { + return; + } + } + Segment::Index(index) => { + if let serde_json::Value::Array(array) = target { + array.get_mut(*index) + } else { + return; + } + } + }; + + let Some(target) = target else { + return; + }; + + JsonPath(Cow::Borrowed(rest)).set(target, value); + } + + pub(crate) fn set_entity(&self, entity: &mut Entity, value: Value<'a>) { + if self.0.is_empty() { + if let Value::Object(object) = value { + entity.properties = object.into(); + } + + return; + } + + let (first, rest) = self.0.split_first().expect("infallible"); + + let target = match first { + Segment::Field(field) => entity.properties.properties_mut().get_mut(field.as_ref()), + Segment::Index(_) => { + return; + } + }; + + let Some(target) = target else { + return; + }; + + JsonPath(Cow::Borrowed(rest)).set(target, value); + } +} diff --git a/libs/turbine-transformer/src/property.rs b/libs/turbine-transformer/src/property.rs new file mode 100644 index 0000000..fa27920 --- /dev/null +++ b/libs/turbine-transformer/src/property.rs @@ -0,0 +1,5 @@ +mod select; +mod update; + +pub use select::{Action, ActionStatement, DynamicAction, PropertySelect, StaticAction}; +pub use update::{DynamicUpdate, PropertyUpdate, StaticUpdate, Update, UpdateStatement}; diff --git a/libs/turbine-transformer/src/property/select.rs b/libs/turbine-transformer/src/property/select.rs new file mode 100644 index 0000000..d4d5e65 --- /dev/null +++ b/libs/turbine-transformer/src/property/select.rs @@ -0,0 +1,265 @@ +use alloc::{borrow::Cow, boxed::Box, collections::BTreeSet, vec::Vec}; + +use turbine::{ + entity::{Entity, EntityId}, + TypeUrl, +}; + +use crate::{ + select::{Clause, JsonPath, Segment}, + View, +}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum Action { + Exclude, + Include, +} + +impl Action { + const fn reverse(self) -> Self { + match self { + Self::Exclude => Self::Include, + Self::Include => Self::Exclude, + } + } +} + +type DynamicActionFn<'a> = dyn Fn(&Entity) -> Option + 'a; +type BoxedDynamicActionFn = Box>; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +enum Then { + Explicit(Action), + // The reverse of the default action + Implicit, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StaticAction<'a> { + path: JsonPath<'a>, + then: Then, +} + +impl StaticAction<'static> { + #[must_use] + pub fn new() -> Self { + Self { + path: JsonPath::new().then::(), + then: Then::Implicit, + } + } +} + +impl<'a> StaticAction<'a> { + #[must_use] + pub fn new_with_path(path: impl Into>) -> Self { + Self { + path: path.into(), + then: Then::Implicit, + } + } + + #[must_use] + pub const fn then(mut self, action: Action) -> Self { + self.then = Then::Explicit(action); + self + } +} + +pub struct DynamicAction<'a> { + path: JsonPath<'a>, + then: BoxedDynamicActionFn, +} + +impl DynamicAction<'static> { + #[must_use] + pub fn new(then: impl Fn(&Entity) -> Option + 'static) -> Self { + Self { + path: JsonPath::new().then::(), + then: Box::new(then), + } + } +} + +impl<'a> DynamicAction<'a> { + #[must_use] + pub fn new_with_path( + path: impl Into>, + then: impl Fn(&Entity) -> Option + 'static, + ) -> Self { + Self { + path: path.into(), + then: Box::new(then), + } + } +} + +pub enum ActionStatement<'a> { + Static(StaticAction<'a>), + Dynamic(DynamicAction<'a>), +} + +impl<'a> From> for ActionStatement<'a> { + fn from(action: StaticAction<'a>) -> Self { + Self::Static(action) + } +} + +impl<'a> From> for ActionStatement<'a> { + fn from(action: DynamicAction<'a>) -> Self { + Self::Dynamic(action) + } +} + +pub struct Select<'a> { + if_: Clause<'a>, + + actions: Vec>, + default: Action, +} + +impl<'a> Select<'a> { + pub fn new(if_: impl Into>, default: Action) -> Self { + Self { + if_: if_.into(), + actions: Vec::new(), + default, + } + } + + pub fn do_(mut self, action: impl Into>) -> Self { + self.actions.push(action.into()); + self + } + + fn matches(&self, view: &View, id: EntityId) -> bool { + self.if_.matches(view, id) + } + + fn apply(&self, entity: &mut Entity) { + // for every depth, find the matching properties that we need to include + let mut included = BTreeSet::new(); + + // depending on the default action, we either include all properties or exclude all + // properties by default + if self.default == Action::Include { + for keys in entity.properties.properties().keys() { + included.insert(Cow::Owned(keys.clone())); + } + } + + for action in &self.actions { + match action { + ActionStatement::Static(action) => { + assert!( + action.path.segments().len() <= 1, + "PropertySelect does not support nested paths (yet)" + ); + + let [key] = action.path.segments() else { + continue; + }; + + match key { + Segment::Index(_) => continue, + Segment::Field(field) => { + if let Some(action) = match action.then { + Then::Explicit(action) => Some(action), + Then::Implicit => Some(self.default.reverse()), + } { + if action == Action::Include { + included.insert(Cow::Borrowed(field.as_ref())); + } else { + included.remove(field.as_ref()); + } + } + } + } + } + ActionStatement::Dynamic(action) => { + assert!( + action.path.segments().len() <= 1, + "PropertySelect does not support nested paths (yet)" + ); + + let [key] = action.path.segments() else { + continue; + }; + + match key { + Segment::Index(_) => continue, + Segment::Field(field) => { + if let Some(action) = (action.then)(entity) { + if action == Action::Include { + included.insert(Cow::Borrowed(field.as_ref())); + } else { + included.remove(field.as_ref()); + } + } + } + } + } + } + } + + entity + .properties + .properties_mut() + .retain(|key, _| included.contains(key.as_str())); + } +} + +pub struct PropertySelect<'a> { + statements: Vec>, +} + +impl<'a> PropertySelect<'a> { + #[must_use] + pub const fn new() -> Self { + Self { + statements: Vec::new(), + } + } + + #[must_use] + pub fn and(mut self, clause: impl Into>) -> Self { + self.statements.push(clause.into()); + self + } + + fn eval(select: &Select, view: &mut View) { + // We need to precompute the matches because we can't borrow the view immutably while we're + // mutating it. + let matches: BTreeSet<_> = view + .entities + .iter() + .enumerate() + .filter(|(_, entity)| select.matches(view, entity.metadata.record_id.entity_id)) + .map(|(index, _)| index) + .collect(); + + let entities = view + .entities + .iter_mut() + .enumerate() + .filter(|(index, _)| matches.contains(index)) + .map(|(_, entity)| entity); + + for entity in entities { + select.apply(entity); + } + } + + fn run(self, view: &mut View) { + for statement in &self.statements { + Self::eval(statement, view); + } + } +} + +impl<'a> View<'a> { + pub fn select_properties(&mut self, statements: Vec