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 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