flor 0.17.0 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bf01d8760a78e903218fef2a040fa42551d7273e
4
- data.tar.gz: 402f89f60de25ebe27a11c16e3e34fdbd3e8b854
3
+ metadata.gz: b74ff25bb24e46910557e440ddedc83d07730ec5
4
+ data.tar.gz: cc0d4de7df8c9a3e39879dfd8968ae70230310c7
5
5
  SHA512:
6
- metadata.gz: 1132dd0609803767926d6546549d5285a0197837dd512ea2980ce03b8be23c76e43016b3a63c36b4d43fd7bd8a7c055bc1085c482b659ea69fa2da1fa94f894e
7
- data.tar.gz: 59498ec443e22d08d01f8708a19dcf7b1b4b41a179e807fa0b6225d10f5642e2a3ab61bb04624c3ad80d9b0b2378d6c4b2b71651bb67bc0c25de5b22e0395120
6
+ metadata.gz: b2085152306c18636cbd64e9e5d3bc140e6d3b19f2d8afc4111d5283e9da56328a7fc09ab5c2db5fa14f429f3f5f858b87c67c3c6defcb790cb34042608871cb
7
+ data.tar.gz: 52c344d0deb517f025eb7491201175d44356257c7fae1cad36e8f0a7006a60ec10a3829e0bbc1e7cae2781a0a2ad434e6e84f7499ef9cac94cb0ca095df91643
@@ -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
- * [the flor language](http://jmettraux.skepti.ch/20180927.html?t=the_flor_language) - on the flor workflow definition language itself
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)
@@ -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 'fugit', '~> 1.1', '>= 1.1.8'
45
- s.add_runtime_dependency 'dense', '~> 1.1', '>= 1.1.6'
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.7'
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
@@ -14,7 +14,7 @@ require 'dense'
14
14
 
15
15
  module Flor
16
16
 
17
- VERSION = '0.17.0'
17
+ VERSION = '0.18.0'
18
18
  #VERSION = '1.0.0'
19
19
  end
20
20
 
@@ -419,15 +419,14 @@ end
419
419
 
420
420
  last = (message['cause'] ||= [])[0]
421
421
 
422
- c = {
423
- 'cause' => cause,
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']
@@ -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 unless orl
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.each do |m|
415
-
416
- m['from'] = @node['parent'] if m['from'] == 'parent'
426
+ orl
427
+ .each do |m|
417
428
 
418
- #m['payload'] ||= Flor.dup(@node['payload'])
419
- # No, let re_applier supply payload
420
- end
429
+ m['from'] = @node['parent'] \
430
+ if m['from'] == 'parent'
421
431
 
422
- orl
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
- c[0] == '_att'
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
@@ -616,9 +616,7 @@ module Flor
616
616
  #
617
617
  def migration_dir
618
618
 
619
- File.absolute_path(
620
- File.join(
621
- File.dirname(__FILE__), 'migrations'))
619
+ File.join(__dir__, 'migrations')
622
620
  end
623
621
 
624
622
  def caller_fname
@@ -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
@@ -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
- 'payload' => @message['payload'].merge('ret' => '') } ] ]
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
 
@@ -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 || @node['heat'][1]['nid']
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
 
@@ -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
- td = [ 'def', [], l ]
175
- td[1] << [ '_att', [ [ 'msg', [], l ] ], l ]
176
- non_att_children.each { |nac| td[1] << Flor.dup(nac) }
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] << td
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
@@ -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
 
@@ -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
- # # TODO document "kill"
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...
@@ -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
- name = att('name', nil)
87
+ na = att('name', nil)
88
+ ei = att('exid') || exid
89
+ pl = att('payload') || payload.copy_current
68
90
 
69
- return super unless name
91
+ return super unless na
70
92
 
71
93
  wrap(
72
- 'point' => 'signal', 'nid' => nid, 'name' => name,
73
- 'payload' => payload.copy_current
74
- ) + super
94
+ 'point' => 'signal',
95
+ 'exid' => ei, 'nid' => nid,
96
+ 'name' => na, 'payload' => pl
97
+ ) +
98
+ super
75
99
  end
76
100
  end
77
101