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.
Files changed (237) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.de-DE.md +108 -0
  4. data/README.md +172 -0
  5. data/README.pt-BR.md +109 -0
  6. data/bin/mxrb +1613 -0
  7. data/docs/README.md +10 -0
  8. data/docs/de-DE/README.md +24 -0
  9. data/docs/de-DE/architectural-patterns.md +26 -0
  10. data/docs/de-DE/architectural-standard.md +48 -0
  11. data/docs/de-DE/architecture.md +76 -0
  12. data/docs/de-DE/compiler.md +162 -0
  13. data/docs/de-DE/conventions.md +19 -0
  14. data/docs/de-DE/design-system.md +52 -0
  15. data/docs/de-DE/entity-dsl.md +53 -0
  16. data/docs/de-DE/native-runtime-quality-report.md +74 -0
  17. data/docs/de-DE/oql-sql.md +211 -0
  18. data/docs/de-DE/platform-api-integration.md +36 -0
  19. data/docs/de-DE/platform-operations.md +243 -0
  20. data/docs/de-DE/project-structure.md +32 -0
  21. data/docs/de-DE/ruby-first-roadmap.md +116 -0
  22. data/docs/de-DE/scaffolds.md +59 -0
  23. data/docs/de-DE/semantic-refactoring.md +56 -0
  24. data/docs/de-DE/team-server.md +33 -0
  25. data/docs/de-DE/validation-matrix.md +64 -0
  26. data/docs/de-DE/vetclinic-acceptance.md +24 -0
  27. data/docs/de-DE/writing.md +138 -0
  28. data/docs/en-US/README.md +24 -0
  29. data/docs/en-US/architectural-patterns.md +151 -0
  30. data/docs/en-US/architectural-standard.md +89 -0
  31. data/docs/en-US/architecture.md +170 -0
  32. data/docs/en-US/compiler.md +157 -0
  33. data/docs/en-US/conventions.md +82 -0
  34. data/docs/en-US/design-system.md +163 -0
  35. data/docs/en-US/entity-dsl.md +53 -0
  36. data/docs/en-US/native-runtime-quality-report.md +73 -0
  37. data/docs/en-US/oql-sql.md +201 -0
  38. data/docs/en-US/platform-api-integration.md +36 -0
  39. data/docs/en-US/platform-operations.md +244 -0
  40. data/docs/en-US/project-structure.md +49 -0
  41. data/docs/en-US/ruby-first-roadmap.md +186 -0
  42. data/docs/en-US/scaffolds.md +57 -0
  43. data/docs/en-US/semantic-refactoring.md +198 -0
  44. data/docs/en-US/team-server.md +32 -0
  45. data/docs/en-US/validation-matrix.md +257 -0
  46. data/docs/en-US/vetclinic-acceptance.md +23 -0
  47. data/docs/en-US/writing.md +655 -0
  48. data/docs/pt-BR/README.md +24 -0
  49. data/docs/pt-BR/architectural-patterns.md +25 -0
  50. data/docs/pt-BR/architectural-standard.md +533 -0
  51. data/docs/pt-BR/architecture.md +300 -0
  52. data/docs/pt-BR/compiler.md +162 -0
  53. data/docs/pt-BR/conventions.md +19 -0
  54. data/docs/pt-BR/design-system.md +65 -0
  55. data/docs/pt-BR/entity-dsl.md +116 -0
  56. data/docs/pt-BR/native-runtime-quality-report.md +73 -0
  57. data/docs/pt-BR/oql-sql.md +208 -0
  58. data/docs/pt-BR/platform-api-integration.md +36 -0
  59. data/docs/pt-BR/platform-operations.md +260 -0
  60. data/docs/pt-BR/project-structure.md +32 -0
  61. data/docs/pt-BR/ruby-first-roadmap.md +239 -0
  62. data/docs/pt-BR/scaffolds.md +104 -0
  63. data/docs/pt-BR/semantic-refactoring.md +55 -0
  64. data/docs/pt-BR/team-server.md +59 -0
  65. data/docs/pt-BR/validation-matrix.md +86 -0
  66. data/docs/pt-BR/vetclinic-acceptance.md +50 -0
  67. data/docs/pt-BR/writing.md +177 -0
  68. data/examples/sudoku_evaluation.rb +14 -0
  69. data/examples/sudoku_functional_test.rb +28 -0
  70. data/lib/mxrb/architecture/graph.rb +124 -0
  71. data/lib/mxrb/architecture/validator.rb +98 -0
  72. data/lib/mxrb/benchmark.rb +33 -0
  73. data/lib/mxrb/compare.rb +390 -0
  74. data/lib/mxrb/compiler/adapter.rb +96 -0
  75. data/lib/mxrb/compiler/artifact_document_compiler.rb +102 -0
  76. data/lib/mxrb/compiler/artifact_materializer.rb +53 -0
  77. data/lib/mxrb/compiler/client_model_materializer.rb +42 -0
  78. data/lib/mxrb/compiler/code_action_document_compiler.rb +64 -0
  79. data/lib/mxrb/compiler/code_action_materializer.rb +35 -0
  80. data/lib/mxrb/compiler/code_action_type_compiler.rb +32 -0
  81. data/lib/mxrb/compiler/combo_box_bundle_compiler.rb +314 -0
  82. data/lib/mxrb/compiler/compatibility_analyzer.rb +169 -0
  83. data/lib/mxrb/compiler/constants_materializer.rb +111 -0
  84. data/lib/mxrb/compiler/data_grid_bundle_compiler.rb +290 -0
  85. data/lib/mxrb/compiler/database_connector_action_compiler.rb +280 -0
  86. data/lib/mxrb/compiler/deployment_asset_copier.rb +60 -0
  87. data/lib/mxrb/compiler/deployment_bootstrapper.rb +208 -0
  88. data/lib/mxrb/compiler/deployment_materializer.rb +43 -0
  89. data/lib/mxrb/compiler/deployment_metadata.rb +64 -0
  90. data/lib/mxrb/compiler/domain_document_compiler.rb +153 -0
  91. data/lib/mxrb/compiler/domain_model_materializer.rb +35 -0
  92. data/lib/mxrb/compiler/domain_security_compiler.rb +82 -0
  93. data/lib/mxrb/compiler/gallery_bundle_compiler.rb +190 -0
  94. data/lib/mxrb/compiler/generic_widget_bundle_compiler.rb +281 -0
  95. data/lib/mxrb/compiler/image_bundle_compiler.rb +216 -0
  96. data/lib/mxrb/compiler/java_proxy_generator.rb +509 -0
  97. data/lib/mxrb/compiler/legacy_data_grid_compiler.rb +267 -0
  98. data/lib/mxrb/compiler/legacy_page_builder.rb +287 -0
  99. data/lib/mxrb/compiler/mda.rb +99 -0
  100. data/lib/mxrb/compiler/microflow_document_compiler.rb +85 -0
  101. data/lib/mxrb/compiler/microflow_materializer.rb +48 -0
  102. data/lib/mxrb/compiler/microflow_node_compiler.rb +250 -0
  103. data/lib/mxrb/compiler/model_package.rb +116 -0
  104. data/lib/mxrb/compiler/model_values.rb +67 -0
  105. data/lib/mxrb/compiler/nanoflow_program_compiler.rb +483 -0
  106. data/lib/mxrb/compiler/navigation_document_compiler.rb +93 -0
  107. data/lib/mxrb/compiler/packager.rb +100 -0
  108. data/lib/mxrb/compiler/page_bundle_builder.rb +56 -0
  109. data/lib/mxrb/compiler/page_bundle_compiler.rb +2004 -0
  110. data/lib/mxrb/compiler/page_document_compiler.rb +77 -0
  111. data/lib/mxrb/compiler/portable_packager.rb +356 -0
  112. data/lib/mxrb/compiler/project_jar_archive.rb +84 -0
  113. data/lib/mxrb/compiler/project_jar_builder.rb +121 -0
  114. data/lib/mxrb/compiler/project_materializer.rb +139 -0
  115. data/lib/mxrb/compiler/project_model_orderer.rb +52 -0
  116. data/lib/mxrb/compiler/runtime_data_types.rb +31 -0
  117. data/lib/mxrb/compiler/runtime_model_schema.rb +90 -0
  118. data/lib/mxrb/compiler/schemas/runtime-10.24.0.73019.json +1052 -0
  119. data/lib/mxrb/compiler/schemas/runtime-11.json +1215 -0
  120. data/lib/mxrb/compiler/schemas/runtime-6.10.8.json +1148 -0
  121. data/lib/mxrb/compiler/schemas/runtime-7.17.0.json +1144 -0
  122. data/lib/mxrb/compiler/schemas/runtime-7.5.0.json +1215 -0
  123. data/lib/mxrb/compiler/schemas/runtime-9.6.1.29396.json +868 -0
  124. data/lib/mxrb/compiler/schemas/system-model-10.24.0.73019.b64 +1135 -0
  125. data/lib/mxrb/compiler/schemas/system-model-11.12.1.b64 +815 -0
  126. data/lib/mxrb/compiler/schemas/system-model-6.10.8.b64 +497 -0
  127. data/lib/mxrb/compiler/schemas/system-model-7.17.0.b64 +519 -0
  128. data/lib/mxrb/compiler/schemas/system-model-7.5.0.b64 +504 -0
  129. data/lib/mxrb/compiler/schemas/system-model-9.6.1.29396.b64 +816 -0
  130. data/lib/mxrb/compiler/security_materializer.rb +104 -0
  131. data/lib/mxrb/compiler/settings_document_compiler.rb +45 -0
  132. data/lib/mxrb/compiler/settings_materializer.rb +25 -0
  133. data/lib/mxrb/compiler/source_model.rb +128 -0
  134. data/lib/mxrb/compiler/system_model_seed.rb +88 -0
  135. data/lib/mxrb/compiler/system_queue_materializer.rb +54 -0
  136. data/lib/mxrb/compiler/system_text_materializer.rb +49 -0
  137. data/lib/mxrb/compiler/translation_materializer.rb +95 -0
  138. data/lib/mxrb/compiler/web_bundle_builder.rb +155 -0
  139. data/lib/mxrb/compiler/web_list_data_source.rb +139 -0
  140. data/lib/mxrb/compiler/web_operation_compiler.rb +614 -0
  141. data/lib/mxrb/compiler/web_shell_materializer.rb +194 -0
  142. data/lib/mxrb/compiler/widget_package_extractor.rb +63 -0
  143. data/lib/mxrb/doctor.rb +116 -0
  144. data/lib/mxrb/dsl/builder.rb +1636 -0
  145. data/lib/mxrb/errors.rb +18 -0
  146. data/lib/mxrb/evaluation.rb +131 -0
  147. data/lib/mxrb/exporter.rb +1716 -0
  148. data/lib/mxrb/frontend/migrator.rb +775 -0
  149. data/lib/mxrb/functional.rb +307 -0
  150. data/lib/mxrb/github/annotator.rb +161 -0
  151. data/lib/mxrb/initializer.rb +257 -0
  152. data/lib/mxrb/integrity/validator.rb +136 -0
  153. data/lib/mxrb/io/bson_codec.rb +145 -0
  154. data/lib/mxrb/io/mpr_file.rb +558 -0
  155. data/lib/mxrb/io/mxunit_codec.rb +42 -0
  156. data/lib/mxrb/marketplace.rb +380 -0
  157. data/lib/mxrb/model/association.rb +98 -0
  158. data/lib/mxrb/model/attribute.rb +100 -0
  159. data/lib/mxrb/model/connector.rb +17 -0
  160. data/lib/mxrb/model/design_materializer.rb +133 -0
  161. data/lib/mxrb/model/design_migration.rb +126 -0
  162. data/lib/mxrb/model/design_system.rb +111 -0
  163. data/lib/mxrb/model/domain_model.rb +51 -0
  164. data/lib/mxrb/model/entity.rb +180 -0
  165. data/lib/mxrb/model/menu.rb +38 -0
  166. data/lib/mxrb/model/microflow.rb +100 -0
  167. data/lib/mxrb/model/module.rb +142 -0
  168. data/lib/mxrb/model/navigation.rb +127 -0
  169. data/lib/mxrb/model/page.rb +410 -0
  170. data/lib/mxrb/model/project.rb +187 -0
  171. data/lib/mxrb/model/unit.rb +48 -0
  172. data/lib/mxrb/module_initializer.rb +75 -0
  173. data/lib/mxrb/official_marketplace/content_api.rb +292 -0
  174. data/lib/mxrb/official_marketplace/dependency_resolver.rb +329 -0
  175. data/lib/mxrb/official_marketplace/lifecycle.rb +367 -0
  176. data/lib/mxrb/official_marketplace/module_package_importer.rb +318 -0
  177. data/lib/mxrb/official_marketplace/widget_package_installer.rb +414 -0
  178. data/lib/mxrb/official_marketplace.rb +720 -0
  179. data/lib/mxrb/oql/analyzer.rb +155 -0
  180. data/lib/mxrb/oql/index_advisor.rb +85 -0
  181. data/lib/mxrb/oql/plan_analyzer.rb +185 -0
  182. data/lib/mxrb/oql/server.rb +132 -0
  183. data/lib/mxrb/oql/sql_server_plan_analyzer.rb +190 -0
  184. data/lib/mxrb/oql/sql_server_workload_analyzer.rb +80 -0
  185. data/lib/mxrb/oql/workload_analyzer.rb +175 -0
  186. data/lib/mxrb/oql/workload_baseline.rb +60 -0
  187. data/lib/mxrb/oql.rb +424 -0
  188. data/lib/mxrb/project_lifecycle.rb +76 -0
  189. data/lib/mxrb/protocols/plan.rb +86 -0
  190. data/lib/mxrb/protocols.rb +123 -0
  191. data/lib/mxrb/runtime/database_workspace.rb +677 -0
  192. data/lib/mxrb/runtime/docker_executor.rb +41 -0
  193. data/lib/mxrb/runtime/docker_workspace.rb +29 -0
  194. data/lib/mxrb/runtime/executor.rb +169 -0
  195. data/lib/mxrb/runtime/java_locator.rb +26 -0
  196. data/lib/mxrb/runtime/native.rb +474 -0
  197. data/lib/mxrb/runtime/sql_server_database.rb +146 -0
  198. data/lib/mxrb/runtime/toolchain.rb +82 -0
  199. data/lib/mxrb/scaffold/cli.rb +113 -0
  200. data/lib/mxrb/scaffold/generator.rb +246 -0
  201. data/lib/mxrb/scaffold/help.rb +80 -0
  202. data/lib/mxrb/scaffold/page_templates.rb +54 -0
  203. data/lib/mxrb/scaffold/recipes.rb +198 -0
  204. data/lib/mxrb/scaffold/registry.rb +82 -0
  205. data/lib/mxrb/scaffold/templates.rb +723 -0
  206. data/lib/mxrb/scaffold/transaction.rb +84 -0
  207. data/lib/mxrb/schema/tables.rb +55 -0
  208. data/lib/mxrb/semantic/analyzer.rb +473 -0
  209. data/lib/mxrb/semantic/batch_plan.rb +68 -0
  210. data/lib/mxrb/semantic/domain_mutator.rb +419 -0
  211. data/lib/mxrb/semantic/embedder.rb +51 -0
  212. data/lib/mxrb/semantic/extractor.rb +290 -0
  213. data/lib/mxrb/semantic/index.rb +623 -0
  214. data/lib/mxrb/semantic/inliner.rb +216 -0
  215. data/lib/mxrb/semantic/mover.rb +153 -0
  216. data/lib/mxrb/semantic/onnx_embedder.rb +34 -0
  217. data/lib/mxrb/semantic/remover.rb +70 -0
  218. data/lib/mxrb/semantic/renamer.rb +211 -0
  219. data/lib/mxrb/semantic/tfidf_embedder.rb +46 -0
  220. data/lib/mxrb/semantic/vec_store.rb +75 -0
  221. data/lib/mxrb/team_server.rb +390 -0
  222. data/lib/mxrb/templates/project/10.24.0.73019.json +32 -0
  223. data/lib/mxrb/templates/project/11.12.1.json +32 -0
  224. data/lib/mxrb/templates/project/6.10.8.json +50 -0
  225. data/lib/mxrb/templates/project/7.17.0.json +50 -0
  226. data/lib/mxrb/templates/project/7.5.0.json +50 -0
  227. data/lib/mxrb/templates/project/9.6.1.29396.json +50 -0
  228. data/lib/mxrb/version.rb +5 -0
  229. data/lib/mxrb/widget_package.rb +338 -0
  230. data/lib/mxrb/widget_synchronizer.rb +31 -0
  231. data/lib/mxrb/writer.rb +3775 -0
  232. data/lib/mxrb.rb +188 -0
  233. data/marketplace/catalog.json +10 -0
  234. data/marketplace/modules/shared-kernel/domain/README.md +4 -0
  235. data/marketplace/modules/shared-kernel/module.rb +5 -0
  236. data/marketplace/modules/shared-kernel/mxrb-module.json +9 -0
  237. 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