mxrb 0.1.2 → 0.1.4
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 +458 -10
- data/docs/de-DE/README.md +1 -0
- data/docs/de-DE/oql-sql.md +42 -0
- data/docs/de-DE/project-structure.md +201 -2
- data/docs/de-DE/runtime-ruby.md +104 -0
- data/docs/de-DE/runtime-schema-migrations.md +44 -0
- data/docs/de-DE/validation-matrix.md +42 -4
- data/docs/en-US/README.md +1 -0
- data/docs/en-US/oql-sql.md +41 -0
- data/docs/en-US/project-structure.md +192 -2
- data/docs/en-US/runtime-ruby.md +100 -0
- data/docs/en-US/runtime-schema-migrations.md +41 -0
- data/docs/en-US/validation-matrix.md +39 -4
- data/docs/pt-BR/README.md +1 -0
- data/docs/pt-BR/entity-dsl.md +8 -0
- data/docs/pt-BR/oql-sql.md +41 -0
- data/docs/pt-BR/project-structure.md +197 -2
- data/docs/pt-BR/runtime-ruby.md +108 -0
- data/docs/pt-BR/runtime-schema-migrations.md +40 -0
- data/docs/pt-BR/validation-matrix.md +42 -5
- data/docs/pt-BR/writing.md +28 -0
- data/lib/mxrb/cli/help.rb +426 -0
- data/lib/mxrb/cli/release.rb +137 -0
- data/lib/mxrb/compiler/compatibility_analyzer.rb +1 -1
- data/lib/mxrb/compiler/data_grid_bundle_compiler.rb +8 -1
- data/lib/mxrb/compiler/domain_document_compiler.rb +3 -0
- data/lib/mxrb/compiler/gallery_bundle_compiler.rb +11 -1
- data/lib/mxrb/compiler/generic_widget_bundle_compiler.rb +76 -19
- data/lib/mxrb/compiler/legacy_custom_widget_compiler.rb +159 -0
- data/lib/mxrb/compiler/legacy_data_grid_compiler.rb +43 -7
- data/lib/mxrb/compiler/legacy_page_builder.rb +1248 -23
- data/lib/mxrb/compiler/packager.rb +27 -13
- data/lib/mxrb/compiler/page_bundle_compiler.rb +13 -4
- data/lib/mxrb/compiler/portable_packager.rb +16 -11
- data/lib/mxrb/compiler/web_bundle_builder.rb +5 -1
- data/lib/mxrb/compiler/web_shell_materializer.rb +42 -2
- data/lib/mxrb/compiler/widget_package_extractor.rb +3 -0
- data/lib/mxrb/domain_diagram/lifecycle.rb +272 -0
- data/lib/mxrb/domain_diagram.rb +409 -0
- data/lib/mxrb/dsl/builder.rb +62 -11
- data/lib/mxrb/environment.rb +226 -0
- data/lib/mxrb/exporter.rb +265 -45
- data/lib/mxrb/http/server.rb +107 -0
- data/lib/mxrb/initializer.rb +80 -9
- data/lib/mxrb/integrity/validator.rb +32 -7
- data/lib/mxrb/io/bson_codec.rb +42 -1
- data/lib/mxrb/io/mpr_file.rb +146 -3
- data/lib/mxrb/model/attribute.rb +3 -0
- data/lib/mxrb/model/entity.rb +44 -2
- data/lib/mxrb/model/microflow.rb +3 -1
- data/lib/mxrb/model/module.rb +71 -0
- data/lib/mxrb/model/page.rb +126 -15
- data/lib/mxrb/model/project.rb +3 -0
- data/lib/mxrb/modeler/catalog.rb +178 -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/reverse_translator.rb +394 -0
- data/lib/mxrb/oql/server.rb +4 -8
- data/lib/mxrb/oql.rb +32 -4
- data/lib/mxrb/progress.rb +242 -0
- data/lib/mxrb/project_lifecycle.rb +5 -2
- data/lib/mxrb/ruby_app/exporter.rb +1749 -0
- data/lib/mxrb/ruby_app/preset.rb +506 -0
- data/lib/mxrb/ruby_app/session_manager.rb +122 -0
- data/lib/mxrb/ruby_app.rb +1361 -0
- data/lib/mxrb/runtime/access_control.rb +408 -0
- data/lib/mxrb/runtime/native.rb +1015 -93
- data/lib/mxrb/runtime/scheduler.rb +396 -0
- data/lib/mxrb/runtime/schema_migrator.rb +598 -0
- data/lib/mxrb/runtime/shared_store.rb +335 -0
- data/lib/mxrb/runtime/sqlite_store.rb +729 -0
- data/lib/mxrb/scaffold/generator.rb +11 -3
- data/lib/mxrb/team_server.rb +10 -7
- data/lib/mxrb/uml/activity_diagram.rb +118 -0
- data/lib/mxrb/uml/class_diagram.rb +118 -0
- data/lib/mxrb/uml/sequence_diagram.rb +112 -0
- data/lib/mxrb/uml/server.rb +146 -0
- data/lib/mxrb/uml/support.rb +35 -0
- data/lib/mxrb/version.rb +1 -1
- data/lib/mxrb/web_ui/assets/abnfDiagram-N423BO3Z-C4JD3cEC.js +1 -0
- data/lib/mxrb/web_ui/assets/arc-3Z53kgFp.js +1 -0
- data/lib/mxrb/web_ui/assets/architecture-TIHT7OUA-BStBTLea.js +1 -0
- data/lib/mxrb/web_ui/assets/architectureDiagram-T3A2C74G-DBwqnM4J.js +36 -0
- data/lib/mxrb/web_ui/assets/array-BifhSqXX.js +1 -0
- data/lib/mxrb/web_ui/assets/blockDiagram-VBNYF7ZC-DLtuHt-j.js +132 -0
- data/lib/mxrb/web_ui/assets/c4Diagram-5PPSVZJV-ConSk_pe.js +10 -0
- data/lib/mxrb/web_ui/assets/channel-BhPvD7Dt.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-2GRJ4B5K-D2HpWsf_.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-4I5QYGJK-CiIdNwaF.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-5RXB4S5H-Bu_AaEkE.js +231 -0
- data/lib/mxrb/web_ui/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
- data/lib/mxrb/web_ui/assets/chunk-6Q2QTUOP-wQkNmt_X.js +88 -0
- data/lib/mxrb/web_ui/assets/chunk-7BUUIJ7U-Bb538aSH.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-GF5L2VYU-G8MmiBzU.js +206 -0
- data/lib/mxrb/web_ui/assets/chunk-I66GZJ75-CulcrLPh.js +127 -0
- data/lib/mxrb/web_ui/assets/chunk-JQJVKLGR-ukjzcdlL.js +156 -0
- data/lib/mxrb/web_ui/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-KBJHAD2P-Wm35lYov.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-KEIR6QF5-BICK3FdT.js +161 -0
- data/lib/mxrb/web_ui/assets/chunk-NSK5VX7P-D2nYrCY4.js +2 -0
- data/lib/mxrb/web_ui/assets/chunk-QR6OTTB3-BrdIzg4Q.js +62 -0
- data/lib/mxrb/web_ui/assets/chunk-RYQCIY6F-CCfZMcW6.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-UBXNYLIW-BPEVYsFK.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-W5SLKNZC-2E6OPhA4.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-WRU74C26-CBIvOyWp.js +70 -0
- data/lib/mxrb/web_ui/assets/chunk-XXDRQBXY-pH58XAyl.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
- data/lib/mxrb/web_ui/assets/classDiagram-JCYQIIEL-xiqo4D89.js +1 -0
- data/lib/mxrb/web_ui/assets/classDiagram-v2-OCEON4UE-xiqo4D89.js +1 -0
- data/lib/mxrb/web_ui/assets/cose-bilkent-JH36ORCC-t_flPpii.js +1 -0
- data/lib/mxrb/web_ui/assets/cynefin-VYW2F7L2-BIqktTEv.js +1 -0
- data/lib/mxrb/web_ui/assets/cynefinDiagram-MW4NZA55-C2d--lut.js +62 -0
- data/lib/mxrb/web_ui/assets/cytoscape.esm-B-NFISlW.js +321 -0
- data/lib/mxrb/web_ui/assets/dagre-Buvkdvvj.js +1 -0
- data/lib/mxrb/web_ui/assets/dagre-VZM6K2ZE-CSz2_tb5.js +4 -0
- data/lib/mxrb/web_ui/assets/defaultLocale-BFoDCU3G.js +1 -0
- data/lib/mxrb/web_ui/assets/diagram-7IWD3JNH-DCt7u7V_.js +30 -0
- data/lib/mxrb/web_ui/assets/diagram-B4RE2ZJO-DqZBqkMu.js +3 -0
- data/lib/mxrb/web_ui/assets/diagram-LBJQPF4R-ClIYbIsI.js +24 -0
- data/lib/mxrb/web_ui/assets/diagram-Q27KOJAE-DFxhvthJ.js +24 -0
- data/lib/mxrb/web_ui/assets/diagram-UB23O5K3-BSUTXptv.js +41 -0
- data/lib/mxrb/web_ui/assets/dist-D2qOEbeJ.js +1 -0
- data/lib/mxrb/web_ui/assets/domain-mGOZ1KCG.css +1 -0
- data/lib/mxrb/web_ui/assets/domain-qsjXhy4v.js +1 -0
- data/lib/mxrb/web_ui/assets/ebnfDiagram-BXEA7PRR-B5reICEi.js +1 -0
- data/lib/mxrb/web_ui/assets/erDiagram-JOGREHBK-Co5Mxn2p.js +85 -0
- data/lib/mxrb/web_ui/assets/eventmodeling-45OFAUF4-C1ylhSks.js +1 -0
- data/lib/mxrb/web_ui/assets/flowDiagram-UKHOOZJN-CHXaJIaS.js +1 -0
- data/lib/mxrb/web_ui/assets/ganttDiagram-PKOTCBZU-DPPPaWaz.js +292 -0
- data/lib/mxrb/web_ui/assets/gitGraph-TEB2WS4Q-qS77ywA9.js +1 -0
- data/lib/mxrb/web_ui/assets/gitGraphDiagram-DS77QQ5N-DpAv6-BT.js +106 -0
- data/lib/mxrb/web_ui/assets/graphlib-DS17s2tU.js +1 -0
- data/lib/mxrb/web_ui/assets/info-DKCQHKI2-C9VD1Y4e.js +1 -0
- data/lib/mxrb/web_ui/assets/infoDiagram-6WML65LV-Bk1kbcCy.js +2 -0
- data/lib/mxrb/web_ui/assets/init-C-OQMol4.js +1 -0
- data/lib/mxrb/web_ui/assets/ishikawaDiagram-WSZJBQD7-0gcISBFR.js +70 -0
- data/lib/mxrb/web_ui/assets/journeyDiagram-NVQOT4AX-Dt0RQnlh.js +139 -0
- data/lib/mxrb/web_ui/assets/jsx-runtime-DV0a5kSb.js +9 -0
- data/lib/mxrb/web_ui/assets/kanban-definition-27J2QSJJ-DIPQM7w9.js +89 -0
- data/lib/mxrb/web_ui/assets/katex-CXMH3UgJ.js +257 -0
- data/lib/mxrb/web_ui/assets/line-C0rYD-tL.js +1 -0
- data/lib/mxrb/web_ui/assets/linear-BPPxWorj.js +1 -0
- data/lib/mxrb/web_ui/assets/map-BaFkSB1l.js +1 -0
- data/lib/mxrb/web_ui/assets/mermaid-parser.core-B_UPzTxa.js +7 -0
- data/lib/mxrb/web_ui/assets/mindmap-definition-FAOFIHXS-CdJRSMuk.js +96 -0
- data/lib/mxrb/web_ui/assets/modeler-BNqHthwk.css +1 -0
- data/lib/mxrb/web_ui/assets/modeler-CQmqflbg.js +1 -0
- data/lib/mxrb/web_ui/assets/ordinal-BDEzSJ7C.js +1 -0
- data/lib/mxrb/web_ui/assets/packet-7NZHBO7P-CwQ4NSBs.js +1 -0
- data/lib/mxrb/web_ui/assets/path-fybaL0A-.js +1 -0
- data/lib/mxrb/web_ui/assets/pegDiagram-VL7TDLO6-D40S7ppx.js +1 -0
- data/lib/mxrb/web_ui/assets/pie-RZYD4A2V-CJroHMKh.js +1 -0
- data/lib/mxrb/web_ui/assets/pieDiagram-7S7Q4E2Y-QB54__BV.js +39 -0
- data/lib/mxrb/web_ui/assets/quadrantDiagram-CIZ2JOQS-DBhDS6ag.js +7 -0
- data/lib/mxrb/web_ui/assets/radar-I7S5WNFK-DdIUwd2d.js +1 -0
- data/lib/mxrb/web_ui/assets/railroad-3IZDKUUU-B1Lw6Xyq.js +1 -0
- data/lib/mxrb/web_ui/assets/railroad-abnf-AHOZXSZD-CQgQoeak.js +1 -0
- data/lib/mxrb/web_ui/assets/railroad-ebnf-EBAXGLYW-D7qEQ2x8.js +1 -0
- data/lib/mxrb/web_ui/assets/railroad-peg-LSFZ7HO6-C005Vxbb.js +1 -0
- data/lib/mxrb/web_ui/assets/railroadDiagram-AXF67PYL-BBP2qk7M.js +1 -0
- data/lib/mxrb/web_ui/assets/requirementDiagram-LRYGKXZP-D2cLsAoI.js +84 -0
- data/lib/mxrb/web_ui/assets/rough.esm-Dy-Kn_BL.js +1 -0
- data/lib/mxrb/web_ui/assets/sankeyDiagram-W5VNT64P-Bpi3FiP2.js +40 -0
- data/lib/mxrb/web_ui/assets/sequenceDiagram-SI44F4Z6-DCRcDlVe.js +162 -0
- data/lib/mxrb/web_ui/assets/sizeCapture-X5ZJPWSS-B0uUizjq.js +1 -0
- data/lib/mxrb/web_ui/assets/src-CvxfUak2.js +1 -0
- data/lib/mxrb/web_ui/assets/stateDiagram-OKZ733FA-C2GCEoxl.js +1 -0
- data/lib/mxrb/web_ui/assets/stateDiagram-v2-UEYNNEHI-D2gWgedh.js +1 -0
- data/lib/mxrb/web_ui/assets/swimlanes-SLNWSIFB-CRI9irEW.js +2 -0
- data/lib/mxrb/web_ui/assets/swimlanesDiagram-ULZ7WXOC-C5Wb_-hZ.js +8 -0
- data/lib/mxrb/web_ui/assets/theme-_mxc7BdY.css +1 -0
- data/lib/mxrb/web_ui/assets/timeline-definition-Z64GVDOM-B1-mNg-h.js +120 -0
- data/lib/mxrb/web_ui/assets/treeView-QDETBFTQ-7CTvIwKq.js +1 -0
- data/lib/mxrb/web_ui/assets/treemap-6X3UGDF4-CtL22pq5.js +1 -0
- data/lib/mxrb/web_ui/assets/uml-C3KAFGTq.js +51 -0
- data/lib/mxrb/web_ui/assets/uml-Dn-qd01X.css +1 -0
- data/lib/mxrb/web_ui/assets/vennDiagram-T6HMQDX7-BzVnUEAy.js +34 -0
- data/lib/mxrb/web_ui/assets/wardley-OPB4EBWU-JNi-QNag.js +1 -0
- data/lib/mxrb/web_ui/assets/wardleyDiagram-T6FBY63Y-BUwmAQ-B.js +78 -0
- data/lib/mxrb/web_ui/assets/xychartDiagram-ELKLHX3M-DEQS8sKp.js +7 -0
- data/lib/mxrb/web_ui/domain.html +15 -0
- data/lib/mxrb/web_ui/modeler.html +16 -0
- data/lib/mxrb/web_ui/uml.html +31 -0
- data/lib/mxrb/web_ui.rb +89 -0
- data/lib/mxrb/widget_certification.rb +208 -0
- data/lib/mxrb/writer.rb +278 -41
- data/lib/mxrb.rb +38 -8
- metadata +157 -5
data/lib/mxrb/exporter.rb
CHANGED
|
@@ -8,7 +8,9 @@ require "digest"
|
|
|
8
8
|
module Mxrb
|
|
9
9
|
# Exports an MPR into an editable, layered Ruby source tree.
|
|
10
10
|
class Exporter
|
|
11
|
+
MODES = %i[mendix ruby].freeze
|
|
11
12
|
LAYERS = %w[domain application presentation infrastructure].freeze
|
|
13
|
+
MODULE_PROGRESS_WEIGHT = 10
|
|
12
14
|
MARKETPLACE_PROVENANCE = [
|
|
13
15
|
".mxrb/marketplace.lock.json",
|
|
14
16
|
".mxrb/marketplace",
|
|
@@ -57,40 +59,64 @@ module Mxrb
|
|
|
57
59
|
Microflows$RestCallAction
|
|
58
60
|
].freeze
|
|
59
61
|
|
|
60
|
-
|
|
62
|
+
attr_reader :mode
|
|
63
|
+
|
|
64
|
+
def initialize(mpr_path, output_dir, mode: :mendix)
|
|
61
65
|
@mpr_path = File.expand_path(mpr_path)
|
|
62
66
|
@output_dir = File.expand_path(output_dir)
|
|
67
|
+
@mode = mode.to_sym
|
|
68
|
+
raise ArgumentError, "export mode must be mendix or ruby" unless MODES.include?(@mode)
|
|
63
69
|
end
|
|
64
70
|
|
|
65
71
|
def export!(parallel: true)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
72
|
+
return export_ruby!(parallel:) if mode == :ruby
|
|
73
|
+
|
|
74
|
+
Progress.with("Exporting #{File.basename(@mpr_path)}") do |progress|
|
|
75
|
+
FileUtils.mkdir_p(@output_dir)
|
|
76
|
+
Mxrb.open(@mpr_path) do |project|
|
|
77
|
+
@architecture = project.architecture_definition
|
|
78
|
+
units = project.all_units
|
|
79
|
+
modules = project.modules
|
|
80
|
+
@inferred_public_artifacts = infer_public_artifacts(modules)
|
|
81
|
+
assets = project_asset_files
|
|
82
|
+
progress.update(
|
|
83
|
+
current: 0,
|
|
84
|
+
total: 4 + assets.size + (units.size * 2) + (modules.size * MODULE_PROGRESS_WEIGHT),
|
|
85
|
+
detail: "preparing Ruby project"
|
|
86
|
+
)
|
|
87
|
+
export_app_structure
|
|
88
|
+
progress.advance(detail: "project structure")
|
|
89
|
+
export_project_assets(assets, progress)
|
|
90
|
+
export_native_units(project, units, progress)
|
|
91
|
+
ruby_sources = export_ruby_app_sources(project)
|
|
92
|
+
export_security(project)
|
|
93
|
+
progress.advance(detail: "project security")
|
|
94
|
+
export_architecture_contracts(project)
|
|
95
|
+
progress.advance(detail: "navigation and design system")
|
|
96
|
+
export_modules(modules, progress, parallel:)
|
|
97
|
+
write_project(project, ruby_sources:)
|
|
98
|
+
progress.advance(detail: "project.rb")
|
|
80
99
|
end
|
|
81
|
-
write_project(project)
|
|
82
100
|
end
|
|
83
101
|
@output_dir
|
|
84
102
|
end
|
|
85
103
|
|
|
86
104
|
private
|
|
87
105
|
|
|
106
|
+
def export_ruby!(parallel:)
|
|
107
|
+
sidecar = File.join(@output_dir, ".mxrb", "mendix")
|
|
108
|
+
self.class.new(@mpr_path, sidecar, mode: :mendix).export!(parallel:)
|
|
109
|
+
RubyApp::Exporter.new(@mpr_path, @output_dir, mendix_sidecar: sidecar).export!
|
|
110
|
+
end
|
|
111
|
+
|
|
88
112
|
def export_module(mod)
|
|
89
113
|
root = File.join(@output_dir, "modules", mod.name)
|
|
90
114
|
LAYERS.each { FileUtils.mkdir_p(File.join(root, _1)) }
|
|
91
115
|
|
|
92
116
|
export_domain(root, mod)
|
|
93
117
|
export_microflows(root, mod)
|
|
118
|
+
export_application_documents(root, mod)
|
|
119
|
+
export_infrastructure_documents(root, mod)
|
|
94
120
|
export_pages(root, mod)
|
|
95
121
|
export_menus(root, mod)
|
|
96
122
|
export_nanoflows(root, mod)
|
|
@@ -106,25 +132,31 @@ module Mxrb
|
|
|
106
132
|
].each { write(File.join(@output_dir, _1, ".keep"), "") }
|
|
107
133
|
end
|
|
108
134
|
|
|
109
|
-
def
|
|
135
|
+
def project_asset_files
|
|
110
136
|
source_root = File.dirname(@mpr_path)
|
|
111
|
-
|
|
137
|
+
(Model::DesignSystem::ASSET_DIRECTORIES + MARKETPLACE_PROVENANCE).flat_map do |directory|
|
|
112
138
|
root = File.join(source_root, directory)
|
|
113
139
|
next [root] if File.file?(root) && !File.symlink?(root)
|
|
114
140
|
next [] unless File.directory?(root) && !File.symlink?(root)
|
|
115
141
|
|
|
116
142
|
Dir.glob(File.join(root, "**", "*"), File::FNM_DOTMATCH)
|
|
117
143
|
end.select { File.file?(_1) && !File.symlink?(_1) }.sort
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def export_project_assets(files, progress)
|
|
147
|
+
source_root = File.dirname(@mpr_path)
|
|
118
148
|
entries = files.map do |source|
|
|
119
149
|
relative = Pathname.new(source).relative_path_from(Pathname.new(source_root)).to_s
|
|
120
150
|
target = File.join(@output_dir, relative)
|
|
121
151
|
FileUtils.mkdir_p(File.dirname(target))
|
|
122
152
|
FileUtils.cp(source, target)
|
|
123
|
-
{
|
|
153
|
+
result = {
|
|
124
154
|
"path" => relative,
|
|
125
155
|
"size" => File.size(source),
|
|
126
156
|
"sha256" => Digest::SHA256.file(source).hexdigest
|
|
127
157
|
}
|
|
158
|
+
progress.advance(detail: "asset #{relative}")
|
|
159
|
+
result
|
|
128
160
|
end
|
|
129
161
|
write(
|
|
130
162
|
File.join(@output_dir, ".mxrb", "assets.json"),
|
|
@@ -132,8 +164,8 @@ module Mxrb
|
|
|
132
164
|
)
|
|
133
165
|
end
|
|
134
166
|
|
|
135
|
-
def export_native_units(project)
|
|
136
|
-
units =
|
|
167
|
+
def export_native_units(project, project_units, progress)
|
|
168
|
+
units = project_units.filter_map do |unit|
|
|
137
169
|
doc = project.parse_bson(unit)
|
|
138
170
|
type = doc["$Type"]
|
|
139
171
|
next if type.to_s.empty? || type == "Projects$Project"
|
|
@@ -147,16 +179,21 @@ module Mxrb
|
|
|
147
179
|
"type" => type,
|
|
148
180
|
"contents" => Base64.strict_encode64(IO::BsonCodec.serialize(doc))
|
|
149
181
|
}
|
|
182
|
+
ensure
|
|
183
|
+
progress.advance(detail: "native baseline")
|
|
150
184
|
end
|
|
151
185
|
write(
|
|
152
186
|
File.join(@output_dir, ".mxrb", "native_units.json"),
|
|
153
187
|
JSON.pretty_generate("format_version" => project.format_version.to_s, "units" => units)
|
|
154
188
|
)
|
|
155
|
-
source =
|
|
189
|
+
source = project_units.filter_map do |unit|
|
|
156
190
|
doc = project.parse_bson(unit)
|
|
157
191
|
next if doc["$Type"].to_s.empty? || doc["$Type"] == "Projects$Project"
|
|
192
|
+
next if Model::Module::EDITABLE_DOCUMENT_TYPES.include?(doc["$Type"])
|
|
158
193
|
|
|
159
194
|
native_unit_source(project, unit, doc)
|
|
195
|
+
ensure
|
|
196
|
+
progress.advance(detail: "editable native units")
|
|
160
197
|
end
|
|
161
198
|
write(
|
|
162
199
|
File.join(@output_dir, ".mxrb", "native_units.rb"),
|
|
@@ -164,6 +201,36 @@ module Mxrb
|
|
|
164
201
|
)
|
|
165
202
|
end
|
|
166
203
|
|
|
204
|
+
def export_ruby_app_sources(project)
|
|
205
|
+
files = project.mpr.ruby_app_sources
|
|
206
|
+
return false if files.empty?
|
|
207
|
+
|
|
208
|
+
payload = files.map do |file|
|
|
209
|
+
contents = file.fetch(:contents)
|
|
210
|
+
{
|
|
211
|
+
"path" => file.fetch(:path), "sha256" => file.fetch(:sha256),
|
|
212
|
+
"contents" => Base64.strict_encode64(contents), "mode" => file.fetch(:mode, 0o644)
|
|
213
|
+
}
|
|
214
|
+
end
|
|
215
|
+
write(
|
|
216
|
+
File.join(@output_dir, ".mxrb", "ruby_sources.json"),
|
|
217
|
+
JSON.pretty_generate("version" => 1, "files" => payload)
|
|
218
|
+
)
|
|
219
|
+
true
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def export_modules(modules, progress, parallel:)
|
|
223
|
+
operation = lambda do |mod|
|
|
224
|
+
export_module(mod)
|
|
225
|
+
progress.advance(MODULE_PROGRESS_WEIGHT, detail: "module #{mod.name}")
|
|
226
|
+
end
|
|
227
|
+
if parallel && modules.size > 1
|
|
228
|
+
modules.map { |mod| Thread.new { operation.call(mod) } }.each(&:join)
|
|
229
|
+
else
|
|
230
|
+
modules.each { operation.call(_1) }
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
167
234
|
def native_unit_source(project, unit, doc)
|
|
168
235
|
<<~RUBY
|
|
169
236
|
native_unit #{ruby(unit.fetch("UnitID"))},
|
|
@@ -211,8 +278,10 @@ module Mxrb
|
|
|
211
278
|
|
|
212
279
|
def export_module_scaffolding(root)
|
|
213
280
|
%w[
|
|
214
|
-
domain/
|
|
215
|
-
|
|
281
|
+
domain/entities domain/dtos domain/oql_views domain/enumerations domain/constants
|
|
282
|
+
domain/rules domain/policies
|
|
283
|
+
application/ports/repositories application/queries application/queries/datasets
|
|
284
|
+
application/validations application/jobs application/jobs/scheduled_events
|
|
216
285
|
presentation/features presentation/client_actions presentation/snippets presentation/view_models
|
|
217
286
|
presentation/menus
|
|
218
287
|
infrastructure/persistence/mendix infrastructure/persistence/external
|
|
@@ -238,26 +307,62 @@ module Mxrb
|
|
|
238
307
|
|
|
239
308
|
def export_domain(root, mod)
|
|
240
309
|
domain = File.join(root, "domain")
|
|
241
|
-
entities_dir = File.join(domain, "entities")
|
|
242
|
-
FileUtils.mkdir_p(entities_dir)
|
|
243
|
-
|
|
244
310
|
associations = mod.associations.group_by(&:from_entity_id)
|
|
245
311
|
architecture = architecture_module(mod.name)
|
|
246
|
-
|
|
312
|
+
paths = {}
|
|
313
|
+
grouped = mod.entities.group_by { entity_domain_route(_1) }
|
|
314
|
+
grouped.each do |route, entities|
|
|
315
|
+
unique_entity_filenames(entities, route:).each do |id, filename|
|
|
316
|
+
paths[id] = File.join(route, filename)
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
domain_documents = mod.domain_documents
|
|
320
|
+
oql_documents = mod.oql_view_documents
|
|
321
|
+
attached_oql_documents = {}
|
|
247
322
|
mod.entities.each do |entity|
|
|
248
323
|
entity_metadata = architecture&.fetch(:entities, [])&.find { _1[:name] == entity.name }
|
|
324
|
+
source = entity_source(entity, mod, associations.fetch(entity.id, []), entity_metadata)
|
|
325
|
+
if oql_view_entity?(entity) && (document = oql_document_for(entity, oql_documents, mod.name))
|
|
326
|
+
source = "#{source.rstrip}\n\n#{native_document_declaration(document)}"
|
|
327
|
+
attached_oql_documents[document.fetch(:id)] = true
|
|
328
|
+
end
|
|
249
329
|
write(
|
|
250
|
-
File.join(
|
|
251
|
-
entity_source(entity, mod, associations.fetch(entity.id, []), entity_metadata)
|
|
330
|
+
File.join(domain, paths.fetch(entity.id)), source
|
|
252
331
|
)
|
|
253
332
|
end
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
333
|
+
used = paths.values.to_h { [_1, true] }
|
|
334
|
+
domain_documents.reject { attached_oql_documents[_1.fetch(:id)] }.each do |document|
|
|
335
|
+
relative = unique_relative_path(document.fetch(:route), underscore(document.fetch(:name)), used)
|
|
336
|
+
write(File.join(domain, relative), mapping_document_source(document))
|
|
337
|
+
paths[document.fetch(:id)] = relative
|
|
338
|
+
end
|
|
339
|
+
loads = paths.values.sort.map do |relative|
|
|
340
|
+
segments = relative.split(File::SEPARATOR).map { ruby(_1) }.join(", ")
|
|
341
|
+
%(evaluate File.join(__dir__, #{segments}))
|
|
257
342
|
end
|
|
258
343
|
write(File.join(domain, "model.rb"), "#{loads.join("\n")}\n")
|
|
259
344
|
end
|
|
260
345
|
|
|
346
|
+
def entity_domain_route(entity)
|
|
347
|
+
return 'oql_views' if oql_view_entity?(entity)
|
|
348
|
+
return 'dtos' unless entity.persistable
|
|
349
|
+
|
|
350
|
+
'entities'
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def oql_view_entity?(entity)
|
|
354
|
+
entity.respond_to?(:oql_view?) && entity.oql_view?
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def oql_document_for(entity, documents, module_name)
|
|
358
|
+
reference = entity.oql_source_document.to_s
|
|
359
|
+
return nil if reference.empty?
|
|
360
|
+
|
|
361
|
+
documents.find do |document|
|
|
362
|
+
[document.fetch(:name), "#{module_name}.#{document.fetch(:name)}"].include?(reference)
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
|
|
261
366
|
def export_microflows(root, mod)
|
|
262
367
|
files = unique_filenames(mod.microflows)
|
|
263
368
|
by_layer = { "application" => [], "infrastructure" => [] }
|
|
@@ -265,7 +370,10 @@ module Mxrb
|
|
|
265
370
|
mod.microflows.each do |flow|
|
|
266
371
|
layer, category = flow_location(flow.name)
|
|
267
372
|
relative = File.join(category, files.fetch(flow.id))
|
|
268
|
-
metadata =
|
|
373
|
+
metadata = flow_metadata(
|
|
374
|
+
mod.name, :microflow, flow.name,
|
|
375
|
+
architecture&.fetch(:microflows, [])&.find { _1[:name] == flow.name }
|
|
376
|
+
)
|
|
269
377
|
write(File.join(root, layer, relative), microflow_source(flow, metadata))
|
|
270
378
|
by_layer.fetch(layer) << relative
|
|
271
379
|
end
|
|
@@ -296,6 +404,74 @@ module Mxrb
|
|
|
296
404
|
write_path_aggregator(File.join(presentation, "presentation.rb"), paths)
|
|
297
405
|
end
|
|
298
406
|
|
|
407
|
+
def export_infrastructure_documents(root, mod)
|
|
408
|
+
documents = mod.infrastructure_documents
|
|
409
|
+
return if documents.empty?
|
|
410
|
+
|
|
411
|
+
infrastructure = File.join(root, "infrastructure")
|
|
412
|
+
used = {}
|
|
413
|
+
paths = documents.map do |document|
|
|
414
|
+
base = underscore(document.fetch(:name))
|
|
415
|
+
relative = unique_relative_path(document.fetch(:route), base, used)
|
|
416
|
+
write(File.join(infrastructure, relative), mapping_document_source(document))
|
|
417
|
+
relative
|
|
418
|
+
end
|
|
419
|
+
append_to_aggregator(File.join(infrastructure, "infrastructure.rb"), paths)
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
def export_application_documents(root, mod)
|
|
423
|
+
documents = mod.application_documents
|
|
424
|
+
return if documents.empty?
|
|
425
|
+
|
|
426
|
+
application = File.join(root, 'application')
|
|
427
|
+
used = {}
|
|
428
|
+
paths = documents.map do |document|
|
|
429
|
+
relative = unique_relative_path(document.fetch(:route), underscore(document.fetch(:name)), used)
|
|
430
|
+
write(File.join(application, relative), mapping_document_source(document))
|
|
431
|
+
relative
|
|
432
|
+
end
|
|
433
|
+
append_to_aggregator(File.join(application, 'application.rb'), paths)
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def unique_relative_path(directory, base, used)
|
|
437
|
+
candidate = File.join(directory, "#{base}.rb")
|
|
438
|
+
suffix = 2
|
|
439
|
+
while used[candidate]
|
|
440
|
+
candidate = File.join(directory, "#{base}_#{suffix}.rb")
|
|
441
|
+
suffix += 1
|
|
442
|
+
end
|
|
443
|
+
used[candidate] = true
|
|
444
|
+
candidate
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def mapping_document_source(document)
|
|
448
|
+
<<~RUBY
|
|
449
|
+
# frozen_string_literal: true
|
|
450
|
+
|
|
451
|
+
#{native_document_declaration(document)}
|
|
452
|
+
RUBY
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
def native_document_declaration(document)
|
|
456
|
+
<<~RUBY.rstrip
|
|
457
|
+
native_document #{symbol(document.fetch(:name))},
|
|
458
|
+
type: #{ruby(document.fetch(:type))},
|
|
459
|
+
unit_id: #{ruby(document.fetch(:id))},
|
|
460
|
+
container_id: #{ruby(document.fetch(:container_id))},
|
|
461
|
+
containment: #{ruby(document.fetch(:containment))},
|
|
462
|
+
deep_structure: #{native_ruby(document.fetch(:doc), 24)}
|
|
463
|
+
RUBY
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
def append_to_aggregator(path, relative_paths)
|
|
467
|
+
existing = File.exist?(path) ? File.read(path).lines : []
|
|
468
|
+
additions = relative_paths.sort.map do |relative|
|
|
469
|
+
segments = relative.split(File::SEPARATOR).map { ruby(_1) }.join(", ")
|
|
470
|
+
"evaluate File.join(__dir__, #{segments})\n"
|
|
471
|
+
end
|
|
472
|
+
write(path, (existing + additions).uniq.join)
|
|
473
|
+
end
|
|
474
|
+
|
|
299
475
|
def export_menus(root, mod)
|
|
300
476
|
menus = mod.menus
|
|
301
477
|
return if menus.empty?
|
|
@@ -326,7 +502,10 @@ module Mxrb
|
|
|
326
502
|
architecture = architecture_module(mod.name)
|
|
327
503
|
flows.each do |flow|
|
|
328
504
|
relative = File.join("client_actions", files.fetch(flow.id))
|
|
329
|
-
metadata =
|
|
505
|
+
metadata = flow_metadata(
|
|
506
|
+
mod.name, :nanoflow, flow.name,
|
|
507
|
+
architecture&.fetch(:nanoflows, [])&.find { _1[:name] == flow.name }
|
|
508
|
+
)
|
|
330
509
|
write(File.join(presentation, relative), nanoflow_source(flow, metadata))
|
|
331
510
|
paths << relative
|
|
332
511
|
end
|
|
@@ -357,7 +536,7 @@ module Mxrb
|
|
|
357
536
|
end
|
|
358
537
|
end
|
|
359
538
|
|
|
360
|
-
def write_project(project)
|
|
539
|
+
def write_project(project, ruby_sources: false)
|
|
361
540
|
module_loads = project.modules.map do |mod|
|
|
362
541
|
%( evaluate File.join(__dir__, "modules", #{ruby(mod.name)}, "module.rb"))
|
|
363
542
|
end.join("\n")
|
|
@@ -373,6 +552,7 @@ module Mxrb
|
|
|
373
552
|
mendix_version #{ruby(project.mendix_version || "10.18.0")}
|
|
374
553
|
native_units File.join(__dir__, ".mxrb", "native_units.json")
|
|
375
554
|
project_assets File.join(__dir__, ".mxrb", "assets.json"), root: __dir__
|
|
555
|
+
#{' ruby_app_sources File.join(__dir__, ".mxrb", "ruby_sources.json")' if ruby_sources}
|
|
376
556
|
evaluate File.join(__dir__, ".mxrb", "native_units.rb")
|
|
377
557
|
evaluate File.join(__dir__, "app", "security", "security.rb")
|
|
378
558
|
evaluate File.join(__dir__, "app", "navigation", "navigation.rb")
|
|
@@ -586,6 +766,12 @@ module Mxrb
|
|
|
586
766
|
end
|
|
587
767
|
flags = []
|
|
588
768
|
flags << " non_persistent!" unless entity.persistable
|
|
769
|
+
if oql_view_entity?(entity)
|
|
770
|
+
options = []
|
|
771
|
+
options << "source: #{ruby(entity.oql_source_document)}" if entity.oql_source_document
|
|
772
|
+
options << "query: #{ruby(entity.oql_query)}" unless entity.oql_query.to_s.empty?
|
|
773
|
+
flags << " oql_view #{options.join(', ')}" unless options.empty?
|
|
774
|
+
end
|
|
589
775
|
flags << " documentation #{ruby(entity.documentation)}" unless entity.documentation.to_s.empty?
|
|
590
776
|
generalization = entity.respond_to?(:generalization_target) ? entity.generalization_target : nil
|
|
591
777
|
flags << " generalizes #{ruby(generalization)}" if generalization
|
|
@@ -678,19 +864,21 @@ module Mxrb
|
|
|
678
864
|
end
|
|
679
865
|
end
|
|
680
866
|
|
|
681
|
-
def unique_entity_filenames(entities)
|
|
682
|
-
|
|
867
|
+
def unique_entity_filenames(entities, route: nil)
|
|
868
|
+
suffix = route == "dtos" ? "_dto" : nil
|
|
869
|
+
unique_filenames(entities, suffix:)
|
|
683
870
|
end
|
|
684
871
|
|
|
685
|
-
def unique_filenames(entities)
|
|
872
|
+
def unique_filenames(entities, suffix: nil)
|
|
686
873
|
used = {}
|
|
687
874
|
entities.to_h do |entity|
|
|
688
875
|
base = underscore(entity.name)
|
|
876
|
+
base = "#{base}#{suffix}" if suffix && !base.end_with?(suffix)
|
|
689
877
|
candidate = "#{base}.rb"
|
|
690
|
-
|
|
878
|
+
sequence = 2
|
|
691
879
|
while used[candidate]
|
|
692
|
-
candidate = "#{base}_#{
|
|
693
|
-
|
|
880
|
+
candidate = "#{base}_#{sequence}.rb"
|
|
881
|
+
sequence += 1
|
|
694
882
|
end
|
|
695
883
|
used[candidate] = true
|
|
696
884
|
[entity.id, candidate]
|
|
@@ -718,6 +906,8 @@ module Mxrb
|
|
|
718
906
|
body << " return_type #{symbol(flow.return_type)}" if flow.return_type.is_a?(String)
|
|
719
907
|
body << " documentation #{ruby(flow.documentation)}" unless flow.documentation.to_s.empty?
|
|
720
908
|
body << " allow_concurrent_execution #{flow.allow_concurrent_execution ? 'true' : 'false'}"
|
|
909
|
+
apply_entity_access = flow.respond_to?(:apply_entity_access) && flow.apply_entity_access
|
|
910
|
+
body << " apply_entity_access #{apply_entity_access ? 'true' : 'false'}"
|
|
721
911
|
body << " mark_as_used #{flow.mark_as_used ? 'true' : 'false'}"
|
|
722
912
|
body << " excluded #{flow.excluded ? 'true' : 'false'}"
|
|
723
913
|
body << " allowed_roles #{flow.allowed_module_roles.map { symbol(_1) }.join(', ')}" unless flow.allowed_module_roles.empty?
|
|
@@ -737,10 +927,12 @@ module Mxrb
|
|
|
737
927
|
elsif objects.any?
|
|
738
928
|
body << " # Native body baseline retained: this graph shape has no typed DSL mapping yet"
|
|
739
929
|
end
|
|
930
|
+
declaration = [symbol(flow.name)]
|
|
931
|
+
declaration << "public: true" if metadata&.fetch(:public, false)
|
|
740
932
|
<<~RUBY
|
|
741
933
|
# frozen_string_literal: true
|
|
742
934
|
|
|
743
|
-
microflow #{
|
|
935
|
+
microflow #{declaration.join(', ')} do
|
|
744
936
|
#{body.join("\n")}
|
|
745
937
|
end
|
|
746
938
|
RUBY
|
|
@@ -781,7 +973,7 @@ module Mxrb
|
|
|
781
973
|
body << " allowed_roles #{page.allowed_module_roles.map { symbol(_1) }.join(', ')}" unless page.allowed_module_roles.empty?
|
|
782
974
|
deep = page_deep_structure(page)
|
|
783
975
|
if (source = metadata&.dig(:data_source) || page.data_source)
|
|
784
|
-
body << " data_source #{source.fetch(:kind)}: #{
|
|
976
|
+
body << " data_source #{source.fetch(:kind)}: #{reference(source.fetch(:name))}"
|
|
785
977
|
end
|
|
786
978
|
body << " deep_structure(#{native_ruby(deep, 2)})" if deep
|
|
787
979
|
metadata&.fetch(:events, [])&.each do |event|
|
|
@@ -870,7 +1062,9 @@ module Mxrb
|
|
|
870
1062
|
def render_widget(widget, indent)
|
|
871
1063
|
pad = " " * indent
|
|
872
1064
|
child_pad = " " * (indent + 2)
|
|
873
|
-
|
|
1065
|
+
# Architecture metadata is serialized through JSON inside generated MPRs,
|
|
1066
|
+
# so a second export can surface widget type values as strings.
|
|
1067
|
+
type = widget.fetch(:type).to_sym
|
|
874
1068
|
options = widget.fetch(:options, {})
|
|
875
1069
|
|
|
876
1070
|
# Snippet: snippet :name, from: "..."
|
|
@@ -1710,6 +1904,32 @@ module Mxrb
|
|
|
1710
1904
|
@architecture&.fetch(:modules, [])&.find { _1[:name] == name }
|
|
1711
1905
|
end
|
|
1712
1906
|
|
|
1907
|
+
def infer_public_artifacts(modules)
|
|
1908
|
+
modules.each_with_object({}) do |mod, public_artifacts|
|
|
1909
|
+
mod.pages.each do |page|
|
|
1910
|
+
source = page.data_source
|
|
1911
|
+
next unless source
|
|
1912
|
+
|
|
1913
|
+
target_module, target_name = source.fetch(:name).to_s.split(".", 2)
|
|
1914
|
+
next if target_name.to_s.empty? || target_module == mod.name
|
|
1915
|
+
|
|
1916
|
+
public_artifacts[[target_module, source.fetch(:kind).to_sym, target_name]] = true
|
|
1917
|
+
end
|
|
1918
|
+
end
|
|
1919
|
+
end
|
|
1920
|
+
|
|
1921
|
+
def flow_metadata(module_name, kind, name, metadata)
|
|
1922
|
+
result = metadata ? metadata.dup : {}
|
|
1923
|
+
if @inferred_public_artifacts&.include?([module_name, kind, name])
|
|
1924
|
+
result[:public] = true
|
|
1925
|
+
end
|
|
1926
|
+
result.empty? ? nil : result
|
|
1927
|
+
end
|
|
1928
|
+
|
|
1929
|
+
def reference(value)
|
|
1930
|
+
value.to_s.include?(".") ? ruby(value.to_s) : symbol(value)
|
|
1931
|
+
end
|
|
1932
|
+
|
|
1713
1933
|
def symbol(value)
|
|
1714
1934
|
value.to_s.to_sym.inspect
|
|
1715
1935
|
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'puma'
|
|
4
|
+
require 'uri'
|
|
5
|
+
|
|
6
|
+
module Mxrb
|
|
7
|
+
module Http
|
|
8
|
+
# Case-insensitive HTTP header collection used by the internal request API.
|
|
9
|
+
class Headers
|
|
10
|
+
def initialize(values = {})
|
|
11
|
+
@values = values.to_h { |name, value| [name.to_s.downcase, value.to_s] }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def [](name) = @values[name.to_s.downcase]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
Request = Data.define(:path, :request_method, :body, :query, :headers) do
|
|
18
|
+
def [](name) = headers[name]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Mutable response keeps the existing MXRB dispatchers independent of Rack.
|
|
22
|
+
class Response
|
|
23
|
+
attr_accessor :status, :body
|
|
24
|
+
attr_reader :headers
|
|
25
|
+
|
|
26
|
+
def initialize
|
|
27
|
+
@status = 200
|
|
28
|
+
@body = ''
|
|
29
|
+
@headers = {}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def [](name) = headers[name]
|
|
33
|
+
|
|
34
|
+
def []=(name, value)
|
|
35
|
+
headers[name] = value
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Small embedded-Puma adapter for MXRB's loopback-only HTTP services.
|
|
40
|
+
class Server
|
|
41
|
+
DEFAULT_MAX_THREADS = 5
|
|
42
|
+
|
|
43
|
+
attr_reader :host, :port, :puma
|
|
44
|
+
|
|
45
|
+
def initialize(host:, port:, logger: nil, max_threads: DEFAULT_MAX_THREADS, &dispatcher)
|
|
46
|
+
raise ArgumentError, 'HTTP dispatcher is required' unless dispatcher
|
|
47
|
+
|
|
48
|
+
@host = host.to_s
|
|
49
|
+
@port = Integer(port)
|
|
50
|
+
@dispatcher = dispatcher
|
|
51
|
+
@logger = logger
|
|
52
|
+
@max_threads = Integer(max_threads)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def start
|
|
56
|
+
@puma = Puma::Server.new(method(:call), nil, puma_options)
|
|
57
|
+
@puma.add_tcp_listener(host, port)
|
|
58
|
+
yield(@puma) if block_given?
|
|
59
|
+
@thread = @puma.run
|
|
60
|
+
@thread.join
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def shutdown = @puma&.stop
|
|
64
|
+
|
|
65
|
+
def call(environment)
|
|
66
|
+
request = request_from(environment)
|
|
67
|
+
response = Response.new
|
|
68
|
+
@dispatcher.call(request, response)
|
|
69
|
+
[response.status, response.headers, [response.body.to_s]]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def puma_options
|
|
75
|
+
{
|
|
76
|
+
min_threads: 0,
|
|
77
|
+
max_threads: @max_threads,
|
|
78
|
+
environment: 'production',
|
|
79
|
+
log_writer: @logger || Puma::LogWriter.null,
|
|
80
|
+
queue_requests: true
|
|
81
|
+
}
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def request_from(environment)
|
|
85
|
+
input = environment['rack.input']
|
|
86
|
+
body = input ? input.read.to_s : ''
|
|
87
|
+
input.rewind if input.respond_to?(:rewind)
|
|
88
|
+
Request.new(
|
|
89
|
+
path: "#{environment['SCRIPT_NAME']}#{environment.fetch('PATH_INFO', '/')}",
|
|
90
|
+
request_method: environment.fetch('REQUEST_METHOD', 'GET'),
|
|
91
|
+
body:,
|
|
92
|
+
query: URI.decode_www_form(environment['QUERY_STRING'].to_s).to_h,
|
|
93
|
+
headers: Headers.new(request_headers(environment))
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def request_headers(environment)
|
|
98
|
+
environment.each_with_object({}) do |(name, value), headers|
|
|
99
|
+
next unless name.start_with?('HTTP_')
|
|
100
|
+
|
|
101
|
+
header = name.delete_prefix('HTTP_').split('_').map(&:capitalize).join('-')
|
|
102
|
+
headers[header] = value
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|