mxrb 0.1.1 → 0.1.3

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.de-DE.md +4 -0
  3. data/README.md +4 -0
  4. data/README.pt-BR.md +4 -0
  5. data/bin/mxrb +38 -2
  6. data/docs/de-DE/oql-sql.md +18 -0
  7. data/docs/de-DE/scaffolds.md +23 -0
  8. data/docs/en-US/oql-sql.md +18 -0
  9. data/docs/en-US/scaffolds.md +22 -0
  10. data/docs/pt-BR/entity-dsl.md +8 -0
  11. data/docs/pt-BR/oql-sql.md +18 -0
  12. data/docs/pt-BR/scaffolds.md +22 -0
  13. data/docs/pt-BR/writing.md +28 -0
  14. data/lib/mxrb/compare.rb +7 -0
  15. data/lib/mxrb/compiler/packager.rb +27 -13
  16. data/lib/mxrb/compiler/portable_packager.rb +16 -11
  17. data/lib/mxrb/dsl/builder.rb +100 -2
  18. data/lib/mxrb/exporter.rb +184 -32
  19. data/lib/mxrb/initializer.rb +1 -0
  20. data/lib/mxrb/integrity/validator.rb +18 -7
  21. data/lib/mxrb/model/entity.rb +17 -1
  22. data/lib/mxrb/model/module.rb +71 -0
  23. data/lib/mxrb/official_marketplace/module_package_importer.rb +23 -12
  24. data/lib/mxrb/official_marketplace/widget_package_installer.rb +15 -11
  25. data/lib/mxrb/official_marketplace.rb +9 -3
  26. data/lib/mxrb/oql.rb +9 -3
  27. data/lib/mxrb/progress.rb +242 -0
  28. data/lib/mxrb/runtime/clipboard.rb +62 -0
  29. data/lib/mxrb/runtime/database_workspace.rb +28 -0
  30. data/lib/mxrb/scaffold/cli.rb +20 -1
  31. data/lib/mxrb/scaffold/generator.rb +125 -3
  32. data/lib/mxrb/scaffold/help.rb +16 -2
  33. data/lib/mxrb/scaffold/recipes.rb +27 -0
  34. data/lib/mxrb/scaffold/templates.rb +11 -0
  35. data/lib/mxrb/scaffold/transaction.rb +2 -2
  36. data/lib/mxrb/team_server.rb +10 -7
  37. data/lib/mxrb/version.rb +1 -1
  38. data/lib/mxrb/writer.rb +94 -19
  39. data/lib/mxrb.rb +13 -7
  40. metadata +3 -1
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'securerandom'
4
+
3
5
  module Mxrb
4
6
  module Scaffold
5
7
  # Creates conventional Ruby-first artifacts and connects missing aggregators.
@@ -24,8 +26,7 @@ module Mxrb
24
26
  @root = File.expand_path(target)
25
27
  @transaction = Transaction.new
26
28
  @dry_run = dry_run
27
- @page_chain = page_options.delete(:page_chain)
28
- @page_template = page_options.delete(:page_template)
29
+ assign_options(page_options)
29
30
  raise ArgumentError, "unknown generator options: #{page_options.keys.join(', ')}" unless
30
31
  page_options.empty?
31
32
  end
@@ -42,6 +43,13 @@ module Mxrb
42
43
 
43
44
  private
44
45
 
46
+ def assign_options(options)
47
+ @page_chain = options.delete(:page_chain)
48
+ @page_template = options.delete(:page_template)
49
+ @demo_entity = options.delete(:demo_entity)
50
+ @demo_roles = options.delete(:demo_roles)
51
+ end
52
+
45
53
  def page_chain
46
54
  return unless @page_chain
47
55
  return @page_chain if PAGE_CHAINS.include?(@page_chain)
@@ -55,8 +63,13 @@ module Mxrb
55
63
  end
56
64
 
57
65
  def stage_registry
66
+ files = @transaction.created.dup
67
+ if @kind == :demo_user
68
+ shared = [project_path('.env'), project_path('.env.example')]
69
+ files -= shared
70
+ end
58
71
  Registry.stage(
59
- @transaction, root: @root, key: "#{@kind}:#{@name}", files: @transaction.created.dup
72
+ @transaction, root: @root, key: "#{@kind}:#{@name}", files:
60
73
  )
