flor 0.17.0 → 0.18.0
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/CHANGELOG.md +24 -0
- data/README.md +19 -1
- data/flor.gemspec +7 -7
- data/lib/flor.rb +1 -1
- data/lib/flor/core/executor.rb +5 -6
- data/lib/flor/core/procedure.rb +47 -11
- data/lib/flor/flor.rb +1 -3
- data/lib/flor/log.rb +6 -0
- data/lib/flor/pcore/_dmute.rb +4 -1
- data/lib/flor/pcore/break.rb +12 -1
- data/lib/flor/pcore/on.rb +30 -6
- data/lib/flor/pcore/sequence.rb +48 -0
- data/lib/flor/punit/cancel.rb +9 -1
- data/lib/flor/punit/signal.rb +29 -5
- data/lib/flor/punit/task.rb +8 -2
- data/lib/flor/punit/trap.rb +31 -0
- data/lib/flor/unit/executor.rb +9 -5
- data/lib/flor/unit/ganger.rb +21 -6
- data/lib/flor/unit/models.rb +40 -9
- data/lib/flor/unit/models/trap.rb +12 -4
- data/lib/flor/unit/scheduler.rb +186 -51
- data/lib/flor/unit/storage.rb +20 -5
- data/lib/flor/unit/taskers.rb +46 -1
- metadata +13 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b74ff25bb24e46910557e440ddedc83d07730ec5
|
4
|
+
data.tar.gz: cc0d4de7df8c9a3e39879dfd8968ae70230310c7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b2085152306c18636cbd64e9e5d3bc140e6d3b19f2d8afc4111d5283e9da56328a7fc09ab5c2db5fa14f429f3f5f858b87c67c3c6defcb790cb34042608871cb
|
7
|
+
data.tar.gz: 52c344d0deb517f025eb7491201175d44356257c7fae1cad36e8f0a7006a60ec10a3829e0bbc1e7cae2781a0a2ad434e6e84f7499ef9cac94cb0ca095df91643
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,30 @@
|
|
2
2
|
# CHANGELOG.md
|
3
3
|
|
4
4
|
|
5
|
+
## flor 0.18.0 released 2019-05-05
|
6
|
+
|
7
|
+
* Refine BasicTasker#reply (more arg patterns)
|
8
|
+
* Fix "signal" vs exid: and payload:
|
9
|
+
* Make payload optional when cancelling
|
10
|
+
* Unlock `signal exid: other_execution_id "xxx"`
|
11
|
+
* Allow for `trap 'signal0' payload: { a: 'A' }`
|
12
|
+
* Allow for "on" in blocking mode (no block given)
|
13
|
+
* Turn "sequence" single string att results to tags
|
14
|
+
* gh-26, refine cancel / on_cancel and payload return
|
15
|
+
* Allow for custom :schema_info migration table
|
16
|
+
* Introduce a dedicated #refresh for all flor models
|
17
|
+
* Let scheduler sleep only 0.001s if @idle_count less than 1
|
18
|
+
* Implement Scheduler #dump and #load
|
19
|
+
* Default target #cancel and #kill to node '0'
|
20
|
+
* Expose taskname to tasker on detasking (@Subtletree)
|
21
|
+
* Refine BasicTasker#reply (more arg patterns)
|
22
|
+
* Allow for `trap 'signal0' payload: { a: 'A' }`
|
23
|
+
* Allow for "on" in blocking mode (no block given)
|
24
|
+
* Unlock `signal exid: other_execution_id "xxx"`
|
25
|
+
* Make payload optional when cancelling (default to payload as it was
|
26
|
+
upon reaching the cancelled node)
|
27
|
+
|
28
|
+
|
5
29
|
## flor 0.17.0 released 2019-04-08
|
6
30
|
|
7
31
|
- Switch to 0.17.x
|
data/README.md
CHANGED
@@ -108,13 +108,31 @@ See [doc/](doc/).
|
|
108
108
|
|
109
109
|
## blog posts and presentations
|
110
110
|
|
111
|
-
* [
|
111
|
+
* [flor workflow engine](http://jmettraux.skepti.ch/20190407.html?t=flor_workflow_engine&f=readme) - on flor itself
|
112
|
+
* [the flor language](http://jmettraux.skepti.ch/20180927.html?t=the_flor_language&f=readme) - on the flor workflow definition language itself
|
113
|
+
* [reddit answer on workflow engines](http://jmettraux.skepti.ch/20190416.html?t=reddit_answer_on_workflow_engines&f=readme) - an answer to a Reddit question on workflow engines, archived as a post
|
112
114
|
* [Flor, hubristic interpreter](http://rubykaigi.org/2017/presentations/jmettraux.html) - RubyKaigi 2017, Hiroshima - presentation
|
113
115
|
* [flor design 0](http://jmettraux.skepti.ch/20171021.html?t=flor_design_0&f=readme) - running a simple execution, what happens - blog post
|
114
116
|
* [flor, branch to branch](https://speakerdeck.com/jmettraux/flor-branch-to-branch) - q1 2017 - very dry deck
|
115
117
|
* [flor 2017](https://speakerdeck.com/jmettraux/flor-2017) - q1 2017 - very dry deck
|
116
118
|
|
117
119
|
|
120
|
+
## other Ruby projects about workflows
|
121
|
+
|
122
|
+
There are various other Ruby and Ruby on Rails projects about workflows and business processes, each with its own take on them.
|
123
|
+
|
124
|
+
* [Dynflow](http://dynflow.github.io/) - "Dynflow (DYNamic workFLOW) is a workflow engine written in Ruby"
|
125
|
+
* [rails_workflow](https://github.com/madzhuga/rails_workflow) - "Rails Workflow Engine allows you to organize your application business logic by joining user- and auto- operations in processes"
|
126
|
+
* [rails_engine/workflow_core](https://github.com/rails-engine/workflow_core) - "A Rails engine which providing essential infrastructure of workflow. It's based on Workflow Nets"
|
127
|
+
* [Trailblazer](http://trailblazer.to/) - "The Advanced Business Logic Framework"
|
128
|
+
|
129
|
+
There is a [workflow engine](https://ruby.libhunt.com/categories/5786-workflow-engine) category on [Awesome Ruby](https://ruby.libhunt.com/).
|
130
|
+
|
131
|
+
If you want your engine/library to be added in this list, don't hesitate to ask me on [Gitter](https://gitter.im/floraison/flor) or via a pull request.
|
132
|
+
|
133
|
+
It's not limited to Ruby, but there is a wider list at [meirwah/awesome-workflow-engines](https://github.com/meirwah/awesome-workflow-engines).
|
134
|
+
|
135
|
+
|
118
136
|
## license
|
119
137
|
|
120
138
|
MIT, see [LICENSE.txt](LICENSE.txt)
|
data/flor.gemspec
CHANGED
@@ -38,16 +38,16 @@ A Ruby workflow engine (ruote next generation)
|
|
38
38
|
"#{s.name}.gemspec",
|
39
39
|
]
|
40
40
|
|
41
|
-
s.add_runtime_dependency 'munemo', '~> 1.0', '>= 1.0.1'
|
42
|
-
s.add_runtime_dependency 'raabro', '~> 1.1', '>= 1.1.5'
|
43
41
|
#s.add_runtime_dependency 'rufus-lru', '~> 1.1'
|
44
|
-
s.add_runtime_dependency '
|
45
|
-
s.add_runtime_dependency '
|
42
|
+
s.add_runtime_dependency 'munemo', '~> 1.0' # >= 1.0 and < 2
|
43
|
+
s.add_runtime_dependency 'raabro', '~> 1.1' # >= 1.1 and < 2
|
44
|
+
s.add_runtime_dependency 'fugit', '~> 1.2' # >= 1.2 and < 2
|
45
|
+
s.add_runtime_dependency 'dense', '~> 1.1' # >= 1.1 and < 2
|
46
46
|
|
47
|
-
s.add_runtime_dependency 'sequel', '~> 5'
|
47
|
+
s.add_runtime_dependency 'sequel', '~> 5.0' # >= 5.0 and < 6
|
48
48
|
|
49
|
-
s.add_development_dependency 'rspec', '~> 3.
|
50
|
-
s.add_development_dependency 'terminal-table'
|
49
|
+
s.add_development_dependency 'rspec', '~> 3.8' # >= 3.8 and < 4
|
50
|
+
s.add_development_dependency 'terminal-table', '~> 1.8' # >= 1.8 and < 2
|
51
51
|
|
52
52
|
s.require_path = 'lib'
|
53
53
|
end
|
data/lib/flor.rb
CHANGED
data/lib/flor/core/executor.rb
CHANGED
@@ -419,15 +419,14 @@ end
|
|
419
419
|
|
420
420
|
last = (message['cause'] ||= [])[0]
|
421
421
|
|
422
|
-
c = {
|
423
|
-
|
424
|
-
'm' => message['m'],
|
425
|
-
'nid' => message['nid'],
|
426
|
-
'type' => message['type'],
|
427
|
-
'at' => last && last['at'] }
|
422
|
+
c = { 'cause' => cause, 'at' => last && last['at'] }
|
423
|
+
%w[ m sm nid type ].each { |k| c[k] = message[k] }
|
428
424
|
|
429
425
|
return if c == last
|
430
426
|
|
427
|
+
# argh, the causes in the messages go most recent first
|
428
|
+
# while the statuses in the nodes go most recent last
|
429
|
+
|
431
430
|
message['cause'] =
|
432
431
|
[ c.tap { |h| h['at'] = Flor.tstamp } ] +
|
433
432
|
message['cause']
|
data/lib/flor/core/procedure.rb
CHANGED
@@ -386,18 +386,30 @@ class Flor::Procedure < Flor::Node
|
|
386
386
|
end
|
387
387
|
end
|
388
388
|
|
389
|
+
# From the incoming message, return the most recent cause for this node
|
390
|
+
#
|
389
391
|
def message_cause
|
390
392
|
|
391
393
|
(@message['cause'] || [])
|
392
394
|
.find { |c| c['nid'] == nid }
|
393
395
|
end
|
394
396
|
|
397
|
+
# Given the current node status and the incoming message, returns the
|
398
|
+
# upstream cause that lead to the status.
|
399
|
+
# Returns nil if the incoming message is not related to the current status.
|
400
|
+
#
|
401
|
+
def status_cause
|
402
|
+
|
403
|
+
m = node_status['m']
|
404
|
+
|
405
|
+
(@message['cause'] || []).find { |c| c['m'] == m }
|
406
|
+
end
|
407
|
+
|
395
408
|
def pop_on_receive_last
|
396
409
|
|
397
410
|
orl = @node['on_receive_last']
|
398
411
|
|
399
|
-
return nil
|
400
|
-
return nil if orl.empty?
|
412
|
+
return nil if orl.nil? || orl.empty?
|
401
413
|
|
402
414
|
c = message_cause
|
403
415
|
|
@@ -411,15 +423,16 @@ class Flor::Procedure < Flor::Node
|
|
411
423
|
|
412
424
|
@node['mtime'] = Flor.tstamp
|
413
425
|
|
414
|
-
orl
|
415
|
-
|
416
|
-
m['from'] = @node['parent'] if m['from'] == 'parent'
|
426
|
+
orl
|
427
|
+
.each do |m|
|
417
428
|
|
418
|
-
|
419
|
-
|
420
|
-
end
|
429
|
+
m['from'] = @node['parent'] \
|
430
|
+
if m['from'] == 'parent'
|
421
431
|
|
422
|
-
|
432
|
+
m['payload'] =
|
433
|
+
m.delete('force_payload') ||
|
434
|
+
message['payload']
|
435
|
+
end
|
423
436
|
end
|
424
437
|
|
425
438
|
def receive_from_child_when_closed
|
@@ -447,8 +460,21 @@ class Flor::Procedure < Flor::Node
|
|
447
460
|
|
448
461
|
@fcid &&
|
449
462
|
Flor.same_branch?(nid, from) &&
|
450
|
-
(c = children[@fcid]) &&
|
451
|
-
|
463
|
+
(c = children[@fcid]) && c[0] == '_att'
|
464
|
+
end
|
465
|
+
|
466
|
+
def from_keyed_att?
|
467
|
+
|
468
|
+
@fcid &&
|
469
|
+
Flor.same_branch?(nid, from) &&
|
470
|
+
(c = children[@fcid]) && c[0] == '_att' && c[1].size == 2
|
471
|
+
end
|
472
|
+
|
473
|
+
def from_unkeyed_att?
|
474
|
+
|
475
|
+
@fcid &&
|
476
|
+
Flor.same_branch?(nid, from) &&
|
477
|
+
(c = children[@fcid]) && c[0] == '_att' && c[1].size == 1
|
452
478
|
end
|
453
479
|
|
454
480
|
def from_sub_nid
|
@@ -828,6 +854,16 @@ class Flor::Procedure < Flor::Node
|
|
828
854
|
#
|
829
855
|
def do_cancel
|
830
856
|
|
857
|
+
@message['payload'] ||= Flor.dup(@node['payload'])
|
858
|
+
#
|
859
|
+
# Ensure the 'cancel' message has a payload.
|
860
|
+
# If not, let's use the payload as it was upon reaching this node.
|
861
|
+
|
862
|
+
return wrap if node_closed? && status_cause
|
863
|
+
#
|
864
|
+
# if the node is cancelled and that is caused by a message upstream
|
865
|
+
# that caused the incoming message, simply reply immediately...
|
866
|
+
|
831
867
|
if orl = @message['on_receive_last']
|
832
868
|
#
|
833
869
|
# the message on_receive_last is used by the re_apply feature
|
data/lib/flor/flor.rb
CHANGED
data/lib/flor/log.rb
CHANGED
@@ -106,6 +106,12 @@ module Flor
|
|
106
106
|
a << " #{_c.dg}#{k}:#{m[k]}" if m.has_key?(k)
|
107
107
|
end
|
108
108
|
|
109
|
+
#a << " #{_c.dim}#{_c.dg}"
|
110
|
+
#a << "pl#{Flor.to_d(m['payload'], compact: true, colors: false)}"
|
111
|
+
#
|
112
|
+
# keep that around for when debugging payload shifts
|
113
|
+
# eventually consider FLOR_DEBUG flag
|
114
|
+
|
109
115
|
a << _c.rs
|
110
116
|
|
111
117
|
a.join
|
data/lib/flor/pcore/_dmute.rb
CHANGED
@@ -12,7 +12,10 @@ class Flor::Pro::Dmute < Flor::Procedure
|
|
12
12
|
'from' => Flor.child_nid(nid, 999 + tree[1].size),
|
13
13
|
# "#{nid}_#{999 + child.count}"
|
14
14
|
'exid' => exid,
|
15
|
-
'
|
15
|
+
'force_payload' => @message['payload'].merge('ret' => '') } ] ]
|
16
|
+
#
|
17
|
+
# using 'force_payload' to tell Procedure#pop_on_receive_last
|
18
|
+
# to use this payload
|
16
19
|
end
|
17
20
|
end
|
18
21
|
|
data/lib/flor/pcore/break.rb
CHANGED
@@ -59,7 +59,8 @@ class Flor::Pro::Break < Flor::Procedure
|
|
59
59
|
def receive_last
|
60
60
|
|
61
61
|
ref = att('ref')
|
62
|
-
nid = tags_to_nids(ref).first ||
|
62
|
+
nid = tags_to_nids(ref).first || target_nid
|
63
|
+
# nid derived from tag or target nid given by parent "cursor"
|
63
64
|
|
64
65
|
payload['ret'] = att(nil) if has_att?(nil)
|
65
66
|
|
@@ -80,5 +81,15 @@ class Flor::Pro::Break < Flor::Procedure
|
|
80
81
|
|
81
82
|
ms
|
82
83
|
end
|
84
|
+
|
85
|
+
protected
|
86
|
+
|
87
|
+
def target_nid
|
88
|
+
|
89
|
+
# this nid is given by the parent "cursor" via a "break" or "continue"
|
90
|
+
# variable
|
91
|
+
|
92
|
+
@node['heat'][1]['nid']
|
93
|
+
end
|
83
94
|
end
|
84
95
|
|
data/lib/flor/pcore/on.rb
CHANGED
@@ -100,6 +100,20 @@ class Flor::Pro::On < Flor::Macro
|
|
100
100
|
# If it were, it would trap the signal named "timeout".
|
101
101
|
#
|
102
102
|
#
|
103
|
+
# ## blocking mode
|
104
|
+
#
|
105
|
+
# When "on" is given no code block, it will block.
|
106
|
+
# ```
|
107
|
+
# sequence
|
108
|
+
# # ...
|
109
|
+
# on 'green' # execution (branch) blocks here until signal 'green' comes
|
110
|
+
# # ...
|
111
|
+
# ```
|
112
|
+
#
|
113
|
+
# Behind the scenes, it simply rewrites the "on" to a "trap" without a
|
114
|
+
# function, a blocking trap.
|
115
|
+
#
|
116
|
+
#
|
103
117
|
# ## see also
|
104
118
|
#
|
105
119
|
# Trap and signal.
|
@@ -166,16 +180,26 @@ class Flor::Pro::On < Flor::Macro
|
|
166
180
|
l = tree[2]
|
167
181
|
|
168
182
|
th = [ 'trap', [], l, *tree[3] ]
|
183
|
+
|
169
184
|
th[1] << [ '_att', [ [ 'point', [], l ], [ '_sqs', 'signal', l ] ], l ]
|
170
185
|
th[1] << [ '_att', [ [ 'name', [], l ], tname ], l ]
|
171
|
-
th[1] << [ '_att', [ [ 'payload', [], l ], [ '_sqs', 'event', l ] ], l ]
|
172
|
-
atts.each { |ac| th[1] << Flor.dup(ac) }
|
173
186
|
|
174
|
-
|
175
|
-
|
176
|
-
|
187
|
+
att_names = []
|
188
|
+
atts.each { |at|
|
189
|
+
att_names << at[1][0][0]
|
190
|
+
th[1] << Flor.dup(at) }
|
177
191
|
|
178
|
-
th[1] <<
|
192
|
+
th[1] << [ '_att', [ [ 'payload', [], l ], [ '_sqs', 'event', l ] ], l ] \
|
193
|
+
unless att_names.include?('payload')
|
194
|
+
|
195
|
+
if (nac = non_att_children).any?
|
196
|
+
|
197
|
+
td = [ 'def', [], l ]
|
198
|
+
td[1] << [ '_att', [ [ 'msg', [], l ] ], l ]
|
199
|
+
non_att_children.each { |nac| td[1] << Flor.dup(nac) }
|
200
|
+
|
201
|
+
th[1] << td
|
202
|
+
end
|
179
203
|
|
180
204
|
th
|
181
205
|
end
|
data/lib/flor/pcore/sequence.rb
CHANGED
@@ -9,14 +9,62 @@ class Flor::Pro::Sequence < Flor::Procedure
|
|
9
9
|
# task 'bravo' if f.amount > 2000
|
10
10
|
# task 'charly'
|
11
11
|
# ```
|
12
|
+
#
|
13
|
+
# Giving a string as attribute result to "sequence" lets it interpret
|
14
|
+
# that string as a tag name, as in:
|
15
|
+
# ```
|
16
|
+
# sequence 'phase one'
|
17
|
+
# alice 'gather customer requirements'
|
18
|
+
# bob 'establish offer'
|
19
|
+
# sequence 'phase two'
|
20
|
+
# alice 'present offer to customer'
|
21
|
+
# bob 'sign contract'
|
22
|
+
# ```
|
23
|
+
# It is equivalent to:
|
24
|
+
# ```
|
25
|
+
# sequence tag: 'phase one'
|
26
|
+
# alice 'gather customer requirements'
|
27
|
+
# bob 'establish offer'
|
28
|
+
# sequence tag: 'phase two'
|
29
|
+
# alice 'present offer to customer'
|
30
|
+
# bob 'sign contract'
|
31
|
+
# ```
|
32
|
+
# Learn more about [tags](../tags.md).
|
33
|
+
#
|
34
|
+
# Please note that it sets only 1 tag, and if there are already tags
|
35
|
+
# sets (`sequence tags: [ 'a' 'b' ] "this won't become a tag"`), it won't set
|
36
|
+
# further tags.
|
37
|
+
#
|
38
|
+
# ## see also
|
39
|
+
#
|
40
|
+
# Concurrence, loop
|
12
41
|
|
13
42
|
names %w[ sequence begin ]
|
14
43
|
|
44
|
+
def receive_att
|
45
|
+
|
46
|
+
ms = []
|
47
|
+
|
48
|
+
ms = enter_tag \
|
49
|
+
if @node['tags'].nil? && payload_ret.is_a?(String) && from_unkeyed_att?
|
50
|
+
|
51
|
+
ms + super
|
52
|
+
end
|
53
|
+
|
15
54
|
def cancel_when_closed
|
16
55
|
|
17
56
|
return cancel if node_status_flavour == 'on-error'
|
18
57
|
|
19
58
|
[]
|
20
59
|
end
|
60
|
+
|
61
|
+
protected
|
62
|
+
|
63
|
+
def enter_tag
|
64
|
+
|
65
|
+
@node['tags'] = tags = [ payload_ret ]
|
66
|
+
|
67
|
+
wrap('point' => 'entered', 'nid' => nid, 'tags' => tags)
|
68
|
+
end
|
21
69
|
end
|
22
70
|
|
data/lib/flor/punit/cancel.rb
CHANGED
@@ -29,7 +29,15 @@ class Flor::Pro::Cancel < Flor::Procedure
|
|
29
29
|
# ```
|
30
30
|
# But it's kind of brittle compared to using tags.
|
31
31
|
#
|
32
|
-
#
|
32
|
+
# ## kill
|
33
|
+
#
|
34
|
+
# "kill" is equivalent to "cancel", but once called, cancel handlers are
|
35
|
+
# ignored, it cancels through.
|
36
|
+
#
|
37
|
+
#
|
38
|
+
# ## see also
|
39
|
+
#
|
40
|
+
# On_cancel, on
|
33
41
|
|
34
42
|
name 'cancel', 'kill'
|
35
43
|
# ruote had "undo" as well...
|
data/lib/flor/punit/signal.rb
CHANGED
@@ -51,6 +51,26 @@ class Flor::Pro::Signal < Flor::Procedure
|
|
51
51
|
# flor_unit.signal('close', exid: execution_id, payload: { 'f0' => 'zero' })
|
52
52
|
# ```
|
53
53
|
#
|
54
|
+
# ## signalling another execution
|
55
|
+
#
|
56
|
+
# If the exid of execution A is known in execution B, execution B can
|
57
|
+
# send a signal to A.
|
58
|
+
#
|
59
|
+
# ```ruby
|
60
|
+
# exid0 = @unit.launch(
|
61
|
+
# %q{
|
62
|
+
# trap signal: 'green' # block until receiving the 'green' signal
|
63
|
+
# set f.done true
|
64
|
+
# })
|
65
|
+
# @unit.launch(
|
66
|
+
# %q{
|
67
|
+
# set f.a 'a'
|
68
|
+
# signal exid: f.exid0 'green' # send 'green' signal to exid0
|
69
|
+
# set f.b 'b'
|
70
|
+
# },
|
71
|
+
# payload: { 'exid0' => exid0 })
|
72
|
+
# ```
|
73
|
+
#
|
54
74
|
# ## see also
|
55
75
|
#
|
56
76
|
# On and trap.
|
@@ -64,14 +84,18 @@ class Flor::Pro::Signal < Flor::Procedure
|
|
64
84
|
|
65
85
|
def receive_last
|
66
86
|
|
67
|
-
|
87
|
+
na = att('name', nil)
|
88
|
+
ei = att('exid') || exid
|
89
|
+
pl = att('payload') || payload.copy_current
|
68
90
|
|
69
|
-
return super unless
|
91
|
+
return super unless na
|
70
92
|
|
71
93
|
wrap(
|
72
|
-
'point' => 'signal',
|
73
|
-
'
|
74
|
-
|
94
|
+
'point' => 'signal',
|
95
|
+
'exid' => ei, 'nid' => nid,
|
96
|
+
'name' => na, 'payload' => pl
|
97
|
+
) +
|
98
|
+
super
|
75
99
|
end
|
76
100
|
end
|
77
101
|
|