mxrb 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.de-DE.md +108 -0
- data/README.md +172 -0
- data/README.pt-BR.md +109 -0
- data/bin/mxrb +1613 -0
- data/docs/README.md +10 -0
- data/docs/de-DE/README.md +24 -0
- data/docs/de-DE/architectural-patterns.md +26 -0
- data/docs/de-DE/architectural-standard.md +48 -0
- data/docs/de-DE/architecture.md +76 -0
- data/docs/de-DE/compiler.md +162 -0
- data/docs/de-DE/conventions.md +19 -0
- data/docs/de-DE/design-system.md +52 -0
- data/docs/de-DE/entity-dsl.md +53 -0
- data/docs/de-DE/native-runtime-quality-report.md +74 -0
- data/docs/de-DE/oql-sql.md +211 -0
- data/docs/de-DE/platform-api-integration.md +36 -0
- data/docs/de-DE/platform-operations.md +243 -0
- data/docs/de-DE/project-structure.md +32 -0
- data/docs/de-DE/ruby-first-roadmap.md +116 -0
- data/docs/de-DE/scaffolds.md +59 -0
- data/docs/de-DE/semantic-refactoring.md +56 -0
- data/docs/de-DE/team-server.md +33 -0
- data/docs/de-DE/validation-matrix.md +64 -0
- data/docs/de-DE/vetclinic-acceptance.md +24 -0
- data/docs/de-DE/writing.md +138 -0
- data/docs/en-US/README.md +24 -0
- data/docs/en-US/architectural-patterns.md +151 -0
- data/docs/en-US/architectural-standard.md +89 -0
- data/docs/en-US/architecture.md +170 -0
- data/docs/en-US/compiler.md +157 -0
- data/docs/en-US/conventions.md +82 -0
- data/docs/en-US/design-system.md +163 -0
- data/docs/en-US/entity-dsl.md +53 -0
- data/docs/en-US/native-runtime-quality-report.md +73 -0
- data/docs/en-US/oql-sql.md +201 -0
- data/docs/en-US/platform-api-integration.md +36 -0
- data/docs/en-US/platform-operations.md +244 -0
- data/docs/en-US/project-structure.md +49 -0
- data/docs/en-US/ruby-first-roadmap.md +186 -0
- data/docs/en-US/scaffolds.md +57 -0
- data/docs/en-US/semantic-refactoring.md +198 -0
- data/docs/en-US/team-server.md +32 -0
- data/docs/en-US/validation-matrix.md +257 -0
- data/docs/en-US/vetclinic-acceptance.md +23 -0
- data/docs/en-US/writing.md +655 -0
- data/docs/pt-BR/README.md +24 -0
- data/docs/pt-BR/architectural-patterns.md +25 -0
- data/docs/pt-BR/architectural-standard.md +533 -0
- data/docs/pt-BR/architecture.md +300 -0
- data/docs/pt-BR/compiler.md +162 -0
- data/docs/pt-BR/conventions.md +19 -0
- data/docs/pt-BR/design-system.md +65 -0
- data/docs/pt-BR/entity-dsl.md +116 -0
- data/docs/pt-BR/native-runtime-quality-report.md +73 -0
- data/docs/pt-BR/oql-sql.md +208 -0
- data/docs/pt-BR/platform-api-integration.md +36 -0
- data/docs/pt-BR/platform-operations.md +260 -0
- data/docs/pt-BR/project-structure.md +32 -0
- data/docs/pt-BR/ruby-first-roadmap.md +239 -0
- data/docs/pt-BR/scaffolds.md +104 -0
- data/docs/pt-BR/semantic-refactoring.md +55 -0
- data/docs/pt-BR/team-server.md +59 -0
- data/docs/pt-BR/validation-matrix.md +86 -0
- data/docs/pt-BR/vetclinic-acceptance.md +50 -0
- data/docs/pt-BR/writing.md +177 -0
- data/examples/sudoku_evaluation.rb +14 -0
- data/examples/sudoku_functional_test.rb +28 -0
- data/lib/mxrb/architecture/graph.rb +124 -0
- data/lib/mxrb/architecture/validator.rb +98 -0
- data/lib/mxrb/benchmark.rb +33 -0
- data/lib/mxrb/compare.rb +390 -0
- data/lib/mxrb/compiler/adapter.rb +96 -0
- data/lib/mxrb/compiler/artifact_document_compiler.rb +102 -0
- data/lib/mxrb/compiler/artifact_materializer.rb +53 -0
- data/lib/mxrb/compiler/client_model_materializer.rb +42 -0
- data/lib/mxrb/compiler/code_action_document_compiler.rb +64 -0
- data/lib/mxrb/compiler/code_action_materializer.rb +35 -0
- data/lib/mxrb/compiler/code_action_type_compiler.rb +32 -0
- data/lib/mxrb/compiler/combo_box_bundle_compiler.rb +314 -0
- data/lib/mxrb/compiler/compatibility_analyzer.rb +169 -0
- data/lib/mxrb/compiler/constants_materializer.rb +111 -0
- data/lib/mxrb/compiler/data_grid_bundle_compiler.rb +290 -0
- data/lib/mxrb/compiler/database_connector_action_compiler.rb +280 -0
- data/lib/mxrb/compiler/deployment_asset_copier.rb +60 -0
- data/lib/mxrb/compiler/deployment_bootstrapper.rb +208 -0
- data/lib/mxrb/compiler/deployment_materializer.rb +43 -0
- data/lib/mxrb/compiler/deployment_metadata.rb +64 -0
- data/lib/mxrb/compiler/domain_document_compiler.rb +153 -0
- data/lib/mxrb/compiler/domain_model_materializer.rb +35 -0
- data/lib/mxrb/compiler/domain_security_compiler.rb +82 -0
- data/lib/mxrb/compiler/gallery_bundle_compiler.rb +190 -0
- data/lib/mxrb/compiler/generic_widget_bundle_compiler.rb +281 -0
- data/lib/mxrb/compiler/image_bundle_compiler.rb +216 -0
- data/lib/mxrb/compiler/java_proxy_generator.rb +509 -0
- data/lib/mxrb/compiler/legacy_data_grid_compiler.rb +267 -0
- data/lib/mxrb/compiler/legacy_page_builder.rb +287 -0
- data/lib/mxrb/compiler/mda.rb +99 -0
- data/lib/mxrb/compiler/microflow_document_compiler.rb +85 -0
- data/lib/mxrb/compiler/microflow_materializer.rb +48 -0
- data/lib/mxrb/compiler/microflow_node_compiler.rb +250 -0
- data/lib/mxrb/compiler/model_package.rb +116 -0
- data/lib/mxrb/compiler/model_values.rb +67 -0
- data/lib/mxrb/compiler/nanoflow_program_compiler.rb +483 -0
- data/lib/mxrb/compiler/navigation_document_compiler.rb +93 -0
- data/lib/mxrb/compiler/packager.rb +100 -0
- data/lib/mxrb/compiler/page_bundle_builder.rb +56 -0
- data/lib/mxrb/compiler/page_bundle_compiler.rb +2004 -0
- data/lib/mxrb/compiler/page_document_compiler.rb +77 -0
- data/lib/mxrb/compiler/portable_packager.rb +356 -0
- data/lib/mxrb/compiler/project_jar_archive.rb +84 -0
- data/lib/mxrb/compiler/project_jar_builder.rb +121 -0
- data/lib/mxrb/compiler/project_materializer.rb +139 -0
- data/lib/mxrb/compiler/project_model_orderer.rb +52 -0
- data/lib/mxrb/compiler/runtime_data_types.rb +31 -0
- data/lib/mxrb/compiler/runtime_model_schema.rb +90 -0
- data/lib/mxrb/compiler/schemas/runtime-10.24.0.73019.json +1052 -0
- data/lib/mxrb/compiler/schemas/runtime-11.json +1215 -0
- data/lib/mxrb/compiler/schemas/runtime-6.10.8.json +1148 -0
- data/lib/mxrb/compiler/schemas/runtime-7.17.0.json +1144 -0
- data/lib/mxrb/compiler/schemas/runtime-7.5.0.json +1215 -0
- data/lib/mxrb/compiler/schemas/runtime-9.6.1.29396.json +868 -0
- data/lib/mxrb/compiler/schemas/system-model-10.24.0.73019.b64 +1135 -0
- data/lib/mxrb/compiler/schemas/system-model-11.12.1.b64 +815 -0
- data/lib/mxrb/compiler/schemas/system-model-6.10.8.b64 +497 -0
- data/lib/mxrb/compiler/schemas/system-model-7.17.0.b64 +519 -0
- data/lib/mxrb/compiler/schemas/system-model-7.5.0.b64 +504 -0
- data/lib/mxrb/compiler/schemas/system-model-9.6.1.29396.b64 +816 -0
- data/lib/mxrb/compiler/security_materializer.rb +104 -0
- data/lib/mxrb/compiler/settings_document_compiler.rb +45 -0
- data/lib/mxrb/compiler/settings_materializer.rb +25 -0
- data/lib/mxrb/compiler/source_model.rb +128 -0
- data/lib/mxrb/compiler/system_model_seed.rb +88 -0
- data/lib/mxrb/compiler/system_queue_materializer.rb +54 -0
- data/lib/mxrb/compiler/system_text_materializer.rb +49 -0
- data/lib/mxrb/compiler/translation_materializer.rb +95 -0
- data/lib/mxrb/compiler/web_bundle_builder.rb +155 -0
- data/lib/mxrb/compiler/web_list_data_source.rb +139 -0
- data/lib/mxrb/compiler/web_operation_compiler.rb +614 -0
- data/lib/mxrb/compiler/web_shell_materializer.rb +194 -0
- data/lib/mxrb/compiler/widget_package_extractor.rb +63 -0
- data/lib/mxrb/doctor.rb +116 -0
- data/lib/mxrb/dsl/builder.rb +1636 -0
- data/lib/mxrb/errors.rb +18 -0
- data/lib/mxrb/evaluation.rb +131 -0
- data/lib/mxrb/exporter.rb +1716 -0
- data/lib/mxrb/frontend/migrator.rb +775 -0
- data/lib/mxrb/functional.rb +307 -0
- data/lib/mxrb/github/annotator.rb +161 -0
- data/lib/mxrb/initializer.rb +257 -0
- data/lib/mxrb/integrity/validator.rb +136 -0
- data/lib/mxrb/io/bson_codec.rb +145 -0
- data/lib/mxrb/io/mpr_file.rb +558 -0
- data/lib/mxrb/io/mxunit_codec.rb +42 -0
- data/lib/mxrb/marketplace.rb +380 -0
- data/lib/mxrb/model/association.rb +98 -0
- data/lib/mxrb/model/attribute.rb +100 -0
- data/lib/mxrb/model/connector.rb +17 -0
- data/lib/mxrb/model/design_materializer.rb +133 -0
- data/lib/mxrb/model/design_migration.rb +126 -0
- data/lib/mxrb/model/design_system.rb +111 -0
- data/lib/mxrb/model/domain_model.rb +51 -0
- data/lib/mxrb/model/entity.rb +180 -0
- data/lib/mxrb/model/menu.rb +38 -0
- data/lib/mxrb/model/microflow.rb +100 -0
- data/lib/mxrb/model/module.rb +142 -0
- data/lib/mxrb/model/navigation.rb +127 -0
- data/lib/mxrb/model/page.rb +410 -0
- data/lib/mxrb/model/project.rb +187 -0
- data/lib/mxrb/model/unit.rb +48 -0
- data/lib/mxrb/module_initializer.rb +75 -0
- data/lib/mxrb/official_marketplace/content_api.rb +292 -0
- data/lib/mxrb/official_marketplace/dependency_resolver.rb +329 -0
- data/lib/mxrb/official_marketplace/lifecycle.rb +367 -0
- data/lib/mxrb/official_marketplace/module_package_importer.rb +318 -0
- data/lib/mxrb/official_marketplace/widget_package_installer.rb +414 -0
- data/lib/mxrb/official_marketplace.rb +720 -0
- data/lib/mxrb/oql/analyzer.rb +155 -0
- data/lib/mxrb/oql/index_advisor.rb +85 -0
- data/lib/mxrb/oql/plan_analyzer.rb +185 -0
- data/lib/mxrb/oql/server.rb +132 -0
- data/lib/mxrb/oql/sql_server_plan_analyzer.rb +190 -0
- data/lib/mxrb/oql/sql_server_workload_analyzer.rb +80 -0
- data/lib/mxrb/oql/workload_analyzer.rb +175 -0
- data/lib/mxrb/oql/workload_baseline.rb +60 -0
- data/lib/mxrb/oql.rb +424 -0
- data/lib/mxrb/project_lifecycle.rb +76 -0
- data/lib/mxrb/protocols/plan.rb +86 -0
- data/lib/mxrb/protocols.rb +123 -0
- data/lib/mxrb/runtime/database_workspace.rb +677 -0
- data/lib/mxrb/runtime/docker_executor.rb +41 -0
- data/lib/mxrb/runtime/docker_workspace.rb +29 -0
- data/lib/mxrb/runtime/executor.rb +169 -0
- data/lib/mxrb/runtime/java_locator.rb +26 -0
- data/lib/mxrb/runtime/native.rb +474 -0
- data/lib/mxrb/runtime/sql_server_database.rb +146 -0
- data/lib/mxrb/runtime/toolchain.rb +82 -0
- data/lib/mxrb/scaffold/cli.rb +113 -0
- data/lib/mxrb/scaffold/generator.rb +246 -0
- data/lib/mxrb/scaffold/help.rb +80 -0
- data/lib/mxrb/scaffold/page_templates.rb +54 -0
- data/lib/mxrb/scaffold/recipes.rb +198 -0
- data/lib/mxrb/scaffold/registry.rb +82 -0
- data/lib/mxrb/scaffold/templates.rb +723 -0
- data/lib/mxrb/scaffold/transaction.rb +84 -0
- data/lib/mxrb/schema/tables.rb +55 -0
- data/lib/mxrb/semantic/analyzer.rb +473 -0
- data/lib/mxrb/semantic/batch_plan.rb +68 -0
- data/lib/mxrb/semantic/domain_mutator.rb +419 -0
- data/lib/mxrb/semantic/embedder.rb +51 -0
- data/lib/mxrb/semantic/extractor.rb +290 -0
- data/lib/mxrb/semantic/index.rb +623 -0
- data/lib/mxrb/semantic/inliner.rb +216 -0
- data/lib/mxrb/semantic/mover.rb +153 -0
- data/lib/mxrb/semantic/onnx_embedder.rb +34 -0
- data/lib/mxrb/semantic/remover.rb +70 -0
- data/lib/mxrb/semantic/renamer.rb +211 -0
- data/lib/mxrb/semantic/tfidf_embedder.rb +46 -0
- data/lib/mxrb/semantic/vec_store.rb +75 -0
- data/lib/mxrb/team_server.rb +390 -0
- data/lib/mxrb/templates/project/10.24.0.73019.json +32 -0
- data/lib/mxrb/templates/project/11.12.1.json +32 -0
- data/lib/mxrb/templates/project/6.10.8.json +50 -0
- data/lib/mxrb/templates/project/7.17.0.json +50 -0
- data/lib/mxrb/templates/project/7.5.0.json +50 -0
- data/lib/mxrb/templates/project/9.6.1.29396.json +50 -0
- data/lib/mxrb/version.rb +5 -0
- data/lib/mxrb/widget_package.rb +338 -0
- data/lib/mxrb/widget_synchronizer.rb +31 -0
- data/lib/mxrb/writer.rb +3775 -0
- data/lib/mxrb.rb +188 -0
- data/marketplace/catalog.json +10 -0
- data/marketplace/modules/shared-kernel/domain/README.md +4 -0
- data/marketplace/modules/shared-kernel/module.rb +5 -0
- data/marketplace/modules/shared-kernel/mxrb-module.json +9 -0
- metadata +447 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require "set"
|
|
5
|
+
|
|
6
|
+
module Mxrb
|
|
7
|
+
module Semantic
|
|
8
|
+
class InlinePlan
|
|
9
|
+
attr_reader :source, :called_name, :call_id
|
|
10
|
+
|
|
11
|
+
def initialize(project:, source:, called_name:, call_id:,
|
|
12
|
+
inner_objects:, inner_flows:,
|
|
13
|
+
entry_flow:, exit_flow:,
|
|
14
|
+
source_doc:, source_raw:)
|
|
15
|
+
@project = project
|
|
16
|
+
@source = source
|
|
17
|
+
@called_name = called_name
|
|
18
|
+
@call_id = call_id
|
|
19
|
+
@inner_objects = inner_objects
|
|
20
|
+
@inner_flows = inner_flows
|
|
21
|
+
@entry_flow = entry_flow
|
|
22
|
+
@exit_flow = exit_flow
|
|
23
|
+
@source_doc = source_doc
|
|
24
|
+
@source_raw = source_raw
|
|
25
|
+
@applied = false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def applied? = @applied
|
|
29
|
+
|
|
30
|
+
def changes
|
|
31
|
+
count = @inner_objects.size
|
|
32
|
+
suffix = count == 1 ? "y" : "ies"
|
|
33
|
+
[
|
|
34
|
+
"inline #{@called_name} into #{@source.qualified_name}",
|
|
35
|
+
"replace call_microflow activity with #{count} inlined activit#{suffix}"
|
|
36
|
+
]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def apply!
|
|
40
|
+
raise ArgumentError, "inline plan was already applied" if applied?
|
|
41
|
+
|
|
42
|
+
@project.mpr.transaction { patch_source_microflow! }
|
|
43
|
+
@project.refresh!
|
|
44
|
+
@applied = true
|
|
45
|
+
self
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def patch_source_microflow!
|
|
51
|
+
# Remap every inlined object's UUID to avoid collisions with existing caller objects
|
|
52
|
+
id_map = @inner_objects.to_h { [_1["$ID"], SecureRandom.uuid] }
|
|
53
|
+
|
|
54
|
+
remapped_objects = @inner_objects.map { |obj| obj.merge("$ID" => id_map.fetch(obj["$ID"])) }
|
|
55
|
+
remapped_flows = @inner_flows.map do |flow|
|
|
56
|
+
flow.merge(
|
|
57
|
+
"$ID" => SecureRandom.uuid,
|
|
58
|
+
"OriginPointer" => id_map.fetch(flow["OriginPointer"], flow["OriginPointer"]),
|
|
59
|
+
"DestinationPointer" => id_map.fetch(flow["DestinationPointer"], flow["DestinationPointer"])
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
first_id = id_map.fetch(@entry_flow["DestinationPointer"])
|
|
64
|
+
last_id = id_map.fetch(@exit_flow["OriginPointer"])
|
|
65
|
+
origin_id = @entry_flow["OriginPointer"]
|
|
66
|
+
dest_id = @exit_flow["DestinationPointer"]
|
|
67
|
+
|
|
68
|
+
all_objects = normalize_all(parse_array(@source_doc["ObjectCollection"]["Objects"]))
|
|
69
|
+
kept_objects = all_objects.reject { _1["$ID"] == @call_id }
|
|
70
|
+
kept_objects.concat(remapped_objects)
|
|
71
|
+
|
|
72
|
+
removed_flow_ids = Set.new([@entry_flow["$ID"], @exit_flow["$ID"]].compact)
|
|
73
|
+
all_flows = normalize_all(parse_array(@source_doc["Flows"]))
|
|
74
|
+
kept_flows = all_flows.reject { removed_flow_ids.include?(_1["$ID"]) }
|
|
75
|
+
kept_flows << simple_flow_doc(origin_id, first_id)
|
|
76
|
+
kept_flows << simple_flow_doc(last_id, dest_id)
|
|
77
|
+
kept_flows.concat(remapped_flows)
|
|
78
|
+
|
|
79
|
+
patched = @source_doc.merge(
|
|
80
|
+
"ObjectCollection" => @source_doc["ObjectCollection"].merge(
|
|
81
|
+
"Objects" => IO::BsonCodec.build_array(kept_objects)
|
|
82
|
+
),
|
|
83
|
+
"Flows" => IO::BsonCodec.build_array(kept_flows)
|
|
84
|
+
)
|
|
85
|
+
@project.mpr.update_unit(@source_raw.fetch("UnitID"), patched)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def simple_flow_doc(from_id, to_id)
|
|
89
|
+
{
|
|
90
|
+
"$ID" => SecureRandom.uuid,
|
|
91
|
+
"$Type" => "Microflows$SequenceFlow",
|
|
92
|
+
"OriginPointer" => from_id,
|
|
93
|
+
"DestinationPointer" => to_id,
|
|
94
|
+
"IsErrorHandler" => false,
|
|
95
|
+
"OriginConnectionIndex" => 1,
|
|
96
|
+
"DestinationConnectionIndex" => 3
|
|
97
|
+
}
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
POINTER_KEYS = %w[$ID OriginPointer DestinationPointer].freeze
|
|
101
|
+
|
|
102
|
+
def normalize_ids(hash)
|
|
103
|
+
hash.each_with_object({}) do |(k, v), out|
|
|
104
|
+
out[k] = POINTER_KEYS.include?(k) ? (IO::BsonCodec.extract_id(v) || v) : v
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def normalize_all(arr) = arr.map { normalize_ids(_1) }
|
|
109
|
+
def parse_array(value) = IO::BsonCodec.parse_array(value)[:items]
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Inlines a called microflow back into its caller, replacing the
|
|
113
|
+
# call_microflow activity with the callee's activities.
|
|
114
|
+
class Inliner
|
|
115
|
+
FLOW_KINDS = %i[microflow nanoflow].freeze
|
|
116
|
+
|
|
117
|
+
def initialize(project)
|
|
118
|
+
@project = project
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @param source_name [String] qualified name of the caller microflow
|
|
122
|
+
# @param calling [String] qualified name of the microflow to inline
|
|
123
|
+
def plan(source_name, calling:)
|
|
124
|
+
source = resolve(source_name)
|
|
125
|
+
raise ArgumentError, "#{source.qualified_name} is not a microflow or nanoflow" \
|
|
126
|
+
unless FLOW_KINDS.include?(source.kind)
|
|
127
|
+
|
|
128
|
+
called = resolve(calling)
|
|
129
|
+
raise ArgumentError, "#{called.qualified_name} is not a microflow or nanoflow" \
|
|
130
|
+
unless FLOW_KINDS.include?(called.kind)
|
|
131
|
+
|
|
132
|
+
source_raw = @project.raw_unit(source.unit_id)
|
|
133
|
+
source_doc = @project.parse_bson(source_raw)
|
|
134
|
+
|
|
135
|
+
all_objects = normalize_all(parse_array(source_doc["ObjectCollection"]["Objects"]))
|
|
136
|
+
all_flows = normalize_all(parse_array(source_doc["Flows"]))
|
|
137
|
+
|
|
138
|
+
call_activity = all_objects.find do |obj|
|
|
139
|
+
obj["$Type"] == "Microflows$ActionActivity" &&
|
|
140
|
+
obj.dig("Action", "MicroflowCall", "Microflow") == calling
|
|
141
|
+
end
|
|
142
|
+
raise ArgumentError, "no call to #{calling.inspect} found in #{source_name.inspect}" \
|
|
143
|
+
unless call_activity
|
|
144
|
+
|
|
145
|
+
call_id = call_activity["$ID"]
|
|
146
|
+
entry_flow = all_flows.find { _1["DestinationPointer"] == call_id }
|
|
147
|
+
exit_flow = all_flows.find { _1["OriginPointer"] == call_id }
|
|
148
|
+
raise ArgumentError, "cannot find entry flow for call activity" unless entry_flow
|
|
149
|
+
raise ArgumentError, "cannot find exit flow for call activity" unless exit_flow
|
|
150
|
+
|
|
151
|
+
called_raw = @project.raw_unit(called.unit_id)
|
|
152
|
+
called_doc = @project.parse_bson(called_raw)
|
|
153
|
+
|
|
154
|
+
called_objects = normalize_all(parse_array(called_doc["ObjectCollection"]["Objects"]))
|
|
155
|
+
called_flows = normalize_all(parse_array(called_doc["Flows"]))
|
|
156
|
+
|
|
157
|
+
start_event = called_objects.find { _1["$Type"] == "Microflows$StartEvent" }
|
|
158
|
+
end_event = called_objects.find { _1["$Type"] == "Microflows$EndEvent" }
|
|
159
|
+
raise ArgumentError, "#{calling.inspect} has no start event" unless start_event
|
|
160
|
+
raise ArgumentError, "#{calling.inspect} has no end event" unless end_event
|
|
161
|
+
|
|
162
|
+
start_id = start_event["$ID"]
|
|
163
|
+
end_id = end_event["$ID"]
|
|
164
|
+
|
|
165
|
+
inner_objects = called_objects.reject { [start_id, end_id].include?(_1["$ID"]) }
|
|
166
|
+
raise ArgumentError, "#{calling.inspect} has no activities to inline" if inner_objects.empty?
|
|
167
|
+
|
|
168
|
+
inner_flows = called_flows.reject do |f|
|
|
169
|
+
[start_id, end_id].include?(f["OriginPointer"]) ||
|
|
170
|
+
[start_id, end_id].include?(f["DestinationPointer"])
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
start_to_first = called_flows.find { _1["OriginPointer"] == start_id }
|
|
174
|
+
last_to_end = called_flows.find { _1["DestinationPointer"] == end_id }
|
|
175
|
+
raise ArgumentError, "cannot resolve first activity in #{calling.inspect}" unless start_to_first
|
|
176
|
+
raise ArgumentError, "cannot resolve last activity in #{calling.inspect}" unless last_to_end
|
|
177
|
+
|
|
178
|
+
# Synthesise entry/exit flows that point at the actual first/last inlined objects
|
|
179
|
+
synthetic_entry = entry_flow.merge("DestinationPointer" => start_to_first["DestinationPointer"])
|
|
180
|
+
synthetic_exit = exit_flow.merge("OriginPointer" => last_to_end["OriginPointer"])
|
|
181
|
+
|
|
182
|
+
InlinePlan.new(
|
|
183
|
+
project: @project,
|
|
184
|
+
source: source,
|
|
185
|
+
called_name: calling,
|
|
186
|
+
call_id: call_id,
|
|
187
|
+
inner_objects: inner_objects,
|
|
188
|
+
inner_flows: inner_flows,
|
|
189
|
+
entry_flow: synthetic_entry,
|
|
190
|
+
exit_flow: synthetic_exit,
|
|
191
|
+
source_doc: source_doc,
|
|
192
|
+
source_raw: source_raw
|
|
193
|
+
)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
private
|
|
197
|
+
|
|
198
|
+
def resolve(name)
|
|
199
|
+
artifact = @project.find_artifact(name)
|
|
200
|
+
raise KeyError, "unknown Mendix artifact #{name.inspect}" unless artifact
|
|
201
|
+
artifact
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
POINTER_KEYS = %w[$ID OriginPointer DestinationPointer].freeze
|
|
205
|
+
|
|
206
|
+
def normalize_ids(hash)
|
|
207
|
+
hash.each_with_object({}) do |(k, v), out|
|
|
208
|
+
out[k] = POINTER_KEYS.include?(k) ? (IO::BsonCodec.extract_id(v) || v) : v
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def normalize_all(arr) = arr.map { normalize_ids(_1) }
|
|
213
|
+
def parse_array(value) = IO::BsonCodec.parse_array(value)[:items]
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mxrb
|
|
4
|
+
module Semantic
|
|
5
|
+
class MovePlan
|
|
6
|
+
attr_reader :source, :target, :before_container, :containment_name
|
|
7
|
+
|
|
8
|
+
def initialize(project:, source:, target:, before_container:, containment_name:)
|
|
9
|
+
@project = project
|
|
10
|
+
@source = source
|
|
11
|
+
@target = target
|
|
12
|
+
@before_container = before_container
|
|
13
|
+
@containment_name = containment_name
|
|
14
|
+
@applied = false
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def after_container = target.unit_id
|
|
18
|
+
def empty? = before_container == after_container
|
|
19
|
+
def applied? = @applied
|
|
20
|
+
|
|
21
|
+
def apply!
|
|
22
|
+
raise ArgumentError, "move plan was already applied" if applied?
|
|
23
|
+
|
|
24
|
+
unless empty?
|
|
25
|
+
@project.mpr.transaction do
|
|
26
|
+
@project.mpr.relocate_unit(
|
|
27
|
+
source.unit_id,
|
|
28
|
+
container_uuid: after_container,
|
|
29
|
+
containment_name:
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
@project.refresh!
|
|
34
|
+
@applied = true
|
|
35
|
+
self
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Cross-module move: relocates a unit AND renames its qualified name across
|
|
40
|
+
# the project so that all references stay consistent.
|
|
41
|
+
class CrossModuleMovePlan
|
|
42
|
+
attr_reader :source, :target, :before_container, :rename_changes
|
|
43
|
+
|
|
44
|
+
def initialize(project:, source:, target:, rename_plan:, before_container:, containment_name:)
|
|
45
|
+
@project = project
|
|
46
|
+
@source = source
|
|
47
|
+
@target = target
|
|
48
|
+
@rename_plan = rename_plan
|
|
49
|
+
@before_container = before_container
|
|
50
|
+
@containment_name = containment_name
|
|
51
|
+
@applied = false
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def after_container = target.unit_id
|
|
55
|
+
def empty? = false
|
|
56
|
+
def rename_changes = @rename_plan.changes
|
|
57
|
+
def applied? = @applied
|
|
58
|
+
|
|
59
|
+
def apply!
|
|
60
|
+
raise ArgumentError, "cross-module move plan was already applied" if applied?
|
|
61
|
+
|
|
62
|
+
@project.mpr.transaction do
|
|
63
|
+
@rename_plan.documents.each do |unit_id, doc|
|
|
64
|
+
@project.mpr.update_unit(unit_id, doc)
|
|
65
|
+
end
|
|
66
|
+
@project.mpr.relocate_unit(
|
|
67
|
+
@source.unit_id,
|
|
68
|
+
container_uuid: @target.unit_id,
|
|
69
|
+
containment_name: @containment_name
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
@project.refresh!
|
|
73
|
+
@applied = true
|
|
74
|
+
self
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
class Mover
|
|
79
|
+
EMBEDDED_KINDS = %i[module entity attribute association].freeze
|
|
80
|
+
CONTAINER_KINDS = %i[module folder].freeze
|
|
81
|
+
|
|
82
|
+
def initialize(project)
|
|
83
|
+
@project = project
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def plan(name, to:)
|
|
87
|
+
source = resolve(name)
|
|
88
|
+
target = resolve(to)
|
|
89
|
+
validate_kinds(source, target)
|
|
90
|
+
|
|
91
|
+
raw = @project.raw_unit(source.unit_id)
|
|
92
|
+
raise ArgumentError, "#{source.qualified_name} is not a movable unit" unless raw
|
|
93
|
+
if descendant_ids(source.unit_id).include?(target.unit_id)
|
|
94
|
+
raise ArgumentError, "cannot move #{source.qualified_name} into its descendant"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
if source.module_name != target.module_name
|
|
98
|
+
plan_cross_module(source, target, raw)
|
|
99
|
+
else
|
|
100
|
+
MovePlan.new(
|
|
101
|
+
project: @project,
|
|
102
|
+
source:,
|
|
103
|
+
target:,
|
|
104
|
+
before_container: raw.fetch("ContainerID"),
|
|
105
|
+
containment_name: raw.fetch("ContainmentName")
|
|
106
|
+
)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
def plan_cross_module(source, target, raw)
|
|
113
|
+
new_name = "#{target.module_name}.#{source.name}"
|
|
114
|
+
rename_plan = Renamer.new(@project).plan(source.qualified_name, to: new_name, cross_module: true)
|
|
115
|
+
CrossModuleMovePlan.new(
|
|
116
|
+
project: @project,
|
|
117
|
+
source:,
|
|
118
|
+
target:,
|
|
119
|
+
rename_plan:,
|
|
120
|
+
before_container: raw.fetch("ContainerID"),
|
|
121
|
+
containment_name: raw.fetch("ContainmentName")
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def resolve(name)
|
|
126
|
+
artifact = @project.find_artifact(name)
|
|
127
|
+
return artifact if artifact
|
|
128
|
+
|
|
129
|
+
matches = @project.semantic_index.find_all(name)
|
|
130
|
+
raise KeyError, "unknown Mendix artifact #{name.inspect}" if matches.empty?
|
|
131
|
+
raise ArgumentError, "ambiguous Mendix artifact #{name.inspect}" if matches.size > 1
|
|
132
|
+
|
|
133
|
+
matches.first
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def validate_kinds(source, target)
|
|
137
|
+
if EMBEDDED_KINDS.include?(source.kind)
|
|
138
|
+
raise ArgumentError, "#{source.kind} cannot be moved as a standalone unit"
|
|
139
|
+
end
|
|
140
|
+
return if CONTAINER_KINDS.include?(target.kind)
|
|
141
|
+
|
|
142
|
+
raise ArgumentError, "#{target.qualified_name} is not a module or folder"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def descendant_ids(unit_id)
|
|
146
|
+
direct = @project.children_of(unit_id)
|
|
147
|
+
direct.flat_map do |child|
|
|
148
|
+
[child.fetch("UnitID"), *descendant_ids(child.fetch("UnitID"))]
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mxrb
|
|
4
|
+
module Semantic
|
|
5
|
+
# Optional sentence-transformer backend powered by the informers gem.
|
|
6
|
+
class OnnxEmbedder
|
|
7
|
+
MODEL = 'sentence-transformers/all-MiniLM-L6-v2'
|
|
8
|
+
MODEL_REVISION = '1110a243fdf4706b3f48f1d95db1a4f5529b4d41'
|
|
9
|
+
|
|
10
|
+
def self.available?
|
|
11
|
+
require 'informers'
|
|
12
|
+
true
|
|
13
|
+
rescue LoadError
|
|
14
|
+
false
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def initialize(pipeline: nil)
|
|
18
|
+
require 'informers' unless pipeline
|
|
19
|
+
@pipeline = pipeline || Informers.pipeline(
|
|
20
|
+
'embedding', MODEL, revision: MODEL_REVISION
|
|
21
|
+
)
|
|
22
|
+
@dimension = nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def backend = :onnx
|
|
26
|
+
def dimension = (@dimension ||= embed('warmup').size)
|
|
27
|
+
|
|
28
|
+
def embed(text)
|
|
29
|
+
vector = @pipeline.call(text)
|
|
30
|
+
vector.first.is_a?(Array) ? vector.first : vector
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mxrb
|
|
4
|
+
module Semantic
|
|
5
|
+
class RemovalPlan
|
|
6
|
+
attr_reader :artifact, :incoming, :children
|
|
7
|
+
|
|
8
|
+
def initialize(project:, artifact:, incoming:, children:)
|
|
9
|
+
@project = project
|
|
10
|
+
@artifact = artifact
|
|
11
|
+
@incoming = incoming.freeze
|
|
12
|
+
@children = children.freeze
|
|
13
|
+
@applied = false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def safe? = incoming.empty? && children.empty?
|
|
17
|
+
def applied? = @applied
|
|
18
|
+
|
|
19
|
+
def apply!
|
|
20
|
+
raise ArgumentError, "removal plan was already applied" if applied?
|
|
21
|
+
unless safe?
|
|
22
|
+
raise ArgumentError,
|
|
23
|
+
"cannot remove #{artifact.qualified_name}: " \
|
|
24
|
+
"#{incoming.size} incoming reference(s), #{children.size} child unit(s)"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
@project.mpr.transaction { @project.mpr.delete_unit(artifact.unit_id) }
|
|
28
|
+
@project.refresh!
|
|
29
|
+
@applied = true
|
|
30
|
+
self
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class Remover
|
|
35
|
+
EMBEDDED_KINDS = %i[module entity attribute association].freeze
|
|
36
|
+
|
|
37
|
+
def initialize(project)
|
|
38
|
+
@project = project
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def plan(name)
|
|
42
|
+
artifact = resolve(name)
|
|
43
|
+
if EMBEDDED_KINDS.include?(artifact.kind)
|
|
44
|
+
raise ArgumentError,
|
|
45
|
+
"#{artifact.kind} removal requires a typed domain-model mutation"
|
|
46
|
+
end
|
|
47
|
+
raw = @project.raw_unit(artifact.unit_id)
|
|
48
|
+
raise ArgumentError, "#{artifact.qualified_name} is not a removable unit" unless raw
|
|
49
|
+
|
|
50
|
+
incoming = @project.references_to(artifact)
|
|
51
|
+
.reject { _1.source.id == artifact.id }
|
|
52
|
+
children = @project.children_of(artifact.unit_id)
|
|
53
|
+
RemovalPlan.new(project: @project, artifact:, incoming:, children:)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def resolve(name)
|
|
59
|
+
artifact = @project.find_artifact(name)
|
|
60
|
+
return artifact if artifact
|
|
61
|
+
|
|
62
|
+
matches = @project.semantic_index.find_all(name)
|
|
63
|
+
raise KeyError, "unknown Mendix artifact #{name.inspect}" if matches.empty?
|
|
64
|
+
raise ArgumentError, "ambiguous Mendix artifact #{name.inspect}" if matches.size > 1
|
|
65
|
+
|
|
66
|
+
matches.first
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mxrb
|
|
4
|
+
module Semantic
|
|
5
|
+
RenameChange = Data.define(:unit_id, :artifact, :path, :before, :after)
|
|
6
|
+
|
|
7
|
+
# Immutable preview plus the prepared BSON documents needed for an explicit
|
|
8
|
+
# apply. Construct plans through Project#plan_rename.
|
|
9
|
+
class RenamePlan
|
|
10
|
+
attr_reader :source, :target, :changes, :documents, :architecture
|
|
11
|
+
|
|
12
|
+
def initialize(project:, source:, target:, changes:, documents:, architecture: nil)
|
|
13
|
+
@project = project
|
|
14
|
+
@source = source
|
|
15
|
+
@target = target
|
|
16
|
+
@changes = changes.freeze
|
|
17
|
+
@documents = documents.freeze
|
|
18
|
+
@architecture = architecture
|
|
19
|
+
@applied = false
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def empty? = @changes.empty?
|
|
23
|
+
def applied? = @applied
|
|
24
|
+
def affected_units = @documents.keys.freeze
|
|
25
|
+
|
|
26
|
+
def apply!
|
|
27
|
+
raise ArgumentError, "rename plan was already applied" if @applied
|
|
28
|
+
|
|
29
|
+
@project.mpr.transaction do
|
|
30
|
+
@documents.each { |unit_id, document| @project.mpr.update_unit(unit_id, document) }
|
|
31
|
+
@project.mpr.write_architecture_definition(@architecture) if @architecture
|
|
32
|
+
end
|
|
33
|
+
@project.refresh!
|
|
34
|
+
@applied = true
|
|
35
|
+
self
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Plans and applies model-wide renames without introducing a query
|
|
40
|
+
# language. Every changed BSON string is visible in the Ruby preview.
|
|
41
|
+
class Renamer
|
|
42
|
+
METADATA_FIELDS = %w[Documentation documentation Caption caption Text text].freeze
|
|
43
|
+
|
|
44
|
+
def initialize(project)
|
|
45
|
+
@project = project
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def plan(source_name, to:, cross_module: false)
|
|
49
|
+
source = resolve(source_name)
|
|
50
|
+
target = qualified_target(source, to, cross_module: cross_module)
|
|
51
|
+
reject_collision(source, target)
|
|
52
|
+
|
|
53
|
+
changes = []
|
|
54
|
+
documents = {}
|
|
55
|
+
@project.all_units.each do |raw|
|
|
56
|
+
original = @project.parse_bson(raw)
|
|
57
|
+
transformed = transform(
|
|
58
|
+
original, source:, target:, unit_id: raw.fetch("UnitID"),
|
|
59
|
+
target_unit: source.unit_id == raw.fetch("UnitID"), changes:
|
|
60
|
+
)
|
|
61
|
+
documents[raw.fetch("UnitID")] = transformed unless transformed.equal?(original)
|
|
62
|
+
end
|
|
63
|
+
original_architecture = @project.architecture_definition
|
|
64
|
+
architecture = if original_architecture
|
|
65
|
+
transform(
|
|
66
|
+
original_architecture, source:, target:, unit_id: "architecture",
|
|
67
|
+
target_unit: false, changes:
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
RenamePlan.new(
|
|
72
|
+
project: @project, source:, target:, changes:, documents:, architecture:
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def resolve(name)
|
|
79
|
+
artifact = @project.find_artifact(name)
|
|
80
|
+
return artifact if artifact
|
|
81
|
+
|
|
82
|
+
matches = @project.semantic_index.find_all(name)
|
|
83
|
+
raise KeyError, "unknown Mendix artifact #{name.inspect}" if matches.empty?
|
|
84
|
+
|
|
85
|
+
kinds = matches.map(&:kind).uniq.join(", ")
|
|
86
|
+
raise ArgumentError, "ambiguous Mendix artifact #{name.inspect} (#{kinds})"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def qualified_target(source, value, cross_module: false)
|
|
90
|
+
requested = value.to_s
|
|
91
|
+
raise ArgumentError, "new name cannot be empty" if requested.empty?
|
|
92
|
+
unless requested.match?(/\A[A-Za-z_][A-Za-z0-9_.]*\z/)
|
|
93
|
+
raise ArgumentError, "invalid Mendix name #{requested.inspect}"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
old_parts = source.qualified_name.split(".")
|
|
97
|
+
new_parts = requested.split(".")
|
|
98
|
+
target = if new_parts.one?
|
|
99
|
+
(old_parts[0...-1] + new_parts).join(".")
|
|
100
|
+
else
|
|
101
|
+
requested
|
|
102
|
+
end
|
|
103
|
+
unless target.split(".").length == old_parts.length
|
|
104
|
+
raise ArgumentError, "#{source.kind} rename must keep its qualification depth"
|
|
105
|
+
end
|
|
106
|
+
if !cross_module && old_parts.length > 1 && target.split(".")[0...-1] != old_parts[0...-1]
|
|
107
|
+
raise ArgumentError, "#{source.kind} rename cannot move the artifact to another container"
|
|
108
|
+
end
|
|
109
|
+
target
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def reject_collision(source, target)
|
|
113
|
+
collisions = @project.semantic_index.find_all(target)
|
|
114
|
+
.reject { _1.id == source.id }
|
|
115
|
+
return if collisions.empty?
|
|
116
|
+
|
|
117
|
+
raise ArgumentError, "Mendix artifact #{target.inspect} already exists"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def transform(
|
|
121
|
+
value, source:, target:, unit_id:, target_unit:, changes:, path: [],
|
|
122
|
+
target_object: false
|
|
123
|
+
)
|
|
124
|
+
case value
|
|
125
|
+
when Hash
|
|
126
|
+
transformed_hash(
|
|
127
|
+
value, source:, target:, unit_id:, target_unit:, changes:, path:,
|
|
128
|
+
target_object:
|
|
129
|
+
)
|
|
130
|
+
when Array
|
|
131
|
+
transformed_array(
|
|
132
|
+
value, source:, target:, unit_id:, target_unit:, changes:, path:,
|
|
133
|
+
target_object:
|
|
134
|
+
)
|
|
135
|
+
when String
|
|
136
|
+
transformed_string(
|
|
137
|
+
value, source:, target:, target_object:, field: path.last
|
|
138
|
+
).tap do |replacement|
|
|
139
|
+
next if replacement == value
|
|
140
|
+
|
|
141
|
+
changes << RenameChange.new(
|
|
142
|
+
unit_id, source, path.map(&:to_s).freeze, value, replacement
|
|
143
|
+
)
|
|
144
|
+
end
|
|
145
|
+
else
|
|
146
|
+
value
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def transformed_hash(value, **context)
|
|
151
|
+
object_id = IO::BsonCodec.extract_id(value["$ID"])
|
|
152
|
+
target_object = context.fetch(:target_unit) &&
|
|
153
|
+
object_id == artifact_object_id(context.fetch(:source))
|
|
154
|
+
changed = false
|
|
155
|
+
result = value.each_with_object({}) do |(key, child), hash|
|
|
156
|
+
transformed = transform(
|
|
157
|
+
child, **context, target_object:, path: context.fetch(:path) + [key]
|
|
158
|
+
)
|
|
159
|
+
changed ||= !transformed.equal?(child)
|
|
160
|
+
hash[key] = transformed
|
|
161
|
+
end
|
|
162
|
+
changed ? result : value
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def transformed_array(value, **context)
|
|
166
|
+
changed = false
|
|
167
|
+
result = value.each_with_index.map do |child, index|
|
|
168
|
+
transformed = transform(
|
|
169
|
+
child, **context, target_object: false,
|
|
170
|
+
path: context.fetch(:path) + [index]
|
|
171
|
+
)
|
|
172
|
+
changed ||= !transformed.equal?(child)
|
|
173
|
+
transformed
|
|
174
|
+
end
|
|
175
|
+
changed ? result : value
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def transformed_string(value, source:, target:, target_object:, field:)
|
|
179
|
+
return value if METADATA_FIELDS.include?(field.to_s)
|
|
180
|
+
|
|
181
|
+
replacement = replace_qualified(value, source.qualified_name, target)
|
|
182
|
+
return replacement unless replacement == value
|
|
183
|
+
return value unless target_object && declaration_field?(field)
|
|
184
|
+
|
|
185
|
+
value == source.name ? target.split(".").last : value
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def artifact_object_id(artifact)
|
|
189
|
+
artifact.id.sub(/\A(?:module|entity|attribute|association|unit):/, "")
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def replace_qualified(value, source, target)
|
|
193
|
+
pairs = [[source, target]]
|
|
194
|
+
if source.count(".") == 2
|
|
195
|
+
pairs << [source.sub(/\.[^.]+\z/, "/\\0").sub("/.", "/"),
|
|
196
|
+
target.sub(/\.[^.]+\z/, "/\\0").sub("/.", "/")]
|
|
197
|
+
end
|
|
198
|
+
pairs.reduce(value) do |current, (old_name, new_name)|
|
|
199
|
+
current.gsub(
|
|
200
|
+
/(?<![A-Za-z0-9_])#{Regexp.escape(old_name)}(?![A-Za-z0-9_])/,
|
|
201
|
+
new_name
|
|
202
|
+
)
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def declaration_field?(field)
|
|
207
|
+
%w[Name name $QualifiedName].include?(field.to_s)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|