caixanegra 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caixanegra
4
+ module API
5
+ module Designer
6
+ class FlowsController < ::Caixanegra::APIController
7
+ before_action :set_flow, only: %i[show debug_run]
8
+
9
+ def show
10
+ render json: @flow
11
+ end
12
+
13
+ def update
14
+ Caixanegra::Manager.set(params[:id], JSON.parse(request.body.read))
15
+
16
+ head :ok
17
+ end
18
+
19
+ def debug_run
20
+ execution = Caixanegra::Executor.new(
21
+ initial_carryover: initial_carryover,
22
+ flow_definition: @flow,
23
+ unit_scope: params[:unit_scope],
24
+ debug_mode: true
25
+ ).run
26
+
27
+ render json: { result: execution[:result], debug: execution[:debug] }
28
+ end
29
+
30
+ private
31
+
32
+ def initial_carryover
33
+ JSON.parse(request.body.read)
34
+ rescue StandardError
35
+ {}
36
+ end
37
+
38
+ def set_flow
39
+ @flow = Caixanegra::Manager.get(params[:id])
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caixanegra
4
+ module API
5
+ module Designer
6
+ class UnitsController < ::Caixanegra::APIController
7
+ def index
8
+ render json: units
9
+ end
10
+
11
+ private
12
+
13
+ def unit_scope
14
+ params[:scope].presence || nil
15
+ end
16
+
17
+ def units
18
+ @units ||= begin
19
+ base_units = Caixanegra.units.reject { |_, v| v.is_a? Hash }
20
+ scope_units = Caixanegra.units[unit_scope] if unit_scope.present?
21
+ all_units = base_units.merge(scope_units || {})
22
+
23
+ all_units.map do |k, v|
24
+ base = {
25
+ title: v.name,
26
+ type: v.type,
27
+ description: v.description,
28
+ class: k,
29
+ exits: v.exits&.map { |e| { name: e } },
30
+ inputs: v.inputs,
31
+ assignments: v.assignments
32
+ }
33
+
34
+ base[:set] = v.set if base[:set]
35
+
36
+ base
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caixanegra
4
+ class APIController < ActionController::API
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caixanegra
4
+ class ApplicationController < ActionController::Base
5
+ protect_from_forgery with: :exception
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caixanegra
4
+ class DesignerController < ApplicationController
5
+ def index
6
+ @mounted_path = Caixanegra::Engine.routes.find_script_name({})
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,4 @@
1
+ module Caixanegra
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caixanegra
4
+ class Unit
5
+ attr_reader :oid
6
+
7
+ def initialize(oid, inputs = {}, mappings = {}, carry_over = {}, storage = {})
8
+ @oid = oid
9
+ @mappings = mappings
10
+ @inputs = inputs
11
+ @carry_over = carry_over.with_indifferent_access
12
+ @storage = storage.with_indifferent_access
13
+ set_mapping_defaults
14
+ end
15
+
16
+ def current_carry_over
17
+ @carry_over.dup
18
+ end
19
+
20
+ def current_storage
21
+ @storage.dup
22
+ end
23
+
24
+ def carry_over(value)
25
+ @carry_over.merge!(value) if value.is_a? Hash
26
+ end
27
+
28
+ def persist(value)
29
+ @storage.merge!(value) if value.is_a? Hash
30
+ end
31
+
32
+ def exit_through(exit_id)
33
+ {
34
+ exit_through: exit_id,
35
+ carry_over: @carry_over
36
+ }
37
+ end
38
+
39
+ def flow
40
+ exit_through exits.first
41
+ end
42
+
43
+ def input(id)
44
+ input_metadata = @mappings[id]
45
+ input_value = case input_metadata&.[](:type)
46
+ when 'storage'
47
+ @storage[id]
48
+ when 'carryover'
49
+ @carry_over[id]
50
+ when 'user'
51
+ input_metadata[:value]
52
+ end
53
+
54
+ if input_value.present?
55
+ result = input_value.dup
56
+ input_value.to_s.scan(Regexp.new(/%(.*?)%/)) do |match|
57
+ match = match[0]
58
+ result.gsub!("%#{match}%", @carry_over[match] || '')
59
+ end
60
+ input_value.to_s.scan(Regexp.new(/@(.*?)@/)) do |match|
61
+ match = match[0]
62
+ result.gsub!("@#{match}@", @storage[match] || '')
63
+ end
64
+
65
+ input_value = result
66
+ end
67
+ input_value.presence || @inputs[id][:default]
68
+ end
69
+
70
+ def name
71
+ self.class.name
72
+ end
73
+
74
+ def description
75
+ self.class.description
76
+ end
77
+
78
+ def inputs
79
+ self.class.inputs || []
80
+ end
81
+
82
+ def exits
83
+ self.class.exits || []
84
+ end
85
+
86
+ def set
87
+ self.class.set || []
88
+ end
89
+
90
+ private
91
+
92
+ def set_mapping_defaults
93
+ (self.class.inputs || {}).each do |key, data|
94
+ next if @mappings.key? key
95
+
96
+ @mappings[key] = { type: 'carryover' }
97
+ @mappings[key][:value] = data[:default] if data[:default].present?
98
+ end
99
+ end
100
+
101
+ class << self
102
+ attr_reader :name, :description, :inputs, :exits, :assignments, :type
103
+
104
+ @type = :passthrough
105
+
106
+ def configure_name(value)
107
+ @name = value
108
+ end
109
+
110
+ def configure_description(value)
111
+ @description = value
112
+ end
113
+
114
+ def configure_type(value)
115
+ @type = value
116
+ end
117
+
118
+ def configure_exits(exits)
119
+ @exits = exits
120
+ end
121
+
122
+ def configure_inputs(inputs)
123
+ @inputs = inputs
124
+ end
125
+
126
+ def configure_assignments(assignments)
127
+ @assignments = assignments
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,109 @@
1
+ <consts>
2
+ <mounted_path value="<%= @mounted_path %>" />
3
+ </consts>
4
+ <canvas style="position:fixed;top:0px;left:0px;"></canvas>
5
+ <div id="execution">
6
+ <div id="reset">
7
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24" xml:space="preserve">
8
+ <g><path d="M17.1,23.8L12.4,21l2.7-4.8l1.7,1l-1.7,3.1l3,1.8L17.1,23.8z M5.7,11L4,8L1.1,9.7L0.1,8l4.7-2.8L7.5,10L5.7,11z"/></g>
9
+ <g><polygon points="22,6 16.5,6 16.5,4 20,4 20,0.5 22,0.5"/></g>
10
+ <g><path d="M15.4,21.5l-0.4-2c4-0.9,6.9-4.5,6.9-8.6c0-0.6-0.1-1.3-0.2-1.9l2-0.4c0.2,0.8,0.3,1.6,0.3,2.3 C24,16.1,20.4,20.5,15.4,21.5z"/></g>
11
+ <g><path d="M9.8,21.3C5.3,19.9,2.2,15.8,2.2,11c0-1.3,0.2-2.6,0.7-3.8l1.9,0.7c-0.4,1-0.6,2-0.6,3.1c0,3.9,2.5,7.2,6.1,8.4L9.8,21.3z"/></g>
12
+ <g><path d="M19.6,5c-1.7-1.9-4.1-3-6.6-3c-2.1,0-4.1,0.8-5.7,2.1L6,2.6C7.9,0.9,10.4,0,13,0c3.1,0,6,1.3,8.1,3.6L19.6,5z"/></g>
13
+ </svg>
14
+ </div>
15
+ <div id="play">
16
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24" xml:space="preserve">
17
+ <path d="M19,12L5,22.2V1.8L19,12z"/>
18
+ </svg>
19
+ </div>
20
+ <div id="configure">
21
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
22
+ <path d="M19,17h-2v-2.1c-0.4-0.1-0.7-0.2-1-0.4l-1.5,1.5l-1.4-1.4l1.5-1.5c-0.2-0.3-0.3-0.7-0.4-1H12v-2h2.1 c0.1-0.4,0.2-0.7,0.4-1l-1.5-1.5l1.4-1.4L16,7.6c0.3-0.2,0.7-0.3,1-0.4V5h2v2.1c0.4,0.1,0.7,0.2,1,0.4l1.5-1.5l1.4,1.4L21.4,9 c0.2,0.3,0.3,0.7,0.4,1H24v2h-2.1c-0.1,0.4-0.2,0.7-0.4,1l1.5,1.5l-1.4,1.4L20,14.4c-0.3,0.2-0.7,0.3-1,0.4V17z M18,9 c-1.1,0-2,0.9-2,2c0,1.1,0.9,2,2,2s2-0.9,2-2C20,9.9,19.1,9,18,9z"/>
23
+ <path d="M7,12H5V9.9C4.6,9.8,4.3,9.6,4,9.4l-1.5,1.5L1.1,9.5L2.6,8C2.4,7.7,2.2,7.4,2.1,7H0V5h2.1c0.1-0.4,0.2-0.7,0.4-1L1.1,2.5 l1.4-1.4L4,2.6c0.3-0.2,0.7-0.3,1-0.4V0h2v2.1c0.4,0.1,0.7,0.2,1,0.4l1.5-1.5l1.4,1.4L9.4,4c0.2,0.3,0.3,0.7,0.4,1H12v2H9.9 C9.8,7.4,9.6,7.7,9.4,8l1.5,1.5l-1.4,1.4L8,9.4C7.7,9.6,7.4,9.8,7,9.9V12z M6,4C4.9,4,4,4.9,4,6s0.9,2,2,2s2-0.9,2-2S7.1,4,6,4z"/>
24
+ <path d="M9,24H7v-2.1c-0.4-0.1-0.7-0.2-1-0.4l-1.5,1.5l-1.4-1.4L4.6,20c-0.2-0.3-0.3-0.7-0.4-1H2v-2h2.1c0.1-0.4,0.2-0.7,0.4-1 l-1.5-1.5l1.4-1.4L6,14.6c0.3-0.2,0.7-0.3,1-0.4V12h2v2.1c0.4,0.1,0.7,0.2,1,0.4l1.5-1.5l1.4,1.4L11.4,16c0.2,0.3,0.3,0.7,0.4,1 H14v2h-2.1c-0.1,0.4-0.2,0.7-0.4,1l1.5,1.5l-1.4,1.4L10,21.4c-0.3,0.2-0.7,0.3-1,0.4V24z M8,16c-1.1,0-2,0.9-2,2s0.9,2,2,2 s2-0.9,2-2S9.1,16,8,16z"/>
25
+ </svg>
26
+ </div>
27
+ <div id="console">
28
+ <svg version="1.1" id="XMLID_75_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24" xml:space="preserve">
29
+ <path d="M24,24H0V0h24V24z M2,22h20V2H6v2h16v2H2V22z M2,4h2V2H2V4z M16,16h-6v-2h6V16z M5.7,15.7l-1.4-1.4L6.6,12L4.3,9.7 l1.4-1.4L9.4,12L5.7,15.7z"/>
30
+ </svg>
31
+ </div>
32
+ </div>
33
+ <div id="addUnit"><span>+</span></div>
34
+ <div id="save"><span>save</span></div>
35
+ <div id="unitMenu"></div>
36
+ <div id="executionConsole"></div>
37
+ <div id="executionConfiguration">
38
+ <div class="section">
39
+ <span>initial carry-over</span>
40
+ <span id="clearCarryOverObject">clear</span>
41
+ </div>
42
+ <div id="carryOverObject"></div>
43
+ </div>
44
+ <div id="unitDebugHits">
45
+ <div class="content">
46
+ <div class="name">run hits</div>
47
+ <div id="debugData"></div>
48
+ </div>
49
+ </div>
50
+ <div id="unitDetail">
51
+ <div class="color-code"></div>
52
+ <div class="content">
53
+ <div id="unitDetailClass" class="unit-detail-headers">
54
+ <div class="name"></div>
55
+ <div class="delete">
56
+ <div id="toggler">
57
+ <svg viewBox="0 0 56 56" xmlns="http://www.w3.org/2000/svg">
58
+ <path d="M 44.5235 48.6602 L 46.1407 14.3945 L 48.4844 14.3945 C 49.4454 14.3945 50.2187 13.5976 50.2187 12.6367 C 50.2187 11.6758 49.4454 10.8555 48.4844 10.8555 L 38.2422 10.8555 L 38.2422 7.3398 C 38.2422 3.9883 35.9688 1.8086 32.3595 1.8086 L 23.5938 1.8086 C 19.9844 1.8086 17.7344 3.9883 17.7344 7.3398 L 17.7344 10.8555 L 7.5391 10.8555 C 6.6016 10.8555 5.7813 11.6758 5.7813 12.6367 C 5.7813 13.5976 6.6016 14.3945 7.5391 14.3945 L 9.8829 14.3945 L 11.5000 48.6836 C 11.6641 52.0586 13.8907 54.1914 17.2657 54.1914 L 38.7579 54.1914 C 42.1095 54.1914 44.3595 52.0351 44.5235 48.6602 Z M 21.4844 7.5742 C 21.4844 6.2383 22.4688 5.3008 23.8751 5.3008 L 32.1016 5.3008 C 33.5313 5.3008 34.5157 6.2383 34.5157 7.5742 L 34.5157 10.8555 L 21.4844 10.8555 Z M 17.6173 50.6758 C 16.2579 50.6758 15.2500 49.6445 15.1797 48.2852 L 13.5391 14.3945 L 42.3907 14.3945 L 40.8438 48.2852 C 40.7735 49.6680 39.7891 50.6758 38.4063 50.6758 Z M 34.9610 46.5508 C 35.7344 46.5508 36.3204 45.9180 36.3438 45.0273 L 37.0469 20.2773 C 37.0704 19.3867 36.4610 18.7305 35.6641 18.7305 C 34.9376 18.7305 34.3282 19.4102 34.3048 20.2539 L 33.6016 45.0273 C 33.5782 45.8711 34.1641 46.5508 34.9610 46.5508 Z M 21.0626 46.5508 C 21.8595 46.5508 22.4454 45.8711 22.4219 45.0273 L 21.7188 20.2539 C 21.6954 19.4102 21.0626 18.7305 20.3360 18.7305 C 19.5391 18.7305 18.9532 19.3867 18.9766 20.2773 L 19.7032 45.0273 C 19.7266 45.9180 20.2891 46.5508 21.0626 46.5508 Z M 29.4298 45.0273 L 29.4298 20.2539 C 29.4298 19.4102 28.7969 18.7305 28.0235 18.7305 C 27.2500 18.7305 26.5938 19.4102 26.5938 20.2539 L 26.5938 45.0273 C 26.5938 45.8711 27.2500 46.5508 28.0235 46.5508 C 28.7735 46.5508 29.4298 45.8711 29.4298 45.0273 Z"/>
59
+ </svg>
60
+ </div>
61
+ <div class="confirm-buttons">
62
+ <div id="confirmDelete">
63
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
64
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22ZM10.2426 14.4142L17.3137 7.34315L18.7279 8.75736L10.2426 17.2426L6 13L7.41421 11.5858L10.2426 14.4142Z"/>
65
+ </svg>
66
+ </div>
67
+ <div id="cancelDelete">
68
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
69
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12ZM15.7123 16.773L12 13.0607L8.28769 16.773L7.22703 15.7123L10.9393 12L7.22703 8.28769L8.28769 7.22703L12 10.9393L15.7123 7.22703L16.773 8.28769L13.0607 12L16.773 15.7123L15.7123 16.773Z"/>
70
+ </svg>
71
+ </div>
72
+ </div>
73
+ </div>
74
+ </div>
75
+ <div id="unitDetailDescription"></div>
76
+ <div class="field">
77
+ <input type="text" id="unitDetailTitle" placeholder="title" />
78
+ </div>
79
+ <div id="dynamicContent"></div>
80
+ </div>
81
+ </div>
82
+ <div id="blocker">
83
+ <div class="information">
84
+ <div>caixa<span>negra</span></div>
85
+ <div class="message">loading your flow</div>
86
+ </div>
87
+ </div>
88
+ <div id="action_messenger">
89
+ <div class="icon icon-error">
90
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 51.976 51.976" xml:space="preserve">
91
+ <path d="M44.373,7.603c-10.137-10.137-26.632-10.138-36.77,0c-10.138,10.138-10.137,26.632,0,36.77s26.632,10.138,36.77,0 C54.51,34.235,54.51,17.74,44.373,7.603z M36.241,36.241c-0.781,0.781-2.047,0.781-2.828,0l-7.425-7.425l-7.778,7.778 c-0.781,0.781-2.047,0.781-2.828,0c-0.781-0.781-0.781-2.047,0-2.828l7.778-7.778l-7.425-7.425c-0.781-0.781-0.781-2.048,0-2.828 c0.781-0.781,2.047-0.781,2.828,0l7.425,7.425l7.071-7.071c0.781-0.781,2.047-0.781,2.828,0c0.781,0.781,0.781,2.047,0,2.828 l-7.071,7.071l7.425,7.425C37.022,34.194,37.022,35.46,36.241,36.241z"/>
92
+ </svg>
93
+ </div>
94
+ <div class="icon icon-working">
95
+ <svg viewBox="0 -8 72 72" xmlns="http://www.w3.org/2000/svg">
96
+ <path d="M56.74,20.89l-1-2.31c3.33-7.53,3.11-7.75,2.46-8.41L54,6l-.42-.35h-.49c-.26,0-1,0-7.51,2.93l-2.38-1C40.09,0,39.77,0,38.87,0h-6c-.9,0-1.25,0-4.1,7.66l-2.37,1C22,6.78,19.45,5.84,18.75,5.84l-.56,0-4.58,4.49c-.7.65-.94.88,2.58,8.3l-1,2.3c-7.79,3-7.79,3.3-7.79,4.23v5.89c0,.92,0,1.25,7.82,4l1,2.29c-3.33,7.53-3.11,7.76-2.46,8.41L18,50l.42.37h.5c.25,0,1,0,7.5-3l2.38,1C31.9,56,32.21,56,33.12,56h6c.92,0,1.25,0,4.11-7.66l2.39-1c4.37,1.85,6.93,2.79,7.61,2.79l.57,0,4.62-4.52c.66-.66.89-.89-2.62-8.28l1-2.3c7.81-3,7.81-3.33,7.81-4.23V24.93C64.57,24,64.57,23.68,56.74,20.89ZM36,37.8A9.8,9.8,0,1,1,46,28,9.91,9.91,0,0,1,36,37.8Z"/>
97
+ </svg>
98
+ </div>
99
+ <div class="icon icon-ok">
100
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 490.05 490.05" xml:space="preserve">
101
+ <g>
102
+ <path d="M418.275,418.275c95.7-95.7,95.7-250.8,0-346.5s-250.8-95.7-346.5,0s-95.7,250.8,0,346.5S322.675,513.975,418.275,418.275
103
+ z M157.175,207.575l55.1,55.1l120.7-120.6l42.7,42.7l-120.6,120.6l-42.8,42.7l-42.7-42.7l-55.1-55.1L157.175,207.575z"/>
104
+ </g>
105
+ </svg>
106
+ </div>
107
+ <div class="message"></div>
108
+ </div>
109
+ <%= render partial: "shared/caixanegra/js_dependencies" %>
@@ -0,0 +1,16 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Caixanegra</title>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <%= csrf_meta_tags %>
7
+ <%= csp_meta_tag %>
8
+
9
+ <%= stylesheet_link_tag "caixanegra/application", media: "all" %>
10
+ </head>
11
+ <body>
12
+
13
+ <%= yield %>
14
+
15
+ </body>
16
+ </html>
@@ -0,0 +1,4 @@
1
+ <%= javascript_include_tag("caixanegra/caixanegra.js") %>
2
+ <%= javascript_include_tag("caixanegra/api.js") %>
3
+ <%= javascript_include_tag("caixanegra/sabertooth.js") %>
4
+ <%= javascript_include_tag("caixanegra/designer.js") %>
data/config/routes.rb ADDED
@@ -0,0 +1,14 @@
1
+ Caixanegra::Engine.routes.draw do
2
+ get '/design/:id', to: 'designer#index'
3
+
4
+ namespace :api, constraints: { format: :json } do
5
+ namespace :designer do
6
+ resources :units, only: %i[index]
7
+ resources :flows, only: %i[show update] do
8
+ member do
9
+ patch :debug_run
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caixanegra
4
+ self.mattr_accessor :units
5
+ self.mattr_accessor :redis
6
+ self.units = []
7
+
8
+ def self.setup(&block)
9
+ yield self
10
+ end
11
+
12
+ class Engine < ::Rails::Engine
13
+ isolate_namespace Caixanegra
14
+
15
+ initializer "caixanegra.assets.precompile" do |app|
16
+ app.config.assets.precompile += %w[
17
+ caixanegra/api.js
18
+ caixanegra/caixanegra.js
19
+ caixanegra/designer.js
20
+ caixanegra/sabertooth.js
21
+ caixanegra/application.css
22
+ ]
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caixanegra
4
+ class UnitScopedException < StandardError; end
5
+ end
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caixanegra
4
+ class Executor
5
+ def initialize(params)
6
+ @initial_carryover = params[:initial_carryover] || {}
7
+ @flow = (params[:flow_definition] || {}).deep_symbolize_keys
8
+ @unit_scope = params[:unit_scope]
9
+ @debug_mode = params[:debug_mode] == true
10
+ @execution = { history: [], steps: [] }
11
+ @storage = {}
12
+ end
13
+
14
+ def run
15
+ run_and_report_result
16
+ end
17
+
18
+ private
19
+
20
+ def format_result(result)
21
+ return result unless @debug_mode
22
+
23
+ {
24
+ result: result,
25
+ debug: @execution
26
+ }
27
+ end
28
+
29
+ def run_and_report_result
30
+ log_console_entry "Started"
31
+ set_start_unit
32
+ format_result(flow_through)
33
+ rescue Caixanegra::UnitScopedException => e
34
+ log_step_exception(e)
35
+ log_console_entry("Unit error: #{e.message}")
36
+ format_result(e.message)
37
+ rescue => e
38
+ log_console_entry("Error: #{e.message}")
39
+ format_result(e.message)
40
+ end
41
+
42
+ def log_console_entry(message)
43
+ return unless @debug_mode
44
+
45
+ @execution[:history] << {
46
+ timestamp: Time.current.to_i,
47
+ message: message
48
+ }
49
+ end
50
+
51
+ def log_new_step(target_unit = @step_unit)
52
+ return unless @debug_mode
53
+
54
+ @execution[:steps] << {
55
+ oid: target_unit.oid,
56
+ in: {
57
+ timestamp: Time.current.to_i,
58
+ carry_over: target_unit.current_carry_over,
59
+ storage: target_unit.current_storage
60
+ }
61
+ }
62
+ end
63
+
64
+ def log_feeder_step(result, feeder)
65
+ return unless @debug_mode
66
+
67
+ {
68
+ oid: feeder.oid,
69
+ in: {
70
+ timestamp: Time.current.to_i,
71
+ carry_over: feeder.current_carry_over,
72
+ storage: feeder.current_storage
73
+ },
74
+ out: {
75
+ timestamp: Time.current.to_i,
76
+ result: result,
77
+ target_unit: @step_unit&.oid
78
+ }
79
+ }
80
+ end
81
+
82
+ def log_step_exception(exception)
83
+ return unless @debug_mode
84
+
85
+ @execution[:steps].last[:exception] = {
86
+ timestamp: Time.current.to_i,
87
+ message: exception.message,
88
+ backtrace: exception.backtrace
89
+ }
90
+ end
91
+
92
+ def log_step_result(result, next_unit)
93
+ return unless @debug_mode
94
+
95
+ @execution[:steps].last[:out] = {
96
+ timestamp: Time.current.to_i,
97
+ result: result,
98
+ target_unit: next_unit&.oid
99
+ }
100
+ end
101
+
102
+ def flow_through
103
+ while @step_unit.exits.size.nonzero?
104
+ feeder_steps = process_feeders
105
+ result = begin
106
+ log_console_entry("Flowing #{@step_unit.oid}")
107
+ result = @step_unit.flow
108
+ @storage.merge! @step_unit.current_storage
109
+ result
110
+ rescue => e
111
+ exception = Caixanegra::UnitScopedException.new(e)
112
+ exception.set_backtrace(e.backtrace)
113
+ raise exception
114
+ end
115
+ next_unit = next_unit(result)
116
+ log_step_result(result, next_unit)
117
+ @execution[:steps] += feeder_steps
118
+ @step_unit = next_unit
119
+ log_new_step
120
+ end
121
+
122
+ log_console_entry("Reached terminator #{@step_unit.oid}")
123
+
124
+ @step_unit.flow[:carry_over]
125
+ end
126
+
127
+ def map_carry_over(result, target_step: @step_unit)
128
+ mapped_carry_over = {}
129
+ exit_name = result[:exit_through]
130
+ carry_over = result[:carry_over].deep_symbolize_keys
131
+ metadata = unit_metadata(target_step.oid)
132
+ mapped_carry_over.merge!(carry_over) if metadata[:type] == 'passthrough'
133
+ exit_metadata = metadata[:exits].find { |ex| ex[:name] == exit_name.to_s }
134
+ (exit_metadata[:mappings] || []).each do |mapping|
135
+ next if mapping[:use].blank? || mapping[:as].blank?
136
+
137
+ mapped_carry_over[mapping[:as].to_sym] = carry_over.dig(
138
+ *(mapping[:use] || '').split('.').map(&:to_sym)
139
+ )
140
+ end
141
+
142
+ mapped_carry_over
143
+ end
144
+
145
+ def next_unit(result)
146
+ exit_name = result[:exit_through]
147
+ metadata = unit_metadata(@step_unit.oid)
148
+ log_console_entry "Next unit found through '#{exit_name}': '#{@step_unit.oid}'"
149
+ exit_metadata = metadata[:exits].find { |ex| ex[:name] == exit_name.to_s }
150
+ unit(exit_metadata[:target], map_carry_over(result))
151
+ end
152
+
153
+ def process_feeders
154
+ feeder_hits = []
155
+ carry_over = {}
156
+ @flow[:units].filter do |unit|
157
+ unit[:type] == 'feeder' && (unit[:exits] || []).any? { |ex| ex[:target] == @step_unit.oid }
158
+ end.each do |feeder|
159
+ feeder_instance = unit_instance(feeder)
160
+ log_console_entry "Flowing feeder '#{feeder_instance.oid}'"
161
+ result = feeder_instance.flow
162
+ carry_over.merge! map_carry_over(result, target_step: feeder_instance)
163
+ feeder_hits << log_feeder_step(result, feeder_instance)
164
+ end
165
+ @step_unit.carry_over(carry_over)
166
+
167
+ feeder_hits
168
+ end
169
+
170
+ def unit_metadata(oid)
171
+ @flow[:units].find { |u| u[:oid] == oid }
172
+ end
173
+
174
+ def set_start_unit
175
+ @step_unit = unit(@flow[:entrypoint])
176
+ log_console_entry "Start unit set: #{@step_unit&.oid || 'none'}"
177
+ @step_unit.carry_over(@initial_carryover)
178
+ log_new_step
179
+ end
180
+
181
+ def unit(oid, carry_over = {})
182
+ unit_instance(unit_metadata(oid), carry_over)
183
+ end
184
+
185
+ def unit_instance(unit_data, carry_over = {})
186
+ mappings = unit_data[:mappings] || {}
187
+ unit_class = scoped_units[unit_data[:class].to_sym]
188
+ inputs = unit_class.inputs
189
+ unit_class.new(unit_data[:oid], inputs, mappings, carry_over, @storage)
190
+ end
191
+
192
+ def scoped_units
193
+ @scoped_units ||= begin
194
+ base_units = Caixanegra.units.reject { |_, v| v.is_a? Hash }
195
+ scope_units = Caixanegra.units[@unit_scope] if @unit_scope.present?
196
+
197
+ base_units.merge(scope_units || {})
198
+ end
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caixanegra
4
+ class Manager
5
+ class << self
6
+ def handler(flow_definition = {})
7
+ uid = SecureRandom.uuid.gsub("-", "")
8
+
9
+ Caixanegra.redis.multi do |pipeline|
10
+ pipeline.hset(:caixanegra, uid, JSON.dump(flow_definition))
11
+ pipeline.expire(:caixanegra, 1.hour)
12
+ end
13
+
14
+ uid
15
+ end
16
+
17
+ def destroy
18
+ Caixanegra.redis.hdel(:caixanegra, key)
19
+ end
20
+
21
+ def get(uid)
22
+ value = Caixanegra.redis.hget(:caixanegra, uid)
23
+
24
+ JSON.parse(value) if value.present?
25
+ end
26
+
27
+ def set(uid, flow_definition)
28
+ Caixanegra.redis.hset(:caixanegra, uid, JSON.dump(flow_definition))
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,3 @@
1
+ module Caixanegra
2
+ VERSION = '0.1.0'
3
+ end
data/lib/caixanegra.rb ADDED
@@ -0,0 +1,8 @@
1
+ require "caixanegra/exceptions"
2
+ require "caixanegra/engine"
3
+ require "caixanegra/version"
4
+ require "caixanegra/executor"
5
+ require "caixanegra/manager"
6
+
7
+ module Caixanegra
8
+ end