61
74
  end
62
75
 
@@ -125,6 +138,115 @@ module Mxrb
125
138
  @transaction.write(path, lines.join)
126
139
  end
127
140
 
141
+ def connect_project_demo_users
142
+ path = project_path('project.rb')
143
+ source = connect_project_dotenv(@transaction.content(path))
144
+ opening = source.lines.index { _1.match?(/^\s*security do\s*$/) }
145
+ raise ArgumentError, security_initialization_error unless opening
146
+
147
+ insert_demo_user_loader(path, source, opening)
148
+ end
149
+
150
+ def insert_demo_user_loader(path, source, opening)
151
+ line = 'evaluate_dir File.join(__dir__, "app", "security", "demo_users")'
152
+ return if source.include?(line)
153
+
154
+ lines = source.lines
155
+ indentation = "#{lines.fetch(opening)[/^\s*/]} "
156
+ lines.insert(opening + 1, "#{indentation}#{line}\n")
157
+ @transaction.write(path, lines.join)
158
+ end
159
+
160
+ def connect_project_dotenv(source)
161
+ dotenv_line = 'Dotenv.load(File.join(__dir__, ".env"))'
162
+ return source if source.include?(dotenv_line)
163
+
164
+ lines = source.lines
165
+ require_line = lines.index { _1.match?(%r{^require ["']dotenv/load["']\s*$}) }
166
+ raise ArgumentError, 'project.rb must require dotenv/load before scaffolding demo users' unless require_line
167
+
168
+ lines.insert(require_line + 1, "#{dotenv_line}\n").join
169
+ end
170
+
171
+ def security_initialization_error
172
+ 'project security is not initialized; run `mxrb security init Module` first'
173
+ end
174
+
175
+ def create_demo_user_file(name, entity, roles, password_env)
176
+ path = project_path('app', 'security', 'demo_users', "#{Templates.snake_case(name)}.rb")
177
+ @transaction.create(
178
+ path,
179
+ Templates.render(
180
+ :demo_user, name:, entity:, roles:, password_env:
181
+ )
182
+ )
183
+ end
184
+
185
+ def ensure_demo_user_secret(password_env)
186
+ path = project_path('.env')
187
+ source = @transaction.content(path)
188
+ if source
189
+ @transaction.write(path, append_env(source, password_env, generated_demo_password)) unless
190
+ env_key?(source, password_env)
191
+ else
192
+ create_demo_user_secret(path, password_env)
193
+ end
194
+ ensure_demo_user_env_example(password_env)
195
+ end
196
+
197
+ def create_demo_user_secret(path, password_env)
198
+ source = "# Local secrets; never commit this file.\n"
199
+ content = append_env(source, password_env, generated_demo_password)
200
+ @transaction.create(path, content, mode: 0o600)
201
+ end
202
+
203
+ def ensure_demo_user_env_example(password_env)
204
+ path = project_path('.env.example')
205
+ source = @transaction.content(path) || "# Copy to .env and keep real values local.\n"
206
+ @transaction.write(path, append_env(source, password_env, '')) unless env_key?(source, password_env)
207
+ end
208
+
209
+ def append_env(source, key, value)
210
+ "#{source.rstrip}\n#{key}=#{value}\n"
211
+ end
212
+
213
+ def env_key?(source, key)
214
+ source.lines.any? { _1.match?(/\A#{Regexp.escape(key)}=/) }
215
+ end
216
+
217
+ def generated_demo_password
218
+ "Mxrb#{SecureRandom.alphanumeric(16)}7"
219
+ end
220
+
221
+ def validate_demo_user_references!(name, entity, roles)
222
+ sources = project_ruby_sources
223
+ errors = []
224
+ missing_roles = roles - known_user_roles(sources)
225
+ errors << "missing user role(s): #{missing_roles.join(', ')}" unless missing_roles.empty?
226
+ errors << "missing user entity: #{entity}" unless
227
+ entity == 'System.User' || known_entities(sources).include?(entity)
228
+ return if errors.empty?
229
+
230
+ raise ArgumentError, "demo user #{name}: #{errors.join('; ')}"
231
+ end
232
+
233
+ def project_ruby_sources
234
+ Dir.glob(project_path('**', '*.rb')).to_h { [_1, File.read(_1)] }
235
+ end
236
+
237
+ def known_user_roles(sources)
238
+ pattern = /\buser_role\s+(?::([A-Za-z][A-Za-z0-9_]*)|["']([^"']+)["'])/
239
+ sources.values.flat_map { _1.scan(pattern).map { |match| match.compact.first } }.uniq
240
+ end
241
+
242
+ def known_entities(sources)
243
+ pattern = /\bentity\s+(?::([A-Za-z][A-Za-z0-9_]*)|["']([^"']+)["'])/
244
+ sources.flat_map do |path, source|
245
+ mod = path.match(%r{/modules/([^/]+)/})&.[](1)
246
+ mod ? source.scan(pattern).map { "#{mod}.#{_1.compact.first}" } : []
247
+ end.uniq
248
+ end
249
+
128
250
  def insert_before_closing(source, line, path)
129
251
  lines = source.lines
130
252
  closing = lines.rindex { _1.strip == 'end' }
@@ -23,6 +23,11 @@ module Mxrb
23
23
  'nanoflow' => ['new Module.Flow', 'Create a client nanoflow', 'presentation/client_actions'],
24
24
  'repository' => ['new Module.Repository', 'Create repository port and adapter', 'application/infrastructure'],
25
25
  'security' => ['init Module', 'Create module roles template', 'security'],
26
+ 'demo-user' => [
27
+ 'new NAME [--entity Module.Entity] [--role ROLE]',
28
+ 'Create a local Mendix demo user backed by an ignored .env secret',
29
+ 'app/security/demo_users'
30
+ ],
26
31
  'scheduled-event' => ['new Module.Event', 'Create scheduled event and handler', 'application/jobs'],
27
32
  'constant' => ['new Module.Constant', 'Create a string constant', 'domain/constants'],
28
33
  'integration' => ['new Module.Adapter', 'Create integration adapter flow', 'infrastructure/integrations'],
@@ -54,6 +59,14 @@ module Mxrb
54
59
  mxrb page templates [--json]
55
60
  --template starter|blank|dashboard|form-vertical
56
61
  HELP
62
+ DEMO_USER_OPTIONS = <<~HELP
63
+ Demo user options:
64
+ --entity Module.Entity User entity (default: System.User)
65
+ --role ROLE App user role (repeatable; default: User)
66
+
67
+ `new` is optional, so `mxrb demo-user manager ...` is also accepted.
68
+ The generated password is stored in the ignored .env file.
69
+ HELP
57
70
 
58
71
  def text(command)
59
72
  usage, description, destination = COMMANDS.fetch(command)
@@ -71,9 +84,10 @@ module Mxrb
71
84
  end
72
85
 
73
86
  def page_options(command)
74
- return '' unless command == 'page'
87
+ return PAGE_OPTIONS if command == 'page'
88
+ return DEMO_USER_OPTIONS if command == 'demo-user'
75
89
 
76
- PAGE_OPTIONS
90
+ ''
77
91
  end
78
92
  end
79
93
  end
@@ -129,6 +129,33 @@ module Mxrb
129
129
  connect_project_security(module_name)
130
130
  end
131
131
 
132
+ def scaffold_demo_user
133
+ ensure_project
134
+ name = identifier(@name, label: 'demo user')
135
+ entity = demo_user_entity
136
+ roles = demo_user_roles
137
+ validate_demo_user_references!(name, entity, roles)
138
+ password_env = "MXRB_DEMO_USER_#{name.gsub(/[^A-Za-z0-9]/, '_').upcase}_PASSWORD"
139
+
140
+ connect_project_demo_users
141
+ create_demo_user_file(name, entity, roles, password_env)
142
+ ensure_demo_user_secret(password_env) unless @dry_run
143
+ end
144
+
145
+ def demo_user_entity
146
+ entity = (@demo_entity || 'System.User').to_s
147
+ pattern = /\A[A-Za-z][A-Za-z0-9_]*\.[A-Za-z][A-Za-z0-9_]*\z/
148
+ raise ArgumentError, 'demo user entity must be qualified as Module.Entity' unless entity.match?(pattern)
149
+
150
+ entity
151
+ end
152
+
153
+ def demo_user_roles
154
+ roles = Array(@demo_roles)
155
+ roles = ['User'] if roles.empty?
156
+ roles.map { identifier(_1.to_s, label: 'user role') }.uniq
157
+ end
158
+
132
159
  def scaffold_functional_test
133
160
  module_name, artifact_name = qualified_name
134
161
  ensure_project
@@ -293,6 +293,17 @@ module Mxrb
293
293
  RUBY
294
294
  end
295
295
 
296
+ def demo_user(_module_name, name, entity:, roles:, password_env:)
297
+ <<~RUBY
298
+ # frozen_string_literal: true
299
+
300
+ demo_user #{name.inspect},
301
+ entity: #{entity.inspect},
302
+ roles: #{roles.inspect},
303
+ password: ENV.fetch(#{password_env.inspect})
304
+ RUBY
305
+ end
306
+
296
307
  def scheduled_event(module_name, name)
297
308
  <<~RUBY
298
309
  # frozen_string_literal: true
@@ -21,11 +21,11 @@ module Mxrb
21
21
  nil
22
22
  end
23
23
 
24
- def create(path, content)
24
+ def create(path, content, mode: 0o644)
25
25
  abort "#{path}: file already exists" if File.exist?(path) || @changes.key?(path)
26
26
 
27
27
  @created << path
28
- @changes[path] = Change.new(path, content, nil, 0o644)
28
+ @changes[path] = Change.new(path, content, nil, mode)
29
29
  end
30
30
 
31
31
  def write(path, new_content)
@@ -161,7 +161,7 @@ module Mxrb
161
161
 
162
162
  # Git transport for Team Server. PATs are passed to a short-lived
163
163
  # GIT_ASKPASS process and are never embedded in a URL or git config.
164
- class Repository
164
+ class Repository # rubocop:disable Metrics/ClassLength
165
165
  def initialize(credentials: Credentials.new, runner: CommandRunner.new,
166
166
  authenticator: GitAuthenticator.new(credentials))
167
167
  @runner = runner
@@ -249,11 +249,14 @@ module Mxrb
249
249
  end
250
250
 
251
251
  def capture!(command, chdir: nil)
252
- @authenticator.call do |environment|
253
- output, status = @runner.capture(environment, command, chdir:)
254
- raise TeamServerError, "Team Server Git operation failed: #{output.strip}" unless status.success?
255
-
256
- output
252
+ Progress.with("Team Server #{command.first(2).join(' ')}") do |progress|
253
+ progress.update(detail: chdir || 'remote repository')
254
+ @authenticator.call do |environment|
255
+ output, status = @runner.capture(environment, command, chdir:)
256
+ raise TeamServerError, "Team Server Git operation failed: #{output.strip}" unless status.success?
257
+
258
+ output
259
+ end
257
260
  end
258
261
  end
259
262
 
@@ -285,7 +288,7 @@ module Mxrb
285
288
  mpr
286
289
  end
287
290
  end
288
- end
291
+ end # rubocop:enable Metrics/ClassLength
289
292
 
290
293
  # Read-only client for Mendix's official App Repository API.
291
294
  class Api
data/lib/mxrb/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mxrb
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.3"
5
5
  end
data/lib/mxrb/writer.rb CHANGED
@@ -31,19 +31,32 @@ module Mxrb
31
31
  def initialize(path, definition)
32
32
  @path = File.expand_path(path)
33
33
  @definition = definition
34
+ @progress = Progress::NullTask.instance
34
35
  end
35
36
 
36
37
  def write!
37
- create_project! unless File.exist?(@path)
38
- mpr = IO::MprFile.open(@path)
39
- mpr.transaction do
40
- apply(mpr)
41
- mpr.write_architecture_definition(@definition)
42
- end
43
- materialize_project_assets
44
- materialize_design_system
38
+ mpr = nil
39
+ native_units = prepared_native_units
40
+ asset_manifest = project_asset_manifest
41
+ total = 4 + native_units.count { _1["containment"] != "Modules" } +
42
+ @definition.fetch(:modules).size + Array(asset_manifest&.fetch("files", [])).size
43
+ Progress.with("Generating #{File.basename(@path)}", total:) do |progress|
44
+ @progress = progress
45
+ create_project! unless File.exist?(@path)
46
+ progress.advance(detail: "project container")
47
+ mpr = IO::MprFile.open(@path)
48
+ mpr.transaction do
49
+ apply(mpr, native_units)
50
+ mpr.write_architecture_definition(@definition)
51
+ end
52
+ progress.advance(detail: "model transaction")
53
+ materialize_project_assets(asset_manifest)
54
+ materialize_design_system
55
+ progress.advance(detail: "design system")
56
+ end
45
57
  self
46
58
  ensure
59
+ @progress = Progress::NullTask.instance
47
60
  mpr&.close
48
61
  end
49
62
 
@@ -56,11 +69,17 @@ module Mxrb
56
69
  Model::DesignMaterializer.new(File.dirname(@path), design_system).materialize!
57
70
  end
58
71
 
59
- def materialize_project_assets
72
+ def project_asset_manifest
60
73
  assets = @definition[:project_assets]
61
74
  return unless assets
62
75
 
63
- manifest = JSON.parse(File.read(assets.fetch(:manifest)))
76
+ JSON.parse(File.read(assets.fetch(:manifest)))
77
+ end
78
+
79
+ def materialize_project_assets(manifest)
80
+ assets = @definition[:project_assets]
81
+ return unless assets && manifest
82
+
64
83
  source_root = File.realpath(assets.fetch(:root))
65
84
  target_root = File.dirname(@path)
66
85
  manifest.fetch("files").each do |entry|
@@ -79,6 +98,7 @@ module Mxrb
79
98
  FileUtils.mv(temporary, target)
80
99
  ensure
81
100
  FileUtils.rm_f(temporary) if temporary
101
+ @progress.advance(detail: "asset #{relative}") if defined?(relative) && relative
82
102
  end
83
103
  end
84
104
 
@@ -161,7 +181,7 @@ module Mxrb
161
181
  JSON.parse(File.read(path))["format_version"]
162
182
  end
163
183
 
164
- def apply(mpr)
184
+ def apply(mpr, native_units)
165
185
  root = mpr.root_unit
166
186
  root_id = root.fetch("UnitID")
167
187
  root_doc = mpr.parse_contents(root)
@@ -171,10 +191,6 @@ module Mxrb
171
191
  "IsSystemProject" => false
172
192
  }
173
193
  mpr.update_unit(root_id, root_doc)
174
- native_units = load_native_units(@definition[:native_units_path])
175
- native_units = apply_native_unit_overrides(
176
- native_units, @definition.fetch(:native_unit_overrides, [])
177
- )
178
194
  apply_native_project_units(mpr, root_id, native_units)
179
195
  apply_default_project_units(mpr, root_id)
180
196
  ensure_project_documents(mpr, root_id)
@@ -194,9 +210,18 @@ module Mxrb
194
210
  write_module_security(mpr, module_id, mod) if mod.key?(:module_roles)
195
211
  write_domain_model(mpr, module_id, mod)
196
212
  write_documents(mpr, module_id, mod)
213
+ @progress.advance(detail: "module #{mod.fetch(:name)}")
197
214
  end
198
215
  write_project_security(mpr, root_id, @definition[:security]) if @definition[:security]
199
216
  write_project_navigation(mpr, root_id, @definition[:navigation]) if @definition[:navigation]
217
+ @progress.advance(detail: "project security and navigation")
218
+ end
219
+
220
+ def prepared_native_units
221
+ apply_native_unit_overrides(
222
+ load_native_units(@definition[:native_units_path]),
223
+ @definition.fetch(:native_unit_overrides, [])
224
+ )
200
225
  end
201
226
 
202
227
  def load_native_units(path)
@@ -323,8 +348,20 @@ module Mxrb
323
348
  mod.fetch(:native_documents, []).each do |document|
324
349
  doc = document.fetch(:doc)
325
350
  doc = legacy_layout_doc(doc) if doc['$Type'] == 'Forms$Layout' && legacy_layout?
351
+ existing = mpr.unit(document[:unit_id]) if document[:unit_id]
352
+ if existing
353
+ current = mpr.parse_contents(existing)
354
+ preserved = current.merge(doc).merge(
355
+ '$ID' => current['$ID'] || existing.fetch('UnitID'), '$Type' => doc.fetch('$Type')
356
+ )
357
+ mpr.update_unit(existing.fetch('UnitID'), preserved)
358
+ next
359
+ end
360
+
361
+ requested_container = document[:container_id]
362
+ target_container = requested_container && mpr.unit(requested_container) ? requested_container : module_id
326
363
  upsert_native_unit(
327
- mpr, module_id,
364
+ mpr, target_container,
328
365
  'containment' => document.fetch(:containment), 'doc' => doc
329
366
  )
330
367
  end
@@ -365,7 +402,10 @@ module Mxrb
365
402
 
366
403
  def apply_native_unit_tree(mpr, target_root_id, units)
367
404
  if units.any? { _1["unit_id"].to_s.empty? || _1["container_id"].to_s.empty? }
368
- units.each { upsert_native_unit(mpr, target_root_id, _1) }
405
+ units.each do |unit|
406
+ upsert_native_unit(mpr, target_root_id, unit)
407
+ @progress.advance(detail: "native unit #{unit['name']}")
408
+ end
369
409
  return
370
410
  end
371
411
 
@@ -382,6 +422,7 @@ module Mxrb
382
422
  target_container = mapped_containers.fetch(unit.fetch("container_id"))
383
423
  actual_id = upsert_native_unit(mpr, target_container, unit)
384
424
  mapped_containers[unit.fetch("unit_id")] = actual_id
425
+ @progress.advance(detail: "native unit #{unit['name']}")
385
426
  end
386
427
  pending = blocked
387
428
  end
@@ -441,6 +482,7 @@ module Mxrb
441
482
  end
442
483
  {
443
484
  demo_users_enabled: "EnableDemoUsers",
485
+ demo_users: "DemoUsers",
444
486
  guest_access_enabled: "EnableGuestAccess",
445
487
  guest_user_role: "GuestUserRole",
446
488
  sign_in_microflow: "SignInMicroflow",
@@ -1359,7 +1401,7 @@ module Mxrb
1359
1401
  previous&.dig(rules_key) || IO::BsonCodec.build_array([])
1360
1402
  end
1361
1403
  doc = (previous || {}).merge(
1362
- "$ID" => id, "$Type" => "DomainModels$EntityImpl",
1404
+ "$ID" => id, "$Type" => previous&.fetch("$Type", nil) || "DomainModels$EntityImpl",
1363
1405
  "Name" => entity.fetch(:name), "Documentation" => entity.fetch(:documentation, ""),
1364
1406
  "GUID" => previous&.dig("GUID") || SecureRandom.uuid,
1365
1407
  "Location" => previous&.dig("Location") || "#{(index % 4) * 220};#{(index / 4) * 160}",
@@ -1367,6 +1409,7 @@ module Mxrb
1367
1409
  "IsRemote" => previous&.fetch("IsRemote", false) || false,
1368
1410
  "RemoteSource" => previous&.fetch("RemoteSource", "") || ""
1369
1411
  )
1412
+ apply_oql_view!(doc, entity.fetch(:oql_view, nil), previous)
1370
1413
  doc[attrs_key] = IO::BsonCodec.build_array(attrs)
1371
1414
  doc[rules_key] = access_rules
1372
1415
  doc[validation_key] = validation_rules_doc(
@@ -1396,6 +1439,25 @@ module Mxrb
1396
1439
  doc
1397
1440
  end
1398
1441
 
1442
+ def apply_oql_view!(doc, view, previous)
1443
+ return unless view
1444
+
1445
+ if view[:source]
1446
+ source_key = native_existing_key(previous, 'source', 'Source') || 'Source'
1447
+ current_source = previous&.dig(source_key)
1448
+ source = (current_source.is_a?(Hash) ? current_source : {}).merge(
1449
+ '$ID' => current_source&.fetch('$ID', nil) || SecureRandom.uuid,
1450
+ '$Type' => 'DomainModels$OqlViewEntitySource',
1451
+ 'SourceDocument' => view.fetch(:source)
1452
+ )
1453
+ doc[source_key] = source
1454
+ end
1455
+ return unless view[:query]
1456
+
1457
+ query_key = native_existing_key(previous, 'oqlQuery', 'OqlQuery', 'OQLQuery') || 'OqlQuery'
1458
+ doc[query_key] = view.fetch(:query)
1459
+ end
1460
+
1399
1461
  def attribute_doc(attr, previous)
1400
1462
  storage_type = Model::Attribute::TYPE_MAP.fetch(attr.fetch(:type))
1401
1463
  type_key = native_existing_key(previous, "type", "Type", "newType", "NewType") || "NewType"
@@ -1923,7 +1985,9 @@ module Mxrb
1923
1985
  "StrictMode" => false,
1924
1986
  "StrictPageUrlCheck" => true,
1925
1987
  "UserRoles" => IO::BsonCodec.build_array(roles, marker: 2),
1926
- "DemoUsers" => IO::BsonCodec.build_array([], marker: 2),
1988
+ "DemoUsers" => IO::BsonCodec.build_array(
1989
+ Array(security[:demo_users]).map { demo_user_doc(_1) }, marker: 2
1990
+ ),
1927
1991
  "FileDocumentAccess" => access_container("Security$FileDocumentAccessRuleContainer"),
1928
1992
  "ImageAccess" => access_container("Security$ImageAccessRuleContainer"),
1929
1993
  "PasswordPolicySettings" => password_policy
@@ -1945,6 +2009,17 @@ module Mxrb
1945
2009
  }
1946
2010
  end
1947
2011
 
2012
+ def demo_user_doc(user)
2013
+ {
2014
+ "$ID" => SecureRandom.uuid,
2015
+ "$Type" => "Security$DemoUserImpl",
2016
+ "UserName" => user.fetch(:name),
2017
+ "Password" => user.fetch(:password),
2018
+ "Entity" => user.fetch(:entity),
2019
+ "UserRoles" => IO::BsonCodec.build_array(user.fetch(:roles), marker: 1)
2020
+ }
2021
+ end
2022
+
1948
2023
  def module_security_doc(roles, legacy: false)
1949
2024
  {
1950
2025
  "$ID" => SecureRandom.uuid,
data/lib/mxrb.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "mxrb/version"
4
4
  require_relative "mxrb/errors"
5
+ require_relative "mxrb/progress"
5
6
  require_relative "mxrb/doctor"
6
7
  require_relative "mxrb/benchmark"
7
8
  require_relative "mxrb/project_lifecycle"
@@ -126,6 +127,7 @@ require_relative "mxrb/runtime/docker_workspace"
126
127
  require_relative "mxrb/runtime/executor"
127
128
  require_relative "mxrb/runtime/native"
128
129
  require_relative "mxrb/runtime/docker_executor"
130
+ require_relative "mxrb/runtime/clipboard"
129
131
  require_relative "mxrb/runtime/database_workspace"
130
132
  require_relative "mxrb/runtime/sql_server_database"
131
133
  require_relative "mxrb/oql/server"
@@ -138,14 +140,18 @@ module Mxrb
138
140
  #
139
141
  # Mxrb.open("MyApp.mpr") { |p| puts p.modules.map(&:name) }
140
142
  #
141
- def self.open(path, readonly: true, &block)
142
- project = Model::Project.open(path, readonly: readonly)
143
- return project unless block
143
+ def self.open(path, readonly: true, &block) # rubocop:disable Metrics/MethodLength
144
+ return Model::Project.open(path, readonly: readonly) unless block
144
145
 
145
- begin
146
- block.call(project)
147
- ensure
148
- project.close
146
+ Progress.with("Loading #{File.basename(path)}") do |progress|
147
+ progress.update(detail: "opening model")
148
+ project = Model::Project.open(path, readonly: readonly)
149
+ begin
150
+ progress.update(detail: "processing model")
151
+ block.call(project)
152
+ ensure
153
+ project.close
154
+ end
149
155
  end
150
156
  end
151
157
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mxrb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lucas Moura
@@ -372,9 +372,11 @@ files:
372
372
  - lib/mxrb/oql/sql_server_workload_analyzer.rb
373
373
  - lib/mxrb/oql/workload_analyzer.rb
374
374
  - lib/mxrb/oql/workload_baseline.rb
375
+ - lib/mxrb/progress.rb
375
376
  - lib/mxrb/project_lifecycle.rb
376
377
  - lib/mxrb/protocols.rb
377
378
  - lib/mxrb/protocols/plan.rb
379
+ - lib/mxrb/runtime/clipboard.rb
378
380
  - lib/mxrb/runtime/database_workspace.rb
379
381
  - lib/mxrb/runtime/docker_executor.rb
380
382
  - lib/mxrb/runtime/docker_workspace.rb