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,410 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "unit"
|
|
4
|
+
|
|
5
|
+
module Mxrb
|
|
6
|
+
module Model
|
|
7
|
+
# $Type: Pages$Page / Forms$Page
|
|
8
|
+
# ContainmentName: "Documents"
|
|
9
|
+
class Page < Unit
|
|
10
|
+
attr_reader :name, :documentation, :url, :layout_id, :title,
|
|
11
|
+
:popup_width, :popup_height, :popup_resizable, :excluded,
|
|
12
|
+
:allowed_module_roles, :parameters, :export_level,
|
|
13
|
+
:widgets, :data_source, :raw_document
|
|
14
|
+
|
|
15
|
+
def decode(doc)
|
|
16
|
+
@raw_document = doc
|
|
17
|
+
@storage_type = doc["$Type"] || "Forms$Page"
|
|
18
|
+
@name = doc["Name"] || doc["name"]
|
|
19
|
+
@documentation = doc["Documentation"] || doc["documentation"] || ""
|
|
20
|
+
@url = doc["Url"] || doc["URL"] || doc["url"] || ""
|
|
21
|
+
@title = extract_text(doc["Title"] || doc["title"])
|
|
22
|
+
@layout_id = extract_layout(doc)
|
|
23
|
+
@popup_width = doc["PopupWidth"] || 0
|
|
24
|
+
@popup_height = doc["PopupHeight"] || 0
|
|
25
|
+
@popup_resizable = doc["PopupResizable"] == true
|
|
26
|
+
@excluded = doc["Excluded"] == true
|
|
27
|
+
@export_level = doc["ExportLevel"] || "Hidden"
|
|
28
|
+
@allowed_module_roles = parse_array(doc["AllowedModuleRoles"] || doc["AllowedRoles"] || doc["allowedModuleRoles"])
|
|
29
|
+
@parameters = parse_array(doc["Parameters"] || doc["parameters"])
|
|
30
|
+
@widgets = []
|
|
31
|
+
parse_widgets(widget_roots(doc))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def to_bson
|
|
35
|
+
{
|
|
36
|
+
"$ID" => @id,
|
|
37
|
+
"$Type" => @storage_type,
|
|
38
|
+
"Name" => @name,
|
|
39
|
+
"Documentation" => @documentation || "",
|
|
40
|
+
"Url" => @url || "",
|
|
41
|
+
"Title" => serialize_title,
|
|
42
|
+
"Layout" => @layout_id,
|
|
43
|
+
"MarkAsUsed" => false,
|
|
44
|
+
"Excluded" => @excluded || false,
|
|
45
|
+
"AllowedModuleRoles" => IO::BsonCodec.build_array(@allowed_module_roles || [], marker: 1),
|
|
46
|
+
"Parameters" => IO::BsonCodec.build_array(@parameters || []),
|
|
47
|
+
"PopupWidth" => @popup_width || 0,
|
|
48
|
+
"PopupHeight" => @popup_height || 0,
|
|
49
|
+
"PopupResizable" => @popup_resizable || false,
|
|
50
|
+
"ExportLevel" => @export_level || "Hidden",
|
|
51
|
+
}
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def inspect
|
|
55
|
+
"#<Mxrb::Page name=#{@name.inspect} layout=#{@layout_id.to_s.slice(0, 8)}>"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def extract_text(obj)
|
|
61
|
+
return obj if obj.is_a?(String)
|
|
62
|
+
return "" unless obj.is_a?(Hash)
|
|
63
|
+
return extract_text(obj["Template"]) if obj["$Type"] == "Forms$ClientTemplate"
|
|
64
|
+
|
|
65
|
+
translations = parse_array(obj["Translations"] || obj["Items"] || obj["translations"] || [])
|
|
66
|
+
translation = translations.first
|
|
67
|
+
return "" unless translation.is_a?(Hash)
|
|
68
|
+
|
|
69
|
+
translation["Translation"] || translation["Text"] || translation["text"] || ""
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def extract_layout(doc)
|
|
73
|
+
return doc.dig("FormCall", "Form") if doc.dig("FormCall", "Form")
|
|
74
|
+
|
|
75
|
+
IO::BsonCodec.extract_id(doc["Layout"] || doc["LayoutId"])
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def serialize_title
|
|
79
|
+
{ "$ID" => SecureRandom.uuid, "$Type" => "Texts$Text", "Translations" => [1] }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def parse_widgets(items, target = @widgets)
|
|
83
|
+
items.each do |widget|
|
|
84
|
+
next unless widget.is_a?(Hash)
|
|
85
|
+
type = widget["$Type"]
|
|
86
|
+
if %w[Pages$DataView Forms$DataView].include?(type)
|
|
87
|
+
@data_source = parse_source(widget["DataSource"])
|
|
88
|
+
parse_widgets(child_widgets(widget), target)
|
|
89
|
+
next
|
|
90
|
+
end
|
|
91
|
+
if type == "CustomWidgets$CustomWidget"
|
|
92
|
+
target << pluggable_widget(widget)
|
|
93
|
+
next
|
|
94
|
+
end
|
|
95
|
+
if %w[Pages$DataGrid Forms$DataGrid].include?(type)
|
|
96
|
+
target << data_grid_widget(widget)
|
|
97
|
+
next
|
|
98
|
+
end
|
|
99
|
+
if %w[Pages$TabControl Forms$TabControl].include?(type)
|
|
100
|
+
target << tab_control_widget(widget)
|
|
101
|
+
next
|
|
102
|
+
end
|
|
103
|
+
if %w[Pages$SnippetCall Forms$SnippetCall Forms$SnippetCallWidget].include?(type)
|
|
104
|
+
snippet_ref = (
|
|
105
|
+
widget.dig("SnippetSettings", "Snippet") || widget.dig("FormCall", "Form")
|
|
106
|
+
).to_s
|
|
107
|
+
target << { type: :snippet, name: widget["Name"] || "snippet",
|
|
108
|
+
options: { snippet: snippet_ref }, events: [] }
|
|
109
|
+
next
|
|
110
|
+
end
|
|
111
|
+
if %w[Pages$Container Forms$Container Forms$DivContainer].include?(type)
|
|
112
|
+
children = []
|
|
113
|
+
parse_widgets(parse_array(widget["Widgets"]), children)
|
|
114
|
+
target << { type: :container, name: widget["Name"] || "container",
|
|
115
|
+
options: container_options(widget), children: children, events: [] }
|
|
116
|
+
next
|
|
117
|
+
end
|
|
118
|
+
if layout_container?(widget)
|
|
119
|
+
parse_widgets(child_widgets(widget), target)
|
|
120
|
+
parse_form_call_arguments(widget["Arguments"], target)
|
|
121
|
+
next
|
|
122
|
+
end
|
|
123
|
+
unless supported_widget?(type)
|
|
124
|
+
target << native_widget(widget)
|
|
125
|
+
next
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
events = {
|
|
129
|
+
"OnChangeAction" => :on_change,
|
|
130
|
+
"OnEnterAction" => :on_enter,
|
|
131
|
+
"OnLeaveAction" => :on_leave,
|
|
132
|
+
"Action" => :on_click
|
|
133
|
+
}.filter_map do |property, event|
|
|
134
|
+
action = parse_action(widget[property])
|
|
135
|
+
action&.merge(event: event)
|
|
136
|
+
end
|
|
137
|
+
widget_type = widget_type(type)
|
|
138
|
+
target << {
|
|
139
|
+
type: widget_type,
|
|
140
|
+
name: widget["Name"],
|
|
141
|
+
options: widget_options(widget, widget_type),
|
|
142
|
+
events: events
|
|
143
|
+
}
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def supported_widget?(type)
|
|
148
|
+
widget_type(type)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def widget_type(type)
|
|
152
|
+
case type
|
|
153
|
+
when "Pages$ActionButton", "Forms$ActionButton", "Forms$GridActionButton"
|
|
154
|
+
:button
|
|
155
|
+
when "Pages$TextBox", "Forms$TextBox"
|
|
156
|
+
:text_box
|
|
157
|
+
when "Pages$TextArea", "Forms$TextArea"
|
|
158
|
+
:text_area
|
|
159
|
+
when "Pages$CheckBox", "Forms$CheckBox"
|
|
160
|
+
:check_box
|
|
161
|
+
when "Pages$DatePicker", "Forms$DatePicker"
|
|
162
|
+
:date_picker
|
|
163
|
+
when "Pages$ReferenceSelector", "Forms$ReferenceSelector",
|
|
164
|
+
"Pages$InputReferenceSetSelector", "Forms$InputReferenceSetSelector"
|
|
165
|
+
:reference_selector
|
|
166
|
+
when "Pages$DynamicText", "Forms$DynamicText", "Pages$Label", "Forms$Label"
|
|
167
|
+
:text
|
|
168
|
+
when "Pages$DropDownWidget", "Forms$DropDownWidget", "Forms$DropDown"
|
|
169
|
+
:drop_down
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def widget_options(widget, widget_type)
|
|
174
|
+
options = {
|
|
175
|
+
attribute: attribute_path(widget),
|
|
176
|
+
caption: widget_caption(widget, widget_type)
|
|
177
|
+
}.compact
|
|
178
|
+
options[:lines] = widget["NumberOfLines"] if widget_type == :text_area && widget["NumberOfLines"]
|
|
179
|
+
options
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def container_options(widget)
|
|
183
|
+
class_name = widget.dig("Appearance", "Class") || widget["Class"]
|
|
184
|
+
class_name.to_s.empty? ? {} : { class: class_name }
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def attribute_path(widget)
|
|
188
|
+
widget["AttributePath"] || widget.dig("AttributeRef", "Attribute")
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def widget_caption(widget, widget_type)
|
|
192
|
+
return extract_text(widget["Content"] || widget["Text"] || widget["LabelText"]) if widget_type == :text
|
|
193
|
+
|
|
194
|
+
extract_text(
|
|
195
|
+
widget["Caption"] ||
|
|
196
|
+
widget["LabelText"] ||
|
|
197
|
+
widget["LabelTemplate"] ||
|
|
198
|
+
widget.dig("CaptionTemplate", "Template")
|
|
199
|
+
)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def data_grid_widget(widget)
|
|
203
|
+
columns = parse_array(widget["Columns"]).map do |column|
|
|
204
|
+
{
|
|
205
|
+
name: column["Name"],
|
|
206
|
+
attribute: attribute_path(column),
|
|
207
|
+
caption: extract_text(column["Caption"])
|
|
208
|
+
}.compact
|
|
209
|
+
end
|
|
210
|
+
options = { entity: grid_entity(widget), columns: columns }.compact
|
|
211
|
+
options[:search_bar] = parse_search_bar(widget["SearchBar"]) if widget["SearchBar"].is_a?(Hash)
|
|
212
|
+
options[:toolbar] = parse_toolbar(widget["ToolBar"]) if widget["ToolBar"].is_a?(Hash)
|
|
213
|
+
{ type: :data_grid, name: widget["Name"], options: options, events: [] }
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def pluggable_widget(widget)
|
|
217
|
+
case widget.dig("Type", "WidgetId")
|
|
218
|
+
when "com.mendix.widget.web.datagrid.Datagrid" then data_grid2_widget(widget)
|
|
219
|
+
when "com.mendix.widget.web.combobox.Combobox" then combo_box_widget(widget)
|
|
220
|
+
else native_widget(widget)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def native_widget(widget)
|
|
225
|
+
deep = widget.reject { |key, _| %w[$ID $Type Name].include?(key.to_s) }
|
|
226
|
+
{
|
|
227
|
+
type: :native_widget, name: widget["Name"] || "widget",
|
|
228
|
+
options: { native_type: widget["$Type"], deep_structure: deep }, events: []
|
|
229
|
+
}
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def data_grid2_widget(widget)
|
|
233
|
+
properties = custom_property_map(widget["Object"], widget.dig("Type", "ObjectType"))
|
|
234
|
+
entity = properties.dig("datasource", "DataSource", "EntityRef", "Entity")
|
|
235
|
+
column_type = custom_property_type(widget.dig("Type", "ObjectType"), "columns")
|
|
236
|
+
&.dig("ValueType", "ObjectType")
|
|
237
|
+
columns = parse_array(properties.dig("columns", "Objects")).map do |object|
|
|
238
|
+
values = custom_property_map(object, column_type)
|
|
239
|
+
{
|
|
240
|
+
name: extract_text(values.dig("header", "TextTemplate")),
|
|
241
|
+
attribute: values.dig("attribute", "AttributeRef", "Attribute"),
|
|
242
|
+
caption: extract_text(values.dig("header", "TextTemplate"))
|
|
243
|
+
}.compact
|
|
244
|
+
end
|
|
245
|
+
{
|
|
246
|
+
type: :data_grid, name: widget["Name"],
|
|
247
|
+
options: { entity: entity, columns: columns }.compact, events: []
|
|
248
|
+
}
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def combo_box_widget(widget)
|
|
252
|
+
properties = custom_property_map(widget["Object"], widget.dig("Type", "ObjectType"))
|
|
253
|
+
association = parse_array(properties.dig("attributeAssociation", "EntityRef", "Steps"))
|
|
254
|
+
.first&.fetch("Association", nil)
|
|
255
|
+
reference = properties.dig("optionsSourceType", "PrimitiveValue") == "association"
|
|
256
|
+
attribute = if reference
|
|
257
|
+
association
|
|
258
|
+
else
|
|
259
|
+
properties.dig("attributeEnumeration", "AttributeRef", "Attribute")
|
|
260
|
+
end
|
|
261
|
+
options = {
|
|
262
|
+
attribute: attribute, caption: extract_text(widget["LabelTemplate"])
|
|
263
|
+
}.compact
|
|
264
|
+
if reference
|
|
265
|
+
options[:display_attribute] = properties.dig(
|
|
266
|
+
"optionsSourceAssociationCaptionAttribute", "AttributeRef", "Attribute"
|
|
267
|
+
)
|
|
268
|
+
end
|
|
269
|
+
{ type: reference ? :reference_selector : :drop_down,
|
|
270
|
+
name: widget["Name"], options: options.compact, events: [] }
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def custom_property_map(object, object_type)
|
|
274
|
+
return {} unless object.is_a?(Hash) && object_type.is_a?(Hash)
|
|
275
|
+
|
|
276
|
+
types = parse_array(object_type["PropertyTypes"]).to_h do |type|
|
|
277
|
+
[IO::BsonCodec.extract_id(type["$ID"]), type["PropertyKey"]]
|
|
278
|
+
end
|
|
279
|
+
parse_array(object["Properties"]).to_h do |property|
|
|
280
|
+
[types[IO::BsonCodec.extract_id(property["TypePointer"])], property["Value"]]
|
|
281
|
+
end.compact
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def custom_property_type(object_type, key)
|
|
285
|
+
parse_array(object_type && object_type["PropertyTypes"]).find { _1["PropertyKey"] == key }
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def parse_search_bar(sb)
|
|
289
|
+
return nil unless sb.is_a?(Hash)
|
|
290
|
+
fields = parse_array(sb["SearchFields"]).filter_map do |sf|
|
|
291
|
+
attr_ref = sf.dig("AttributeRef", "Attribute") || sf["Attribute"] || sf["AttributePath"]
|
|
292
|
+
next unless attr_ref
|
|
293
|
+
{
|
|
294
|
+
attribute: attr_ref.to_s.split("/").last,
|
|
295
|
+
caption: extract_text(sf["Label"] || sf["Caption"])
|
|
296
|
+
}
|
|
297
|
+
end
|
|
298
|
+
fields.empty? ? nil : { fields: fields }
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def parse_toolbar(tb)
|
|
302
|
+
return nil unless tb.is_a?(Hash)
|
|
303
|
+
type_map = {
|
|
304
|
+
"Pages$GridNewButton" => :new,
|
|
305
|
+
"Forms$GridNewButton" => :new,
|
|
306
|
+
"Pages$GridDeleteButton" => :delete,
|
|
307
|
+
"Forms$GridDeleteButton" => :delete,
|
|
308
|
+
"Pages$GridSearchButton" => :search,
|
|
309
|
+
"Forms$GridSearchButton" => :search,
|
|
310
|
+
"Pages$GridExportToExcelButton" => :export,
|
|
311
|
+
"Forms$GridExportToExcelButton" => :export
|
|
312
|
+
}
|
|
313
|
+
buttons = parse_array(tb["Buttons"]).filter_map do |btn|
|
|
314
|
+
t = type_map[btn["$Type"]]
|
|
315
|
+
next unless t
|
|
316
|
+
{ type: t, caption: extract_text(btn["Caption"]) }
|
|
317
|
+
end
|
|
318
|
+
buttons.empty? ? nil : { buttons: buttons }
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def tab_control_widget(widget)
|
|
322
|
+
tabs = parse_array(widget["TabPages"]).map do |tab|
|
|
323
|
+
widgets = []
|
|
324
|
+
parse_widgets(parse_array(tab["Widgets"]), widgets)
|
|
325
|
+
{
|
|
326
|
+
name: tab["Name"],
|
|
327
|
+
caption: extract_text(tab["Caption"]),
|
|
328
|
+
widgets: widgets
|
|
329
|
+
}.compact
|
|
330
|
+
end
|
|
331
|
+
{
|
|
332
|
+
type: :tab_control,
|
|
333
|
+
name: widget["Name"],
|
|
334
|
+
options: { tabs: tabs },
|
|
335
|
+
events: []
|
|
336
|
+
}
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def grid_entity(widget)
|
|
340
|
+
widget.dig("DataSource", "Entity") ||
|
|
341
|
+
widget.dig("DataSource", "EntityRef", "Entity")
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def widget_roots(doc)
|
|
345
|
+
roots = parse_array(doc["Widgets"] || doc["widgets"])
|
|
346
|
+
roots.concat(form_call_widgets(doc["FormCall"]))
|
|
347
|
+
roots
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def form_call_widgets(form_call)
|
|
351
|
+
return [] unless form_call.is_a?(Hash)
|
|
352
|
+
|
|
353
|
+
parse_array(form_call["Arguments"]).flat_map do |argument|
|
|
354
|
+
child_widgets(argument)
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def parse_form_call_arguments(arguments, target = @widgets)
|
|
359
|
+
parse_array(arguments).each do |argument|
|
|
360
|
+
parse_widgets(child_widgets(argument), target)
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def layout_container?(widget)
|
|
365
|
+
child_widgets(widget).any? || widget.key?("Arguments")
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def child_widgets(widget)
|
|
369
|
+
direct = parse_array(widget["Widgets"]) + parse_array(widget["FooterWidgets"])
|
|
370
|
+
tab_widgets = parse_array(widget["TabPages"]).flat_map { parse_array(_1["Widgets"]) }
|
|
371
|
+
row_columns = parse_array(widget["Rows"]).flat_map { parse_array(_1["Columns"]) }
|
|
372
|
+
column_widgets = row_columns.flat_map { parse_array(_1["Widgets"]) }
|
|
373
|
+
direct + tab_widgets + column_widgets
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
def parse_source(source)
|
|
377
|
+
return nil unless source.is_a?(Hash)
|
|
378
|
+
result = if %w[Pages$NanoflowSource Forms$NanoflowSource].include?(source["$Type"])
|
|
379
|
+
{ kind: :nanoflow, name: local_name(source["Nanoflow"] || source.dig("NanoflowSettings", "Nanoflow")) }
|
|
380
|
+
else
|
|
381
|
+
{ kind: :microflow, name: local_name(source["Microflow"] || source.dig("MicroflowSettings", "Microflow")) }
|
|
382
|
+
end
|
|
383
|
+
result[:name].to_s.empty? ? nil : result
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
def parse_action(action)
|
|
387
|
+
return nil unless action.is_a?(Hash)
|
|
388
|
+
if %w[Pages$CallNanoflowClientAction Forms$CallNanoflowClientAction].include?(action["$Type"])
|
|
389
|
+
handler = local_name(action["Nanoflow"] || action.dig("NanoflowSettings", "Nanoflow"))
|
|
390
|
+
handler.to_s.empty? ? nil : { kind: :nanoflow, handler: handler }
|
|
391
|
+
elsif %w[Pages$MicroflowClientAction Forms$MicroflowAction Forms$MicroflowClientAction].include?(action["$Type"])
|
|
392
|
+
handler = local_name(action["Microflow"] || action.dig("MicroflowSettings", "Microflow"))
|
|
393
|
+
handler.to_s.empty? ? nil : { kind: :microflow, handler: handler }
|
|
394
|
+
elsif %w[Pages$SaveChangesClientAction Forms$SaveChangesClientAction].include?(action["$Type"])
|
|
395
|
+
{ kind: :action, handler: "save_changes" }
|
|
396
|
+
elsif %w[Pages$CancelChangesClientAction Forms$CancelChangesClientAction].include?(action["$Type"])
|
|
397
|
+
{ kind: :action, handler: "cancel_changes" }
|
|
398
|
+
elsif %w[Pages$DeleteClientAction Forms$DeleteClientAction].include?(action["$Type"])
|
|
399
|
+
{ kind: :action, handler: "delete" }
|
|
400
|
+
elsif %w[Pages$ClosePageClientAction Forms$ClosePageClientAction].include?(action["$Type"])
|
|
401
|
+
{ kind: :action, handler: "close_page" }
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
def local_name(name)
|
|
406
|
+
name.to_s.split(".").last
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
end
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../io/mpr_file"
|
|
4
|
+
|
|
5
|
+
module Mxrb
|
|
6
|
+
module Model
|
|
7
|
+
class Project
|
|
8
|
+
attr_reader :mpr
|
|
9
|
+
|
|
10
|
+
def self.open(path, readonly: true)
|
|
11
|
+
new(IO::MprFile.open(path, readonly: readonly))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(mpr)
|
|
15
|
+
@mpr = mpr
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def name = @mpr.project_name
|
|
19
|
+
def mendix_version = @mpr.mendix_version
|
|
20
|
+
def format_version = @mpr.format_version
|
|
21
|
+
|
|
22
|
+
# ── Artefacts ──────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
def modules
|
|
25
|
+
@modules ||= @mpr.units_by_containment("Modules").map { Module.new(_1, @mpr) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def entities = modules.flat_map(&:entities)
|
|
29
|
+
def pages = modules.flat_map(&:pages)
|
|
30
|
+
def microflows = modules.flat_map(&:microflows)
|
|
31
|
+
|
|
32
|
+
def navigation
|
|
33
|
+
@navigation ||= begin
|
|
34
|
+
raw = all_units.find { parse_bson(_1)["$Type"] == "Navigation$NavigationDocument" }
|
|
35
|
+
Navigation.new(raw && parse_bson(raw))
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def design_system = (@design_system ||= DesignSystem.new(File.dirname(@mpr.path)))
|
|
40
|
+
def plan_design_token_migration(replacements) =
|
|
41
|
+
design_system.plan_literal_migration(replacements)
|
|
42
|
+
|
|
43
|
+
# ── Low-level exploration ───────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
def all_units = @mpr.all_units
|
|
46
|
+
def tables = @mpr.tables
|
|
47
|
+
def table_info(n) = @mpr.table_info(n)
|
|
48
|
+
def query(sql, *b) = @mpr.query(sql, *b)
|
|
49
|
+
def raw_unit(uuid) = @mpr.unit(uuid)
|
|
50
|
+
def children_of(uuid) = @mpr.children_of(uuid)
|
|
51
|
+
def parse_bson(raw) = @mpr.parse_contents(raw)
|
|
52
|
+
def architecture_definition = @mpr.architecture_definition
|
|
53
|
+
|
|
54
|
+
# ── Native OQL inspection ───────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
def oql_queries = (@oql_catalog ||= Oql::Catalog.new(self)).queries
|
|
57
|
+
def oql? = !oql_queries.empty?
|
|
58
|
+
def oql_sql_views(dialect: :postgresql)
|
|
59
|
+
translator = Oql::Translator.new(dialect:)
|
|
60
|
+
oql_queries.map { translator.translate(_1) }.freeze
|
|
61
|
+
end
|
|
62
|
+
def oql_analysis(dialect: :postgresql)
|
|
63
|
+
analyzer = Oql::Analyzer.new(dialect:)
|
|
64
|
+
oql_queries.map { analyzer.analyze(_1) }.freeze
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# ── Semantic analysis ───────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
def semantic_index = (@semantic_index ||= Semantic::Index.new(self))
|
|
70
|
+
def semantic_cache_info
|
|
71
|
+
@mpr.index_cache_info(
|
|
72
|
+
current_fingerprint: Semantic::Index.fingerprint(self)
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
def warm_semantic_cache!
|
|
76
|
+
semantic_index
|
|
77
|
+
semantic_cache_info
|
|
78
|
+
end
|
|
79
|
+
def clear_semantic_cache!
|
|
80
|
+
removed = @mpr.clear_index_cache!
|
|
81
|
+
@semantic_index = nil
|
|
82
|
+
removed
|
|
83
|
+
end
|
|
84
|
+
def find_artifact(name, kind: nil) = semantic_index.find(name, kind: kind)
|
|
85
|
+
def search_artifacts(query, **filters) = semantic_index.search(query, **filters)
|
|
86
|
+
def semantic_search_artifacts(query, **options) =
|
|
87
|
+
semantic_index.semantic_search_artifacts(query, **options)
|
|
88
|
+
def semantic_search_hits(query, **options) =
|
|
89
|
+
semantic_index.semantic_search_hits(query, **options)
|
|
90
|
+
def describe_artifact(name) = semantic_index.describe(name)
|
|
91
|
+
def references_to(name) = semantic_index.references_to(name)
|
|
92
|
+
def references_from(name) = semantic_index.references_from(name)
|
|
93
|
+
def callers_of(name) = semantic_index.callers_of(name)
|
|
94
|
+
def callees_of(name) = semantic_index.callees_of(name)
|
|
95
|
+
def impact_of(name, transitive: true) =
|
|
96
|
+
semantic_index.impact_of(name, transitive: transitive)
|
|
97
|
+
def plan_rename(name, to:) = Semantic::Renamer.new(self).plan(name, to: to)
|
|
98
|
+
def rename!(name, to:) = plan_rename(name, to: to).apply!
|
|
99
|
+
def plan_remove(name) = Semantic::Remover.new(self).plan(name)
|
|
100
|
+
def remove!(name) = plan_remove(name).apply!
|
|
101
|
+
def plan_move(name, to:) = Semantic::Mover.new(self).plan(name, to: to)
|
|
102
|
+
def move!(name, to:) = plan_move(name, to: to).apply!
|
|
103
|
+
def plan_extract(name, as:, object_ids:) =
|
|
104
|
+
Semantic::Extractor.new(self).plan(name, as: as, object_ids: object_ids)
|
|
105
|
+
def extract!(name, as:, object_ids:) = plan_extract(name, as: as, object_ids: object_ids).apply!
|
|
106
|
+
def plan_inline(name, calling:) = Semantic::Inliner.new(self).plan(name, calling: calling)
|
|
107
|
+
def inline!(name, calling:) = plan_inline(name, calling: calling).apply!
|
|
108
|
+
def analyze(**options) = Semantic::Analyzer.new(self).analyze(**options)
|
|
109
|
+
alias lint analyze
|
|
110
|
+
|
|
111
|
+
# ── Domain model mutation ───────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
def plan_add_attribute(entity, name:, type: :string, **opts) =
|
|
114
|
+
Semantic::DomainMutator.new(self).plan_add_attribute(entity, name: name, type: type, **opts)
|
|
115
|
+
def add_attribute!(entity, name:, type: :string, **opts) =
|
|
116
|
+
plan_add_attribute(entity, name: name, type: type, **opts).apply!
|
|
117
|
+
|
|
118
|
+
def plan_remove_attribute(attribute) =
|
|
119
|
+
Semantic::DomainMutator.new(self).plan_remove_attribute(attribute)
|
|
120
|
+
def remove_attribute!(attribute) = plan_remove_attribute(attribute).apply!
|
|
121
|
+
|
|
122
|
+
def plan_change_attribute(attribute, **updates) =
|
|
123
|
+
Semantic::DomainMutator.new(self).plan_change_attribute(attribute, **updates)
|
|
124
|
+
def change_attribute!(attribute, **updates) = plan_change_attribute(attribute, **updates).apply!
|
|
125
|
+
|
|
126
|
+
def plan_add_entity(mod, name:, **opts) =
|
|
127
|
+
Semantic::DomainMutator.new(self).plan_add_entity(mod, name: name, **opts)
|
|
128
|
+
def add_entity!(mod, name:, **opts) = plan_add_entity(mod, name: name, **opts).apply!
|
|
129
|
+
|
|
130
|
+
def plan_remove_entity(entity) =
|
|
131
|
+
Semantic::DomainMutator.new(self).plan_remove_entity(entity)
|
|
132
|
+
def remove_entity!(entity) = plan_remove_entity(entity).apply!
|
|
133
|
+
|
|
134
|
+
# ── Batch plan ──────────────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
def batch_plan(plans) = Semantic::BatchPlan.new(project: self, plans: Array(plans))
|
|
137
|
+
def evaluate(&block)
|
|
138
|
+
Evaluation::Suite.new(self).tap { _1.instance_eval(&block) }.run
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Structurally migrates the project to a different Mendix version.
|
|
142
|
+
# Requires an MXRB architecture definition stored in the MPR
|
|
143
|
+
# (i.e. the project was generated by MXRB). Re-runs the Writer with
|
|
144
|
+
# the target version so all version-specific BSON structures are
|
|
145
|
+
# regenerated correctly. Idempotent — safe to call multiple times.
|
|
146
|
+
def migrate_to!(version)
|
|
147
|
+
v = version.to_s
|
|
148
|
+
raise ArgumentError, "version must be a non-empty string" if v.empty?
|
|
149
|
+
|
|
150
|
+
definition = @mpr.architecture_definition
|
|
151
|
+
raise ArgumentError,
|
|
152
|
+
"no MXRB architecture definition found in this MPR — " \
|
|
153
|
+
"structural migration requires a project generated by MXRB" unless definition
|
|
154
|
+
|
|
155
|
+
path = @mpr.path
|
|
156
|
+
readonly = @mpr.readonly?
|
|
157
|
+
raise ArgumentError, "project is opened read-only; reopen with readonly: false" if readonly
|
|
158
|
+
|
|
159
|
+
updated = definition.merge(version: v)
|
|
160
|
+
@mpr.close
|
|
161
|
+
Writer.new(path, updated).write!
|
|
162
|
+
@mpr = IO::MprFile.open(path, readonly: false)
|
|
163
|
+
@mpr.update_version!(v)
|
|
164
|
+
refresh!
|
|
165
|
+
self
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
alias upgrade_to! migrate_to!
|
|
169
|
+
alias downgrade_to! migrate_to!
|
|
170
|
+
|
|
171
|
+
def refresh!
|
|
172
|
+
@modules = nil
|
|
173
|
+
@semantic_index = nil
|
|
174
|
+
@navigation = nil
|
|
175
|
+
@design_system = nil
|
|
176
|
+
@oql_catalog = nil
|
|
177
|
+
self
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def close = @mpr.close
|
|
181
|
+
|
|
182
|
+
def inspect
|
|
183
|
+
"#<Mxrb::Project name=#{name.inspect} version=#{mendix_version.inspect} format=#{format_version}>"
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../io/bson_codec"
|
|
4
|
+
|
|
5
|
+
module Mxrb
|
|
6
|
+
module Model
|
|
7
|
+
# Base for all Mendix artefacts.
|
|
8
|
+
# Each subclass maps to a unit row whose Contents BSON has a specific $Type.
|
|
9
|
+
class Unit
|
|
10
|
+
attr_reader :id, :container_id, :containment_name, :mpr, :bson_type
|
|
11
|
+
|
|
12
|
+
def initialize(raw, mpr)
|
|
13
|
+
@id = raw["UnitID"]
|
|
14
|
+
@container_id = raw["ContainerID"]
|
|
15
|
+
@containment_name = raw["ContainmentName"]
|
|
16
|
+
@mpr = mpr
|
|
17
|
+
doc = mpr.parse_contents(raw)
|
|
18
|
+
@bson_type = doc["$Type"] || doc["\$Type"]
|
|
19
|
+
decode(doc)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Subclasses implement these
|
|
23
|
+
def decode(_doc); end
|
|
24
|
+
def to_bson; raise NotImplementedError, "#{self.class}#to_bson not implemented"; end
|
|
25
|
+
|
|
26
|
+
def save!
|
|
27
|
+
doc = to_bson
|
|
28
|
+
if @id
|
|
29
|
+
@mpr.update_unit(@id, doc)
|
|
30
|
+
else
|
|
31
|
+
raise "Cannot insert without container_id" unless @container_id
|
|
32
|
+
@id = @mpr.insert_unit(
|
|
33
|
+
container_uuid: @container_id,
|
|
34
|
+
containment_name: @containment_name,
|
|
35
|
+
contents_doc: doc
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
self
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
protected
|
|
42
|
+
|
|
43
|
+
# Helpers for BSON field extraction
|
|
44
|
+
def extract_id(val) = IO::BsonCodec.extract_id(val)
|
|
45
|
+
def parse_array(arr) = IO::BsonCodec.parse_array(arr)[:items]
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|