mendix-ruby-bridge 0.1.1
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/.mxcli-version +2 -0
- data/LICENSE +21 -0
- data/README.md +77 -0
- data/bin/git-mendix +4 -0
- data/bin/mendix-apply +145 -0
- data/bin/mendix-desktop +26 -0
- data/bin/mendix-git +121 -0
- data/bin/mendix-ruby +580 -0
- data/bin/mxcli +20 -0
- data/bin/setup-tools +25 -0
- data/lib/mendix_bridge/backend_server.rb +1404 -0
- data/lib/mendix_bridge/change_planner.rb +594 -0
- data/lib/mendix_bridge/config.rb +89 -0
- data/lib/mendix_bridge/dependency_index.rb +188 -0
- data/lib/mendix_bridge/desktop_app.rb +121 -0
- data/lib/mendix_bridge/document_parser.rb +120 -0
- data/lib/mendix_bridge/domain_parser.rb +103 -0
- data/lib/mendix_bridge/dsl.rb +540 -0
- data/lib/mendix_bridge/enumeration_parser.rb +32 -0
- data/lib/mendix_bridge/git_workflow.rb +321 -0
- data/lib/mendix_bridge/html_viewer.rb +672 -0
- data/lib/mendix_bridge/importer.rb +415 -0
- data/lib/mendix_bridge/inventory.rb +93 -0
- data/lib/mendix_bridge/mdl_generator.rb +343 -0
- data/lib/mendix_bridge/microflow_parser.rb +131 -0
- data/lib/mendix_bridge/migration.rb +290 -0
- data/lib/mendix_bridge/migration_executor.rb +234 -0
- data/lib/mendix_bridge/model.rb +204 -0
- data/lib/mendix_bridge/page_parser.rb +95 -0
- data/lib/mendix_bridge/presenter.rb +35 -0
- data/lib/mendix_bridge/project_creator.rb +181 -0
- data/lib/mendix_bridge/ruby_inventory_generator.rb +63 -0
- data/lib/mendix_bridge/security_parser.rb +72 -0
- data/lib/mendix_bridge/snapshot_diff.rb +58 -0
- data/lib/mendix_bridge/validator.rb +46 -0
- data/lib/mendix_bridge/version.rb +5 -0
- data/lib/mendix_bridge/visual_entity_plan.rb +176 -0
- data/lib/mendix_bridge.rb +127 -0
- data/share/applications/mendix-ruby-bridge.desktop +12 -0
- data/share/icons/hicolor/128x128/apps/mendix-ruby-bridge.png +0 -0
- data/share/icons/hicolor/256x256/apps/mendix-ruby-bridge.png +0 -0
- data/share/icons/hicolor/32x32/apps/mendix-ruby-bridge.png +0 -0
- data/share/icons/hicolor/48x48/apps/mendix-ruby-bridge.png +0 -0
- data/share/icons/hicolor/512x512/apps/mendix-ruby-bridge.png +0 -0
- data/share/icons/hicolor/64x64/apps/mendix-ruby-bridge.png +0 -0
- data/web/dist/apple-touch-icon.png +0 -0
- data/web/dist/assets/index-BO6iPT1P.css +1 -0
- data/web/dist/assets/index-JGODw81W.js +27 -0
- data/web/dist/brand/mendix-ruby-bridge.png +0 -0
- data/web/dist/favicon.png +0 -0
- data/web/dist/icons.svg +24 -0
- data/web/dist/index.html +17 -0
- metadata +179 -0
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MendixBridge
|
|
4
|
+
module DSL
|
|
5
|
+
class RootBuilder
|
|
6
|
+
def self.build(&block)
|
|
7
|
+
builder = new
|
|
8
|
+
builder.instance_eval(&block)
|
|
9
|
+
builder.result
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def app(name, version: nil, &block)
|
|
13
|
+
raise ArgumentError, "only one app can be declared" if @app
|
|
14
|
+
|
|
15
|
+
@app = AppBuilder.build(name, version:, &block)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def result
|
|
19
|
+
@app || raise(ArgumentError, "an app declaration is required")
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class AppBuilder
|
|
24
|
+
def self.build(name, version: nil, &block)
|
|
25
|
+
builder = new(name, version)
|
|
26
|
+
builder.instance_eval(&block) if block
|
|
27
|
+
builder.result
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def initialize(name, version)
|
|
31
|
+
@name = name.to_s
|
|
32
|
+
@version = version&.to_s
|
|
33
|
+
@modules = []
|
|
34
|
+
@user_roles = []
|
|
35
|
+
@project_security = nil
|
|
36
|
+
@navigation_profiles = []
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def modulo(name, &block)
|
|
40
|
+
add_module(name, &block)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def app_module(name, &block)
|
|
44
|
+
add_module(name, &block)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def user_role(name, module_roles:, manage_all_roles: false)
|
|
48
|
+
raise ArgumentError, "duplicate user role: #{name}" if
|
|
49
|
+
@user_roles.any? { |role| role.name == name.to_s }
|
|
50
|
+
|
|
51
|
+
roles = Array(module_roles).map(&:to_s)
|
|
52
|
+
raise ArgumentError, "user role #{name} requires module roles" if roles.empty?
|
|
53
|
+
|
|
54
|
+
@user_roles << Model::UserRole.new(
|
|
55
|
+
name: name.to_s,
|
|
56
|
+
module_roles: roles,
|
|
57
|
+
manage_all_roles:
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def project_security(level:, demo_users: false)
|
|
62
|
+
raise ArgumentError, "project security is already declared" if @project_security
|
|
63
|
+
|
|
64
|
+
normalized_level = level.to_s.downcase
|
|
65
|
+
unless %w[off prototype production].include?(normalized_level)
|
|
66
|
+
raise ArgumentError, "security level must be off, prototype, or production"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
@project_security = Model::ProjectSecurity.new(
|
|
70
|
+
level: normalized_level,
|
|
71
|
+
demo_users: !!demo_users
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def navigation_profile(
|
|
76
|
+
name,
|
|
77
|
+
home_page:,
|
|
78
|
+
login_page: nil,
|
|
79
|
+
not_found_page: nil,
|
|
80
|
+
&block
|
|
81
|
+
)
|
|
82
|
+
raise ArgumentError, "duplicate navigation profile: #{name}" if
|
|
83
|
+
@navigation_profiles.any? { |profile| profile.name == name.to_s }
|
|
84
|
+
|
|
85
|
+
@navigation_profiles << NavigationBuilder.build(
|
|
86
|
+
name,
|
|
87
|
+
home_page:,
|
|
88
|
+
login_page:,
|
|
89
|
+
not_found_page:,
|
|
90
|
+
&block
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def result
|
|
95
|
+
Model::App.new(
|
|
96
|
+
name: @name,
|
|
97
|
+
version: @version,
|
|
98
|
+
modules: @modules,
|
|
99
|
+
user_roles: @user_roles,
|
|
100
|
+
project_security: @project_security,
|
|
101
|
+
navigation_profiles: @navigation_profiles
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private
|
|
106
|
+
|
|
107
|
+
def add_module(name, &block)
|
|
108
|
+
ensure_unique!(@modules, name, "module")
|
|
109
|
+
@modules << ModuleBuilder.build(name, &block)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def ensure_unique!(items, name, kind)
|
|
113
|
+
return unless items.any? { |item| item.name == name.to_s }
|
|
114
|
+
|
|
115
|
+
raise ArgumentError, "duplicate #{kind}: #{name}"
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
class NavigationBuilder
|
|
120
|
+
def self.build(name, home_page:, login_page: nil, not_found_page: nil, &block)
|
|
121
|
+
builder = new(name, home_page, login_page, not_found_page)
|
|
122
|
+
builder.instance_eval(&block) if block
|
|
123
|
+
builder.result
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def initialize(name, home_page, login_page, not_found_page)
|
|
127
|
+
@name = name.to_s
|
|
128
|
+
@home_page = home_page.to_s
|
|
129
|
+
@login_page = login_page&.to_s
|
|
130
|
+
@not_found_page = not_found_page&.to_s
|
|
131
|
+
@role_home_pages = []
|
|
132
|
+
@items = []
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def home_page(page, for_role:)
|
|
136
|
+
role = for_role.to_s
|
|
137
|
+
raise ArgumentError, "duplicate role home page: #{role}" if
|
|
138
|
+
@role_home_pages.any? { |entry| entry.role == role }
|
|
139
|
+
|
|
140
|
+
@role_home_pages << Model::RoleHomePage.new(role:, page: page.to_s)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def menu_item(caption, page: nil, microflow: nil, nanoflow: nil)
|
|
144
|
+
@items << build_item(caption, page:, microflow:, nanoflow:)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def menu(caption, &block)
|
|
148
|
+
builder = NavigationMenuBuilder.new
|
|
149
|
+
builder.instance_eval(&block)
|
|
150
|
+
@items << Model::NavigationMenu.new(
|
|
151
|
+
caption: caption.to_s,
|
|
152
|
+
items: builder.items
|
|
153
|
+
)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def result
|
|
157
|
+
Model::NavigationProfile.new(
|
|
158
|
+
name: @name,
|
|
159
|
+
home_page: @home_page,
|
|
160
|
+
role_home_pages: @role_home_pages,
|
|
161
|
+
login_page: @login_page,
|
|
162
|
+
not_found_page: @not_found_page,
|
|
163
|
+
items: @items
|
|
164
|
+
)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
private
|
|
168
|
+
|
|
169
|
+
def build_item(caption, page:, microflow:, nanoflow:)
|
|
170
|
+
targets = { "page" => page, "microflow" => microflow, "nanoflow" => nanoflow }
|
|
171
|
+
.reject { |_action, target| target.nil? }
|
|
172
|
+
raise ArgumentError, "menu item requires exactly one target" unless targets.length == 1
|
|
173
|
+
|
|
174
|
+
action, target = targets.first
|
|
175
|
+
Model::NavigationItem.new(
|
|
176
|
+
caption: caption.to_s,
|
|
177
|
+
action:,
|
|
178
|
+
target: target.to_s
|
|
179
|
+
)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
class NavigationMenuBuilder
|
|
184
|
+
attr_reader :items
|
|
185
|
+
|
|
186
|
+
def initialize
|
|
187
|
+
@items = []
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def menu_item(caption, page: nil, microflow: nil, nanoflow: nil)
|
|
191
|
+
targets = { "page" => page, "microflow" => microflow, "nanoflow" => nanoflow }
|
|
192
|
+
.reject { |_action, target| target.nil? }
|
|
193
|
+
raise ArgumentError, "menu item requires exactly one target" unless targets.length == 1
|
|
194
|
+
|
|
195
|
+
action, target = targets.first
|
|
196
|
+
@items << Model::NavigationItem.new(
|
|
197
|
+
caption: caption.to_s,
|
|
198
|
+
action:,
|
|
199
|
+
target: target.to_s
|
|
200
|
+
)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
class ModuleBuilder
|
|
205
|
+
def self.build(name, &block)
|
|
206
|
+
builder = new(name)
|
|
207
|
+
builder.instance_eval(&block) if block
|
|
208
|
+
builder.result
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def initialize(name)
|
|
212
|
+
@name = name.to_s
|
|
213
|
+
@entities = []
|
|
214
|
+
@enumerations = []
|
|
215
|
+
@microflows = []
|
|
216
|
+
@pages = []
|
|
217
|
+
@module_roles = []
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def entity(name, persistable: true, &block)
|
|
221
|
+
if @entities.any? { |entity| entity.name == name.to_s }
|
|
222
|
+
raise ArgumentError, "duplicate entity: #{name}"
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
@entities << EntityBuilder.build(name, persistable:, &block)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def enumeration(name, folder: nil, &block)
|
|
229
|
+
if @enumerations.any? { |enumeration| enumeration.name == name.to_s }
|
|
230
|
+
raise ArgumentError, "duplicate enumeration: #{name}"
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
@enumerations << EnumerationBuilder.build(name, folder:, &block)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def microflow(name, returns: nil, folder: nil, &block)
|
|
237
|
+
if @microflows.any? { |microflow| microflow.name == name.to_s }
|
|
238
|
+
raise ArgumentError, "duplicate microflow: #{name}"
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
@microflows << MicroflowBuilder.build(
|
|
242
|
+
name,
|
|
243
|
+
returns:,
|
|
244
|
+
folder:,
|
|
245
|
+
&block
|
|
246
|
+
)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def page(name, title:, layout:, folder: nil, &block)
|
|
250
|
+
raise ArgumentError, "duplicate page: #{name}" if
|
|
251
|
+
@pages.any? { |page| page.name == name.to_s }
|
|
252
|
+
|
|
253
|
+
@pages << PageBuilder.build(
|
|
254
|
+
name,
|
|
255
|
+
title:,
|
|
256
|
+
layout:,
|
|
257
|
+
folder:,
|
|
258
|
+
&block
|
|
259
|
+
)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def module_role(name, description: nil)
|
|
263
|
+
raise ArgumentError, "duplicate module role: #{name}" if
|
|
264
|
+
@module_roles.any? { |role| role.name == name.to_s }
|
|
265
|
+
|
|
266
|
+
@module_roles << Model::ModuleRole.new(
|
|
267
|
+
name: name.to_s,
|
|
268
|
+
description: description&.to_s
|
|
269
|
+
)
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def result
|
|
273
|
+
Model::AppModule.new(
|
|
274
|
+
name: @name,
|
|
275
|
+
entities: @entities,
|
|
276
|
+
enumerations: @enumerations,
|
|
277
|
+
microflows: @microflows,
|
|
278
|
+
pages: @pages,
|
|
279
|
+
module_roles: @module_roles
|
|
280
|
+
)
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
class PageBuilder
|
|
285
|
+
def self.build(name, title:, layout:, folder: nil, &block)
|
|
286
|
+
builder = new(name, title, layout, folder)
|
|
287
|
+
builder.instance_eval(&block) if block
|
|
288
|
+
builder.result
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def initialize(name, title, layout, folder)
|
|
292
|
+
@name = name.to_s
|
|
293
|
+
@title = title.to_s
|
|
294
|
+
@layout = layout.to_s
|
|
295
|
+
@folder = folder&.to_s
|
|
296
|
+
@parameters = []
|
|
297
|
+
@content = nil
|
|
298
|
+
@view_roles = []
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def parameter(name, type)
|
|
302
|
+
if @parameters.any? { |parameter| parameter.name == name.to_s }
|
|
303
|
+
raise ArgumentError, "duplicate page parameter: #{name}"
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
@parameters << Model::FlowParameter.new(name: name.to_s, type: type.to_s)
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def content(source)
|
|
310
|
+
raise ArgumentError, "page content is already declared" if @content
|
|
311
|
+
|
|
312
|
+
@content = source.to_s.strip
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def view_role(role)
|
|
316
|
+
role = role.to_s
|
|
317
|
+
raise ArgumentError, "duplicate view role: #{role}" if @view_roles.include?(role)
|
|
318
|
+
|
|
319
|
+
@view_roles << role
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def result
|
|
323
|
+
raise ArgumentError, "page #{@name} requires content" if @content.nil? || @content.empty?
|
|
324
|
+
|
|
325
|
+
Model::Page.new(
|
|
326
|
+
name: @name,
|
|
327
|
+
title: @title,
|
|
328
|
+
layout: @layout,
|
|
329
|
+
folder: @folder,
|
|
330
|
+
parameters: @parameters,
|
|
331
|
+
content: @content,
|
|
332
|
+
view_roles: @view_roles
|
|
333
|
+
)
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
class MicroflowBuilder
|
|
338
|
+
def self.build(name, returns: nil, folder: nil, &block)
|
|
339
|
+
builder = new(name, returns, folder)
|
|
340
|
+
builder.instance_eval(&block) if block
|
|
341
|
+
builder.result
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def initialize(name, returns, folder)
|
|
345
|
+
@name = name.to_s
|
|
346
|
+
@return_type = returns&.to_s
|
|
347
|
+
@folder = folder&.to_s
|
|
348
|
+
@parameters = []
|
|
349
|
+
@body = nil
|
|
350
|
+
@execute_roles = []
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def parameter(name, type)
|
|
354
|
+
if @parameters.any? { |parameter| parameter.name == name.to_s }
|
|
355
|
+
raise ArgumentError, "duplicate microflow parameter: #{name}"
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
@parameters << Model::FlowParameter.new(name: name.to_s, type: type.to_s)
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def body(source)
|
|
362
|
+
raise ArgumentError, "microflow body is already declared" if @body
|
|
363
|
+
|
|
364
|
+
@body = source.to_s.strip
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
def execute_role(role)
|
|
368
|
+
role = role.to_s
|
|
369
|
+
raise ArgumentError, "duplicate execute role: #{role}" if @execute_roles.include?(role)
|
|
370
|
+
|
|
371
|
+
@execute_roles << role
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
def result
|
|
375
|
+
raise ArgumentError, "microflow #{@name} requires a body" if @body.nil? || @body.empty?
|
|
376
|
+
|
|
377
|
+
Model::Microflow.new(
|
|
378
|
+
name: @name,
|
|
379
|
+
parameters: @parameters,
|
|
380
|
+
return_type: @return_type,
|
|
381
|
+
folder: @folder,
|
|
382
|
+
body: @body,
|
|
383
|
+
execute_roles: @execute_roles
|
|
384
|
+
)
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
class EnumerationBuilder
|
|
389
|
+
def self.build(name, folder: nil, &block)
|
|
390
|
+
builder = new(name, folder)
|
|
391
|
+
builder.instance_eval(&block) if block
|
|
392
|
+
builder.result
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def initialize(name, folder)
|
|
396
|
+
@name = name.to_s
|
|
397
|
+
@folder = folder&.to_s
|
|
398
|
+
@values = []
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
def value(name, caption: nil)
|
|
402
|
+
if @values.any? { |value| value.name == name.to_s }
|
|
403
|
+
raise ArgumentError, "duplicate enumeration value: #{name}"
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
@values << Model::EnumerationValue.new(
|
|
407
|
+
name: name.to_s,
|
|
408
|
+
caption: (caption || name).to_s
|
|
409
|
+
)
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def result
|
|
413
|
+
raise ArgumentError, "enumeration #{@name} requires at least one value" if @values.empty?
|
|
414
|
+
|
|
415
|
+
Model::Enumeration.new(name: @name, folder: @folder, values: @values)
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
class EntityBuilder
|
|
420
|
+
CARDINALITIES = %i[one many].freeze
|
|
421
|
+
ATTRIBUTE_TYPES = %i[
|
|
422
|
+
autonumber binary boolean datetime decimal enumeration
|
|
423
|
+
hash_string integer long string
|
|
424
|
+
].freeze
|
|
425
|
+
|
|
426
|
+
def self.build(name, persistable: true, &block)
|
|
427
|
+
builder = new(name, persistable)
|
|
428
|
+
builder.instance_eval(&block) if block
|
|
429
|
+
builder.result
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
def initialize(name, persistable)
|
|
433
|
+
@name = name.to_s
|
|
434
|
+
@persistable = persistable
|
|
435
|
+
@attributes = []
|
|
436
|
+
@associations = []
|
|
437
|
+
@access_rules = []
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
def attribute(
|
|
441
|
+
name,
|
|
442
|
+
type,
|
|
443
|
+
required: false,
|
|
444
|
+
default: nil,
|
|
445
|
+
length: 200,
|
|
446
|
+
enumeration: nil
|
|
447
|
+
)
|
|
448
|
+
type = type.to_sym
|
|
449
|
+
unless ATTRIBUTE_TYPES.include?(type)
|
|
450
|
+
raise ArgumentError,
|
|
451
|
+
"unsupported attribute type #{type.inspect}; " \
|
|
452
|
+
"expected one of: #{ATTRIBUTE_TYPES.join(', ')}"
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
ensure_unique!(@attributes, name, "attribute")
|
|
456
|
+
if type == :enumeration && enumeration.nil?
|
|
457
|
+
raise ArgumentError, "enumeration attributes require enumeration: \"Module.Name\""
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
options = case type
|
|
461
|
+
when :string
|
|
462
|
+
{ length: }
|
|
463
|
+
when :enumeration
|
|
464
|
+
{ enumeration: enumeration.to_s }
|
|
465
|
+
else
|
|
466
|
+
{}
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
@attributes << Model::Attribute.new(
|
|
470
|
+
name: name.to_s,
|
|
471
|
+
type: type.to_s,
|
|
472
|
+
required:,
|
|
473
|
+
default:,
|
|
474
|
+
options:
|
|
475
|
+
)
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
def association(name, target:, cardinality: :one, owner: :default)
|
|
479
|
+
cardinality = cardinality.to_sym
|
|
480
|
+
unless CARDINALITIES.include?(cardinality)
|
|
481
|
+
raise ArgumentError, "cardinality must be :one or :many"
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
ensure_unique!(@associations, name, "association")
|
|
485
|
+
@associations << Model::Association.new(
|
|
486
|
+
name: name.to_s,
|
|
487
|
+
target: target.to_s,
|
|
488
|
+
cardinality: cardinality.to_s,
|
|
489
|
+
owner: owner.to_s
|
|
490
|
+
)
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
def access(
|
|
494
|
+
role,
|
|
495
|
+
create: false,
|
|
496
|
+
delete: false,
|
|
497
|
+
read: [],
|
|
498
|
+
write: [],
|
|
499
|
+
where: nil
|
|
500
|
+
)
|
|
501
|
+
role = role.to_s
|
|
502
|
+
raise ArgumentError, "duplicate entity access role: #{role}" if
|
|
503
|
+
@access_rules.any? { |rule| rule.role == role }
|
|
504
|
+
|
|
505
|
+
@access_rules << Model::AccessRule.new(
|
|
506
|
+
role:,
|
|
507
|
+
create: !!create,
|
|
508
|
+
delete: !!delete,
|
|
509
|
+
read: access_attributes(read),
|
|
510
|
+
write: access_attributes(write),
|
|
511
|
+
xpath: where&.to_s
|
|
512
|
+
)
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
def result
|
|
516
|
+
Model::Entity.new(
|
|
517
|
+
name: @name,
|
|
518
|
+
persistable: @persistable,
|
|
519
|
+
attributes: @attributes,
|
|
520
|
+
associations: @associations,
|
|
521
|
+
access_rules: @access_rules
|
|
522
|
+
)
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
private
|
|
526
|
+
|
|
527
|
+
def ensure_unique!(items, name, kind)
|
|
528
|
+
return unless items.any? { |item| item.name == name.to_s }
|
|
529
|
+
|
|
530
|
+
raise ArgumentError, "duplicate #{kind}: #{name}"
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
def access_attributes(value)
|
|
534
|
+
return "*" if value == :all || value == "*"
|
|
535
|
+
|
|
536
|
+
Array(value).map(&:to_s)
|
|
537
|
+
end
|
|
538
|
+
end
|
|
539
|
+
end
|
|
540
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MendixBridge
|
|
4
|
+
class EnumerationParser
|
|
5
|
+
class << self
|
|
6
|
+
def parse(description)
|
|
7
|
+
mdl = description.fetch("mdl")
|
|
8
|
+
body = mdl[
|
|
9
|
+
/\benumeration\s+[\w.]+\s*\(\s*(?<body>.*?)\s*\)\s*(?:FOLDER\b|;)/im,
|
|
10
|
+
:body
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
return { "mdl" => mdl, "parse_status" => "unsupported" } unless body
|
|
14
|
+
|
|
15
|
+
{
|
|
16
|
+
"values" => parse_values(body),
|
|
17
|
+
"folder" => mdl[/\bFOLDER\s+'((?:''|[^'])*)'/i, 1]&.gsub("''", "'"),
|
|
18
|
+
"mdl" => mdl,
|
|
19
|
+
"parse_status" => "parsed"
|
|
20
|
+
}.compact
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def parse_values(body)
|
|
26
|
+
body.scan(/^\s*(\w+)\s+'((?:''|[^'])*)'\s*,?\s*$/).map do |name, caption|
|
|
27
|
+
{ "name" => name, "caption" => caption.gsub("''", "'") }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|