mxrb 0.1.2 → 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.
- checksums.yaml +4 -4
- data/bin/mxrb +3 -0
- data/docs/pt-BR/entity-dsl.md +8 -0
- data/docs/pt-BR/writing.md +28 -0
- data/lib/mxrb/compiler/packager.rb +27 -13
- data/lib/mxrb/compiler/portable_packager.rb +16 -11
- data/lib/mxrb/dsl/builder.rb +19 -2
- data/lib/mxrb/exporter.rb +178 -32
- data/lib/mxrb/integrity/validator.rb +18 -7
- data/lib/mxrb/model/entity.rb +17 -1
- data/lib/mxrb/model/module.rb +71 -0
- data/lib/mxrb/official_marketplace/module_package_importer.rb +23 -12
- data/lib/mxrb/official_marketplace/widget_package_installer.rb +15 -11
- data/lib/mxrb/official_marketplace.rb +9 -3
- data/lib/mxrb/oql.rb +9 -3
- data/lib/mxrb/progress.rb +242 -0
- data/lib/mxrb/team_server.rb +10 -7
- data/lib/mxrb/version.rb +1 -1
- data/lib/mxrb/writer.rb +79 -18
- data/lib/mxrb.rb +12 -7
- metadata +2 -1
data/lib/mxrb/model/module.rb
CHANGED
|
@@ -7,6 +7,40 @@ module Mxrb
|
|
|
7
7
|
# $Type: Projects$Module
|
|
8
8
|
# ContainmentName: "Modules"
|
|
9
9
|
class Module < Unit
|
|
10
|
+
INFRASTRUCTURE_DOCUMENT_ROUTES = {
|
|
11
|
+
'ExportMappings$ExportMapping' => 'mappings/exports',
|
|
12
|
+
'ImportMappings$ImportMapping' => 'mappings/imports',
|
|
13
|
+
'JsonStructures$JsonStructure' => 'mappings/json_structures',
|
|
14
|
+
'MessageDefinitions$MessageDefinitionCollection' => 'mappings/message_definitions',
|
|
15
|
+
'MessageDefinitions$MessageDefinition2' => 'mappings/message_definitions',
|
|
16
|
+
'XmlSchemas$XmlSchema' => 'mappings/xml_schemas',
|
|
17
|
+
'Rest$PublishedRestService' => 'endpoints',
|
|
18
|
+
'WebServices$PublishedService' => 'endpoints',
|
|
19
|
+
'WebServices$PublishedWebService' => 'endpoints',
|
|
20
|
+
'ODataPublish$PublishedODataService' => 'endpoints',
|
|
21
|
+
'ODataPublish$PublishedODataService2' => 'endpoints',
|
|
22
|
+
'Rest$ConsumedRestService' => 'integrations',
|
|
23
|
+
'Rest$ConsumedODataService' => 'integrations',
|
|
24
|
+
'AppServices$ConsumedAppService' => 'integrations',
|
|
25
|
+
'ODataImport$ConsumedODataService' => 'integrations'
|
|
26
|
+
}.freeze
|
|
27
|
+
MAPPING_DOCUMENT_TYPES = INFRASTRUCTURE_DOCUMENT_ROUTES.keys.grep(
|
|
28
|
+
/Mappings|JsonStructures|MessageDefinitions|XmlSchemas/
|
|
29
|
+
).freeze
|
|
30
|
+
APPLICATION_DOCUMENT_ROUTES = {
|
|
31
|
+
'DataSets$DataSet' => 'queries/datasets',
|
|
32
|
+
'ScheduledEvents$ScheduledEvent' => 'jobs/scheduled_events'
|
|
33
|
+
}.freeze
|
|
34
|
+
DOMAIN_DOCUMENT_ROUTES = {
|
|
35
|
+
'DomainModels$ViewEntitySourceDocument' => 'oql_views',
|
|
36
|
+
'Enumerations$Enumeration' => 'enumerations',
|
|
37
|
+
'Constants$Constant' => 'constants'
|
|
38
|
+
}.freeze
|
|
39
|
+
EDITABLE_DOCUMENT_TYPES = (
|
|
40
|
+
INFRASTRUCTURE_DOCUMENT_ROUTES.keys + APPLICATION_DOCUMENT_ROUTES.keys +
|
|
41
|
+
DOMAIN_DOCUMENT_ROUTES.keys
|
|
42
|
+
).freeze
|
|
43
|
+
|
|
10
44
|
attr_reader :name, :sort_index, :from_app_store,
|
|
11
45
|
:app_store_guid, :app_store_version, :export_level
|
|
12
46
|
|
|
@@ -84,6 +118,28 @@ module Mxrb
|
|
|
84
118
|
.map { unit_to_doc(_1) }
|
|
85
119
|
end
|
|
86
120
|
|
|
121
|
+
def mapping_documents
|
|
122
|
+
infrastructure_documents.select { MAPPING_DOCUMENT_TYPES.include?(_1[:type]) }
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def infrastructure_documents
|
|
126
|
+
@infrastructure_documents ||= routed_documents(INFRASTRUCTURE_DOCUMENT_ROUTES)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def application_documents
|
|
130
|
+
@application_documents ||= routed_documents(APPLICATION_DOCUMENT_ROUTES)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def domain_documents
|
|
134
|
+
@domain_documents ||= routed_documents(DOMAIN_DOCUMENT_ROUTES)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def oql_view_documents
|
|
138
|
+
@oql_view_documents ||= domain_documents.select do |document|
|
|
139
|
+
document[:type] == 'DomainModels$ViewEntitySourceDocument'
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
87
143
|
def module_roles
|
|
88
144
|
@module_roles ||= begin
|
|
89
145
|
raw = @mpr.children_of(@id).find { _1["ContainmentName"] == "ModuleSecurity" }
|
|
@@ -109,6 +165,21 @@ module Mxrb
|
|
|
109
165
|
@mpr.parse_contents(unit_hash[:raw])
|
|
110
166
|
end
|
|
111
167
|
|
|
168
|
+
def routed_documents(routes)
|
|
169
|
+
document_units.filter_map do |unit|
|
|
170
|
+
route = routes[unit[:type]]
|
|
171
|
+
next unless route
|
|
172
|
+
|
|
173
|
+
raw = unit.fetch(:raw)
|
|
174
|
+
doc = @mpr.parse_contents(raw)
|
|
175
|
+
{
|
|
176
|
+
id: raw.fetch("UnitID"), container_id: raw.fetch("ContainerID"),
|
|
177
|
+
containment: raw.fetch("ContainmentName"), type: unit.fetch(:type),
|
|
178
|
+
name: doc["Name"] || doc["name"] || raw.fetch("UnitID"), doc:, route:
|
|
179
|
+
}
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
112
183
|
# Documents live in ContainmentName = "Documents" recursively under this module.
|
|
113
184
|
# We do a simple two-pass: direct Documents children + Documents inside Folders.
|
|
114
185
|
def document_units
|
|
@@ -122,6 +122,7 @@ module Mxrb
|
|
|
122
122
|
def install(staged_files, protected_files = [])
|
|
123
123
|
staged_files.each do |relative, source|
|
|
124
124
|
install_file(relative, source) unless protected_files.include?(relative)
|
|
125
|
+
yield(relative) if block_given?
|
|
125
126
|
end
|
|
126
127
|
end
|
|
127
128
|
|
|
@@ -179,9 +180,12 @@ module Mxrb
|
|
|
179
180
|
end
|
|
180
181
|
|
|
181
182
|
def import!
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
183
|
+
Progress.with("Importing #{File.basename(@package_path)}") do |progress|
|
|
184
|
+
validate_inputs!
|
|
185
|
+
progress.update(detail: 'reading package')
|
|
186
|
+
Dir.mktmpdir('mxrb-module-import-') do |temporary|
|
|
187
|
+
Zip::File.open(@package_path) { import_archive(_1, temporary, progress) }
|
|
188
|
+
end
|
|
185
189
|
end
|
|
186
190
|
rescue Zip::Error, REXML::ParseException => e
|
|
187
191
|
raise MarketplaceError, "invalid Mendix module package: #{e.message}"
|
|
@@ -189,12 +193,12 @@ module Mxrb
|
|
|
189
193
|
|
|
190
194
|
private
|
|
191
195
|
|
|
192
|
-
def import_archive(archive, temporary)
|
|
196
|
+
def import_archive(archive, temporary, progress)
|
|
193
197
|
reader = ModulePackageReader.new(archive)
|
|
194
198
|
descriptor = reader.descriptor
|
|
195
199
|
source_path = reader.extract_project(descriptor, temporary)
|
|
196
200
|
staged_files = reader.stage_files(descriptor.files, temporary)
|
|
197
|
-
import_project(source_path, descriptor, staged_files, temporary)
|
|
201
|
+
import_project(source_path, descriptor, staged_files, temporary, progress)
|
|
198
202
|
end
|
|
199
203
|
|
|
200
204
|
def validate_inputs!
|
|
@@ -203,12 +207,13 @@ module Mxrb
|
|
|
203
207
|
raise MarketplaceError, "target root not found: #{@target_root}" unless File.directory?(@target_root)
|
|
204
208
|
end
|
|
205
209
|
|
|
206
|
-
def import_project(source_path, descriptor, staged_files, temporary) # rubocop:disable Metrics/MethodLength
|
|
210
|
+
def import_project(source_path, descriptor, staged_files, temporary, progress) # rubocop:disable Metrics/MethodLength
|
|
207
211
|
source = IO::MprFile.open(source_path, readonly: true)
|
|
208
212
|
target = IO::MprFile.open(@mpr_path)
|
|
209
213
|
assets = ModulePackageAssets.new(@target_root, temporary)
|
|
210
214
|
imported_ids = []
|
|
211
|
-
import_transaction(source, target, descriptor, staged_files, assets, imported_ids)
|
|
215
|
+
import_transaction(source, target, descriptor, staged_files, assets, imported_ids, progress)
|
|
216
|
+
progress.advance(detail: 'import transaction')
|
|
212
217
|
import_result(source, target, descriptor, staged_files, imported_ids)
|
|
213
218
|
rescue StandardError
|
|
214
219
|
assets&.rollback
|
|
@@ -220,24 +225,30 @@ module Mxrb
|
|
|
220
225
|
end
|
|
221
226
|
|
|
222
227
|
# rubocop:disable Metrics/ParameterLists
|
|
223
|
-
def import_transaction(source, target, descriptor, staged_files, assets, imported_ids)
|
|
228
|
+
def import_transaction(source, target, descriptor, staged_files, assets, imported_ids, progress) # rubocop:disable Metrics/MethodLength
|
|
224
229
|
validate_versions!(source, target, descriptor)
|
|
225
230
|
module_unit, units = package_units(source, descriptor.name)
|
|
226
231
|
validate_target!(target, descriptor.name, units)
|
|
232
|
+
progress.update(
|
|
233
|
+
current: 2, total: units.size + staged_files.size + 3,
|
|
234
|
+
detail: "#{units.size} model units"
|
|
235
|
+
)
|
|
227
236
|
target.transaction do
|
|
228
|
-
imported_ids.concat(insert_units(source, target, module_unit, units))
|
|
229
|
-
assets.install(staged_files, @protected_files)
|
|
237
|
+
imported_ids.concat(insert_units(source, target, module_unit, units, progress))
|
|
238
|
+
assets.install(staged_files, @protected_files) do |relative|
|
|
239
|
+
progress.advance(detail: "asset #{relative}")
|
|
240
|
+
end
|
|
230
241
|
end
|
|
231
242
|
end
|
|
232
243
|
# rubocop:enable Metrics/ParameterLists
|
|
233
244
|
|
|
234
|
-
def insert_units(source, target, module_unit, units)
|
|
245
|
+
def insert_units(source, target, module_unit, units, progress)
|
|
235
246
|
units.map do |unit|
|
|
236
247
|
container = unit == module_unit ? target.root_unit.fetch('UnitID') : unit.fetch('ContainerID')
|
|
237
248
|
target.insert_unit(
|
|
238
249
|
container_uuid: container, containment_name: unit.fetch('ContainmentName'),
|
|
239
250
|
contents_doc: source.parse_contents(unit)
|
|
240
|
-
)
|
|
251
|
+
).tap { progress.advance(detail: "unit #{unit.fetch('UnitID')}") }
|
|
241
252
|
end
|
|
242
253
|
end
|
|
243
254
|
|
|
@@ -200,17 +200,21 @@ module Mxrb
|
|
|
200
200
|
end
|
|
201
201
|
|
|
202
202
|
def install(archive, package)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
203
|
+
Progress.with("Installing widget #{package.name}") do |progress|
|
|
204
|
+
validate_target!
|
|
205
|
+
progress.update(detail: 'reading widget package')
|
|
206
|
+
inventory = WidgetPackageInventory.read(archive)
|
|
207
|
+
validate_identity!(inventory, package)
|
|
208
|
+
digest = Digest::SHA256.file(archive).hexdigest
|
|
209
|
+
destination = safe_path(File.join('widgets', inventory.project_filename))
|
|
210
|
+
cache = safe_path(cache_relative(inventory, package))
|
|
211
|
+
lock_path = safe_path(File.join('.mxrb', 'marketplace.lock.json'))
|
|
212
|
+
current = validate_boundaries!(package, destination, cache, lock_path)
|
|
213
|
+
progress.update(detail: 'installing project assets')
|
|
214
|
+
install_transaction(
|
|
215
|
+
archive, package, inventory, digest, destination, cache, lock_path, current
|
|
216
|
+
)
|
|
217
|
+
end
|
|
214
218
|
end
|
|
215
219
|
|
|
216
220
|
private
|
|
@@ -223,14 +223,20 @@ module Mxrb
|
|
|
223
223
|
end
|
|
224
224
|
|
|
225
225
|
def json(url, authorization: default_authorization)
|
|
226
|
-
|
|
226
|
+
Progress.with('Loading Marketplace data') do |progress|
|
|
227
|
+
progress.update(detail: URI.parse(url).host)
|
|
228
|
+
JSON.parse(get(url, accept: 'application/json', authorization:))
|
|
229
|
+
end
|
|
227
230
|
rescue JSON::ParserError => e
|
|
228
231
|
raise MarketplaceError, "invalid JSON response: #{e.message}"
|
|
229
232
|
end
|
|
230
233
|
|
|
231
234
|
def download(url, destination, authorization: default_authorization)
|
|
232
|
-
File.
|
|
233
|
-
|
|
235
|
+
Progress.with("Downloading #{File.basename(destination)}") do |progress|
|
|
236
|
+
progress.update(detail: URI.parse(url).host)
|
|
237
|
+
File.binwrite(destination, get(url, accept: 'application/octet-stream', authorization:))
|
|
238
|
+
destination
|
|
239
|
+
end
|
|
234
240
|
end
|
|
235
241
|
|
|
236
242
|
private
|
data/lib/mxrb/oql.rb
CHANGED
|
@@ -49,7 +49,7 @@ module Mxrb
|
|
|
49
49
|
|
|
50
50
|
# Recursive BSON discovery is intentionally kept together so that its
|
|
51
51
|
# source-type guard and ownership context cannot diverge.
|
|
52
|
-
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
52
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
|
53
53
|
def discover(document, raw, module_name)
|
|
54
54
|
found = []
|
|
55
55
|
walk(document) do |node, path, ancestors|
|
|
@@ -59,6 +59,11 @@ module Mxrb
|
|
|
59
59
|
node['Query'], raw, module_name, path + ['Query'], source_type, ancestors
|
|
60
60
|
)
|
|
61
61
|
end
|
|
62
|
+
if source_type == 'DomainModels$ViewEntitySourceDocument' && node['Oql'].is_a?(String)
|
|
63
|
+
found << build_query(
|
|
64
|
+
node['Oql'], raw, module_name, path + ['Oql'], source_type, ancestors
|
|
65
|
+
)
|
|
66
|
+
end
|
|
62
67
|
node.each do |key, value|
|
|
63
68
|
next unless key.to_s.match?(/\AOqlQuery\z/i) && value.is_a?(String)
|
|
64
69
|
|
|
@@ -67,7 +72,7 @@ module Mxrb
|
|
|
67
72
|
end
|
|
68
73
|
found.uniq { [_1.unit_id, _1.path] }
|
|
69
74
|
end
|
|
70
|
-
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
75
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
|
71
76
|
|
|
72
77
|
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
|
73
78
|
def walk(node, path = [], ancestors = [], &block)
|
|
@@ -97,7 +102,8 @@ module Mxrb
|
|
|
97
102
|
unit_type = @project.parse_bson(raw)['$Type'].to_s
|
|
98
103
|
kind = if unit_type == 'DataSets$DataSet'
|
|
99
104
|
:dataset
|
|
100
|
-
elsif
|
|
105
|
+
elsif unit_type == 'DomainModels$ViewEntitySourceDocument' ||
|
|
106
|
+
owner['$Type'].to_s.match?(/Entity/i)
|
|
101
107
|
:view_entity
|
|
102
108
|
else
|
|
103
109
|
:oql
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'io/console'
|
|
4
|
+
require 'singleton'
|
|
5
|
+
|
|
6
|
+
module Mxrb
|
|
7
|
+
# Terminal progress rendering shared by every long-running MXRB operation.
|
|
8
|
+
# It is enabled automatically only for an interactive terminal, writes to
|
|
9
|
+
# stderr, and therefore never contaminates command output or JSON on stdout.
|
|
10
|
+
module Progress
|
|
11
|
+
THREAD_KEY = :mxrb_progress_task
|
|
12
|
+
FALSE_VALUES = %w[0 false no off].freeze
|
|
13
|
+
|
|
14
|
+
# No-op object returned when progress rendering is disabled.
|
|
15
|
+
class NullTask
|
|
16
|
+
include Singleton
|
|
17
|
+
|
|
18
|
+
def start = self
|
|
19
|
+
def update(**) = self
|
|
20
|
+
def advance(*, **) = self
|
|
21
|
+
def add_total(*) = self
|
|
22
|
+
def finish(*) = self
|
|
23
|
+
def fail(*) = self
|
|
24
|
+
def enabled? = false
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Thread-safe terminal renderer for a single operation.
|
|
28
|
+
class Task # rubocop:disable Metrics/ClassLength
|
|
29
|
+
SPINNER = %w[| / - \\].freeze
|
|
30
|
+
BAR_WIDTH = 28
|
|
31
|
+
REFRESH_INTERVAL = 0.08
|
|
32
|
+
|
|
33
|
+
attr_reader :label, :total, :current
|
|
34
|
+
|
|
35
|
+
def initialize(label, total: nil, io: $stderr)
|
|
36
|
+
@label = label.to_s
|
|
37
|
+
@total = normalize_total(total)
|
|
38
|
+
@io = io
|
|
39
|
+
@current = 0
|
|
40
|
+
@detail = nil
|
|
41
|
+
@started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
42
|
+
@last_rendered_at = 0.0
|
|
43
|
+
@spinner_index = 0
|
|
44
|
+
@mutex = Mutex.new
|
|
45
|
+
@finished = false
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def enabled? = true
|
|
49
|
+
|
|
50
|
+
def start
|
|
51
|
+
render(force: true)
|
|
52
|
+
start_spinner unless determinate?
|
|
53
|
+
self
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def update(current: nil, total: nil, detail: nil, force: false)
|
|
57
|
+
became_determinate = false
|
|
58
|
+
@mutex.synchronize do
|
|
59
|
+
became_determinate = @total.nil? && !total.nil?
|
|
60
|
+
@total = normalize_total(total) unless total.nil?
|
|
61
|
+
@current = [[Integer(current), 0].max, @total || Float::INFINITY].min unless current.nil?
|
|
62
|
+
@detail = detail.to_s unless detail.nil?
|
|
63
|
+
end
|
|
64
|
+
stop_spinner if became_determinate
|
|
65
|
+
render(force:)
|
|
66
|
+
self
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def advance(amount = 1, detail: nil, force: false)
|
|
70
|
+
@mutex.synchronize do
|
|
71
|
+
@current += amount
|
|
72
|
+
@current = [@current, @total].min if @total
|
|
73
|
+
@detail = detail.to_s unless detail.nil?
|
|
74
|
+
end
|
|
75
|
+
render(force:)
|
|
76
|
+
self
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def add_total(amount)
|
|
80
|
+
@mutex.synchronize { @total = (@total || 0) + Integer(amount) }
|
|
81
|
+
render(force: true)
|
|
82
|
+
self
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def finish(detail = nil)
|
|
86
|
+
stop_spinner
|
|
87
|
+
@mutex.synchronize do
|
|
88
|
+
return self if @finished
|
|
89
|
+
|
|
90
|
+
@detail = detail.to_s if detail
|
|
91
|
+
@current = @total if @total
|
|
92
|
+
@finished = true
|
|
93
|
+
end
|
|
94
|
+
render(force: true, final: true)
|
|
95
|
+
self
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def fail(message = nil)
|
|
99
|
+
stop_spinner
|
|
100
|
+
@mutex.synchronize do
|
|
101
|
+
return self if @finished
|
|
102
|
+
|
|
103
|
+
@detail = message.to_s unless message.to_s.empty?
|
|
104
|
+
@finished = true
|
|
105
|
+
end
|
|
106
|
+
render(force: true, final: true, failed: true)
|
|
107
|
+
self
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
def normalize_total(value)
|
|
113
|
+
return nil if value.nil?
|
|
114
|
+
|
|
115
|
+
[Integer(value), 1].max
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def determinate? = !@total.nil?
|
|
119
|
+
|
|
120
|
+
def start_spinner
|
|
121
|
+
return unless dynamic_terminal?
|
|
122
|
+
|
|
123
|
+
@spinner_thread = Thread.new do
|
|
124
|
+
loop do
|
|
125
|
+
sleep REFRESH_INTERVAL
|
|
126
|
+
break if @mutex.synchronize { @finished }
|
|
127
|
+
|
|
128
|
+
render(force: true)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def stop_spinner
|
|
134
|
+
thread = @spinner_thread
|
|
135
|
+
return unless thread
|
|
136
|
+
|
|
137
|
+
@mutex.synchronize { @finished = true }
|
|
138
|
+
thread.join
|
|
139
|
+
@spinner_thread = nil
|
|
140
|
+
@mutex.synchronize { @finished = false }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def render(force: false, final: false, failed: false) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
144
|
+
line = nil
|
|
145
|
+
@mutex.synchronize do
|
|
146
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
147
|
+
return if !force && !final && now - @last_rendered_at < REFRESH_INTERVAL
|
|
148
|
+
|
|
149
|
+
@last_rendered_at = now
|
|
150
|
+
line = rendered_line(now, failed:)
|
|
151
|
+
end
|
|
152
|
+
prefix = dynamic_terminal? ? "\r\e[2K" : ''
|
|
153
|
+
suffix = final || !dynamic_terminal? ? "\n" : ''
|
|
154
|
+
@io.write("#{prefix}#{truncate(line)}#{suffix}")
|
|
155
|
+
@io.flush if @io.respond_to?(:flush)
|
|
156
|
+
rescue IOError, Errno::EPIPE
|
|
157
|
+
nil
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def rendered_line(now, failed:)
|
|
161
|
+
detail = @detail.to_s.empty? ? '' : " - #{@detail}"
|
|
162
|
+
elapsed = format('%.1fs', now - @started_at)
|
|
163
|
+
return "[mxrb] [FAILED] #{@label}#{detail} (#{elapsed})" if failed
|
|
164
|
+
|
|
165
|
+
determinate_line(detail, elapsed) || spinner_line(detail, elapsed)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def determinate_line(detail, elapsed)
|
|
169
|
+
return unless determinate?
|
|
170
|
+
|
|
171
|
+
ratio = [@current.fdiv(@total), 1.0].min
|
|
172
|
+
filled = (ratio * BAR_WIDTH).round
|
|
173
|
+
bar = '#' * filled + '-' * (BAR_WIDTH - filled)
|
|
174
|
+
"[mxrb] [#{bar}] #{format('%3d%%', ratio * 100)} #{@label}#{detail} (#{elapsed})"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def spinner_line(detail, elapsed)
|
|
178
|
+
frame = SPINNER[@spinner_index % SPINNER.length]
|
|
179
|
+
@spinner_index += 1
|
|
180
|
+
"[mxrb] [#{frame}] #{@label}#{detail} (#{elapsed})"
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def dynamic_terminal?
|
|
184
|
+
@io.respond_to?(:tty?) && @io.tty?
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def truncate(line)
|
|
188
|
+
width = @io.respond_to?(:winsize) ? @io.winsize.last : 100
|
|
189
|
+
width = 100 unless width.to_i.positive?
|
|
190
|
+
return line if line.length <= width
|
|
191
|
+
|
|
192
|
+
"#{line[0, width - 3]}..."
|
|
193
|
+
rescue IOError, Errno::ENOTTY
|
|
194
|
+
line
|
|
195
|
+
end
|
|
196
|
+
end # rubocop:enable Metrics/ClassLength
|
|
197
|
+
|
|
198
|
+
class << self
|
|
199
|
+
attr_writer :io
|
|
200
|
+
|
|
201
|
+
def configure(enabled: nil, io: nil)
|
|
202
|
+
@enabled = enabled unless enabled.nil?
|
|
203
|
+
@io = io if io
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def reset!
|
|
207
|
+
@enabled = nil
|
|
208
|
+
@io = nil
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def enabled?
|
|
212
|
+
return @enabled unless @enabled.nil?
|
|
213
|
+
return false if FALSE_VALUES.include?(ENV.fetch('MXRB_PROGRESS', '').downcase)
|
|
214
|
+
|
|
215
|
+
output.respond_to?(:tty?) && output.tty?
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def with(label, total: nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
219
|
+
active = Thread.current[THREAD_KEY]
|
|
220
|
+
return yield(active) if active
|
|
221
|
+
return yield(NullTask.instance) unless enabled?
|
|
222
|
+
|
|
223
|
+
task = Task.new(label, total:, io: output).start
|
|
224
|
+
Thread.current[THREAD_KEY] = task
|
|
225
|
+
result = yield(task)
|
|
226
|
+
task.finish
|
|
227
|
+
result
|
|
228
|
+
rescue StandardError => e
|
|
229
|
+
task&.fail(e.message)
|
|
230
|
+
raise
|
|
231
|
+
ensure
|
|
232
|
+
Thread.current[THREAD_KEY] = nil if defined?(task) && task
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def current = Thread.current[THREAD_KEY] || NullTask.instance
|
|
236
|
+
|
|
237
|
+
private
|
|
238
|
+
|
|
239
|
+
def output = @io || $stderr
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
data/lib/mxrb/team_server.rb
CHANGED
|
@@ -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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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