flor 1.6.2 → 1.6.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.
@@ -103,7 +103,7 @@ class Flor::Pro::Define < Flor::Procedure
103
103
 
104
104
  return [ tre, sig ] unless wrapped?(sig)
105
105
 
106
- # There is a parenthese around the parameters, let's unwrap that...
106
+ # There are parentheses around the parameters, let's unwrap that...
107
107
 
108
108
  hed = Flor.dup(tre[1][0, off])
109
109
  sig = Flor.dup(sig)
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+
4
+ class Flor::Pro::Del < Flor::Procedure
5
+ #
6
+ # Removes a field or a variable.
7
+ #
8
+ # ```
9
+ # sequence
10
+ # del f.a # blanks field 'a' from the payload
11
+ # del a # blanks variable 'a'
12
+ # ```
13
+ #
14
+ # Returns the value held in the field or variable or `null` else.
15
+ #
16
+ # `del` will raise an error if the target field cannot be reached,
17
+ # but `delf` will not raise and simply return `null`.
18
+
19
+ names %w[ del delf ]
20
+
21
+ def pre_execute
22
+
23
+ unatt_unkeyed_children
24
+ rerep_children
25
+
26
+ @node['refs'] = []
27
+ end
28
+
29
+ def receive_non_att
30
+
31
+ ft = tree[1][@fcid] || []
32
+
33
+ if ft[0] == '_rep'
34
+ @node['refs'] << payload['ret']
35
+ else
36
+ payload['ret'] = node_payload_ret
37
+ end
38
+
39
+ super
40
+ end
41
+
42
+ def receive_last
43
+
44
+ ret = nil
45
+
46
+ @node['refs'].each do |ref|
47
+
48
+ ret = (lookup_value(ref) rescue nil)
49
+ begin
50
+ unset_value(ref)
51
+ rescue
52
+ raise unless tree[0] == 'delf'
53
+ end
54
+ end
55
+
56
+ wrap('ret' => ret)
57
+ end
58
+
59
+ protected
60
+
61
+ def rerep_children
62
+
63
+ t = tree
64
+
65
+ cn = t[1]
66
+ .collect { |ct|
67
+ hd, cn, ln = ct
68
+ if hd == '_ref'
69
+ [ '_rep', cn, ln ]
70
+ elsif hd == '_dqs'
71
+ [ '_rep', [ ct ], ln ]
72
+ elsif Flor.is_single_ref_tree?(ct)
73
+ [ '_rep', [ [ '_sqs', hd, ln ] ], ln ]
74
+ else
75
+ ct
76
+ end }
77
+
78
+ @node['tree'] = [ t[0], cn, t[2] ] if cn != t[1]
79
+ end
80
+ end
81
+
@@ -2,7 +2,7 @@
2
2
 
3
3
  class Flor::Pro::Fail < Flor::Procedure
4
4
  #
5
- # Explicitely raises an error.
5
+ # Explicitly raises an error.
6
6
  #
7
7
  # ```
8
8
  # fail "not enough water in the tank"
data/lib/flor/pcore/if.rb CHANGED
@@ -43,6 +43,27 @@ class Flor::Pro::If < Flor::Procedure
43
43
  #
44
44
  # Currently, if an "else if" is needed, it's better to use [cond](cond.md).
45
45
  #
46
+ # ## improving readability with else and then
47
+ #
48
+ # "then" and "else" can be aliased to "sequence" and be used within if to
49
+ # make the flow definition easier to read, and especially less confusing.
50
+ #
51
+ # ```
52
+ # # at the top of the workflow definition
53
+ # # alias "then" and "else" to "sequence"
54
+ #
55
+ # set then sequence
56
+ # set else sequence
57
+ #
58
+ # # ...
59
+ #
60
+ # if (f.age > 3)
61
+ # then
62
+ # set f.designation 'child'
63
+ # else
64
+ # set f.designation 'baby'
65
+ # order_baby_food _
66
+ # ```
46
67
  #
47
68
  # ## see also
48
69
  #
@@ -146,12 +146,11 @@ class Flor::Macro::Iterator < Flor::Macro
146
146
 
147
147
  def rewrite_iterator_tree(procedure_name)
148
148
 
149
- atts = att_children
150
-
151
149
  l = tree[2]
152
150
 
153
151
  th = [ procedure_name, [], l, *tree[3] ]
154
- atts.each { |ac| th[1] << Flor.dup(ac) }
152
+
153
+ att_children.each { |ac| th[1] << Flor.dup(ac) }
155
154
 
156
155
  if non_att_children.any?
157
156
 
@@ -156,7 +156,7 @@ class Flor::Pro::Match < Flor::Pro::Case
156
156
  #
157
157
  # ## "bind"
158
158
  #
159
- # "bind" binds explicitely a value and allows for a sub-pattern.
159
+ # "bind" binds explicitly a value and allows for a sub-pattern.
160
160
  # ```
161
161
  # match [ 1 4 ]
162
162
  # [ 1 (bind y (or 2 3)) ]; "match y:$(y)"
@@ -17,7 +17,7 @@ class Flor::Pro::Reverse < Flor::Procedure
17
17
  # reverse _ # sets f.ret to [ 4, 6, 5 ]
18
18
  # ```
19
19
  #
20
- # Will fail if it finds nothing reversable.
20
+ # Will fail if it finds nothing reversible.
21
21
  #
22
22
  # # see also
23
23
  #
@@ -49,7 +49,7 @@ class Flor::Pro::Set < Flor::Procedure
49
49
  # set __2 g h
50
50
  # [ 9 10 11 12 13 ]
51
51
  # # ==> g: 11, h: 12
52
- # # `__` is not prefixed by a var name, so it justs discard
52
+ # # `__` is not prefixed by a var name, so it just discards
53
53
  # # what it captures
54
54
  # set i j___
55
55
  # [ 14 15 16 17 18 19 ]
@@ -2,7 +2,7 @@
2
2
 
3
3
  class Flor::Pro::Until < Flor::Procedure
4
4
  #
5
- # Loops until or while a condiation evalutates to true.
5
+ # Loops until or while a condiation evaluates to true.
6
6
  #
7
7
  # ```
8
8
  # set i 0
@@ -156,6 +156,7 @@ class Flor::Pro::ConcurrentIterator < Flor::Procedure
156
156
  is.each { |i| t1[i][1][0][0] = '_reff' }
157
157
 
158
158
  @node['tree'] = [ t[0], t1, *t[2..-1] ]
159
+ Flor.pp @node['tree']
159
160
  end
160
161
  end
161
162
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  class Flor::Pro::Signal < Flor::Procedure
4
4
  #
5
- # Used in conjuction with "on".
5
+ # Used in conjunction with "on".
6
6
  #
7
7
  # An external (or internal) agent may send a signal to an execution and the
8
8
  # execution may have a "on" handler for it.
@@ -139,7 +139,7 @@ class Flor::Pro::Trap < Flor::Procedure
139
139
  #
140
140
  # The range limit determines what is the range, or scope of the trap.
141
141
  # By default the trap only care about nodes below its parent. In other words,
142
- # the trap binds itself to its parent and observes the messages occuring
142
+ # the trap binds itself to its parent and observes the messages occurring
143
143
  # in the execution in the branch of nodes whose root is the parent.
144
144
  #
145
145
  # The possible values for `range:` are:
@@ -5,58 +5,61 @@ module Flor
5
5
  # used in procedure, cancel, until, cursor specs
6
6
  # when "walking" and "stepping"
7
7
 
8
- def self.to_s(o=nil, k=nil)
8
+ class << self
9
9
 
10
- return 'Flor' if o == nil && k == nil
11
- # should it emerge somewhere...
10
+ def to_s(o=nil, k=nil)
12
11
 
13
- return o.collect { |e| Flor.to_s(e, k) }.join("\n") if o.is_a?(Array)
12
+ return 'Flor' if o == nil && k == nil
13
+ # should it emerge somewhere...
14
14
 
15
- if o.is_a?(Hash)
15
+ return o.collect { |e| Flor.to_s(e, k) }.join("\n") if o.is_a?(Array)
16
16
 
17
- return send("message_#{k}_to_s", o) if k && o['point'].is_a?(String)
18
- return message_to_s(o) if o['point'].is_a?(String)
17
+ if o.is_a?(Hash)
19
18
 
20
- return send("node_#{k}_to_s", o) if k && o.has_key?('parent')
21
- return node_to_s(o) if o['parent'].is_a?(String)
19
+ return send("message_#{k}_to_s", o) if k && o['point'].is_a?(String)
20
+ return message_to_s(o) if o['point'].is_a?(String)
21
+
22
+ return send("node_#{k}_to_s", o) if k && o.has_key?('parent')
23
+ return node_to_s(o) if o['parent'].is_a?(String)
24
+ end
25
+
26
+ return [ o, k ].inspect if k
27
+ o.inspect
22
28
  end
23
29
 
24
- return [ o, k ].inspect if k
25
- o.inspect
26
- end
30
+ def message_to_s(m)
27
31
 
28
- def self.message_to_s(m)
32
+ s = StringIO.new
33
+ s << '(msg ' << m['nid'] << ' ' << m['point']
34
+ %w[ from flavour ].each { |k|
35
+ s << ' ' << k << ':' << m[k].to_s if m.has_key?(k) }
36
+ s << ')'
29
37
 
30
- s = StringIO.new
31
- s << '(msg ' << m['nid'] << ' ' << m['point']
32
- %w[ from flavour ].each { |k|
33
- s << ' ' << k << ':' << m[k].to_s if m.has_key?(k) }
34
- s << ')'
38
+ s.string
39
+ end
35
40
 
36
- s.string
37
- end
41
+ def node_status_to_s(n)
38
42
 
39
- def self.node_status_to_s(n)
43
+ stas = n['status'].reverse
40
44
 
41
- stas = n['status'].reverse
45
+ s = StringIO.new
46
+ while sta = stas.shift
47
+ s << '(status ' << (sta['status'] || 'o') # o for open
48
+ s << ' pt:' << sta['point']
49
+ if f = sta['flavour']; s << ' fla:' << f; end
50
+ if f = sta['from']; s << ' fro:' << f; end
51
+ if m = sta['m']; s << ' m:' << m; end
52
+ s << ')'
53
+ s << "\n" if stas.any?
54
+ end
42
55
 
43
- s = StringIO.new
44
- while sta = stas.shift
45
- s << '(status ' << (sta['status'] || 'o') # o for open
46
- s << ' pt:' << sta['point']
47
- if f = sta['flavour']; s << ' fla:' << f; end
48
- if f = sta['from']; s << ' fro:' << f; end
49
- if m = sta['m']; s << ' m:' << m; end
50
- s << ')'
51
- s << "\n" if stas.any?
56
+ s.string
52
57
  end
53
58
 
54
- s.string
55
- end
59
+ def node_to_s(n) # there is already a .node_to_s in log.rb
56
60
 
57
- def self.node_to_s(n) # there is already a .node_to_s in log.rb
58
-
59
- n.inspect
61
+ n.inspect
62
+ end
60
63
  end
61
64
  end
62
65
 
@@ -8,74 +8,77 @@ module Flor::Tools; end
8
8
 
9
9
  module Flor::Tools::Env
10
10
 
11
- def self.make(path, envname=nil, opts={})
11
+ class << self
12
12
 
13
- if envname.is_a?(Hash)
14
- opts = envname
15
- envname = nil
16
- end
13
+ def make(path, envname=nil, opts={})
17
14
 
18
- opts[:env] = envname || 'production'
19
- opts[:sto_uri] ||= 'sqlite://tmp/dev.db'
20
- opts[:gitkeep] = true unless opts.has_key?(:gitkeep)
15
+ if envname.is_a?(Hash)
16
+ opts = envname
17
+ envname = nil
18
+ end
21
19
 
22
- mkdir(path, envname) if envname
20
+ opts[:env] = envname || 'production'
21
+ opts[:sto_uri] ||= 'sqlite://tmp/dev.db'
22
+ opts[:gitkeep] = true unless opts.has_key?(:gitkeep)
23
23
 
24
- mk_etc(path, envname, opts)
25
- mk_lib(path, envname, opts)
26
- mk_usr(path, envname, opts)
27
- mk_var(path, envname, opts)
28
- end
24
+ mkdir(path, envname) if envname
25
+
26
+ mk_etc(path, envname, opts)
27
+ mk_lib(path, envname, opts)
28
+ mk_usr(path, envname, opts)
29
+ mk_var(path, envname, opts)
30
+ end
29
31
 
30
- # protected, somehow
32
+ # protected, somehow
31
33
 
32
- def self.mk_etc(*ps, opts)
34
+ def mk_etc(*ps, opts)
33
35
 
34
- mkdir(*ps, 'etc')
35
- mkdir(*ps, 'etc', 'variables')
36
- touch(*ps, 'etc', 'variables', '.gitkeep') if opts[:gitkeep]
36
+ mkdir(*ps, 'etc')
37
+ mkdir(*ps, 'etc', 'variables')
38
+ touch(*ps, 'etc', 'variables', '.gitkeep') if opts[:gitkeep]
37
39
 
38
- write(*ps, 'etc', 'conf.json') do
39
- "env: #{opts[:env]}\n" +
40
- "sto_uri: #{opts[:sto_uri].inspect}"
40
+ write(*ps, 'etc', 'conf.json') do
41
+ "env: #{opts[:env]}\n" +
42
+ "sto_uri: #{opts[:sto_uri].inspect}"
43
+ end
41
44
  end
42
- end
43
45
 
44
- def self.mk_lib(*ps, opts)
46
+ def mk_lib(*ps, opts)
45
47
 
46
- mkdir(*ps, 'lib')
47
- mkdir(*ps, 'lib', 'flows')
48
- touch(*ps, 'lib', 'flows', '.gitkeep') if opts[:gitkeep]
49
- mkdir(*ps, 'lib', 'taskers')
50
- touch(*ps, 'lib', 'taskers', '.gitkeep') if opts[:gitkeep]
51
- end
48
+ mkdir(*ps, 'lib')
49
+ mkdir(*ps, 'lib', 'flows')
50
+ touch(*ps, 'lib', 'flows', '.gitkeep') if opts[:gitkeep]
51
+ mkdir(*ps, 'lib', 'taskers')
52
+ touch(*ps, 'lib', 'taskers', '.gitkeep') if opts[:gitkeep]
53
+ end
52
54
 
53
- def self.mk_usr(*ps, opts)
55
+ def mk_usr(*ps, opts)
54
56
 
55
- mkdir(*ps, 'usr')
57
+ mkdir(*ps, 'usr')
56
58
 
57
- if opts[:acme] == false
58
- touch(*ps, 'usr', '.gitkeep') if opts[:gitkeep]
59
- else
60
- mkdir(*ps, 'usr', 'com.acme')
61
- mk_etc(*ps, 'usr', 'com.acme', opts)
62
- mk_lib(*ps, 'usr', 'com.acme', opts)
59
+ if opts[:acme] == false
60
+ touch(*ps, 'usr', '.gitkeep') if opts[:gitkeep]
61
+ else
62
+ mkdir(*ps, 'usr', 'com.acme')
63
+ mk_etc(*ps, 'usr', 'com.acme', opts)
64
+ mk_lib(*ps, 'usr', 'com.acme', opts)
65
+ end
63
66
  end
64
- end
65
67
 
66
- def self.mk_var(*ps, opts)
68
+ def mk_var(*ps, opts)
67
69
 
68
- mkdir(*ps, 'var')
69
- mkdir(*ps, 'var', 'log')
70
- touch(*ps, 'var', 'log', '.gitkeep') if opts[:gitkeep]
71
- end
70
+ mkdir(*ps, 'var')
71
+ mkdir(*ps, 'var', 'log')
72
+ touch(*ps, 'var', 'log', '.gitkeep') if opts[:gitkeep]
73
+ end
72
74
 
73
- def self.mkdir(*ps); FileUtils.mkdir(File.join(*ps.compact)); end
74
- def self.touch(*ps); FileUtils.touch(File.join(*ps.compact)); end
75
+ def mkdir(*ps); FileUtils.mkdir(File.join(*ps.compact)); end
76
+ def touch(*ps); FileUtils.touch(File.join(*ps.compact)); end
75
77
 
76
- def self.write(*ps, &block)
78
+ def write(*ps, &block)
77
79
 
78
- File.open(File.join(*ps.compact), 'wb') { |f| f.write(block.call) }
80
+ File.open(File.join(*ps.compact), 'wb') { |f| f.write(block.call) }
81
+ end
79
82
  end
80
83
  end
81
84
 
@@ -208,18 +208,21 @@ module Flor::Tools
208
208
 
209
209
  ALIASES = {}
210
210
 
211
- def self.make_alias(a, b)
211
+ class << self
212
212
 
213
- define_method("hlp_#{a}") { "alias to #{b.inspect}" }
214
- alias_method "man_#{a}", "man_#{b}" rescue nil
215
- alias_method "cmd_#{a}", "cmd_#{b}"
213
+ def make_alias(a, b)
216
214
 
217
- (ALIASES[b] ||= []) << a
218
- end
215
+ define_method("hlp_#{a}") { "alias to #{b.inspect}" }
216
+ alias_method "man_#{a}", "man_#{b}" rescue nil
217
+ alias_method "cmd_#{a}", "cmd_#{b}"
218
+
219
+ (ALIASES[b] ||= []) << a
220
+ end
219
221
 
220
- def self.is_alias?(c)
222
+ def is_alias?(c)
221
223
 
222
- !! ALIASES.values.find { |a| a.include?(c) }
224
+ !! ALIASES.values.find { |a| a.include?(c) }
225
+ end
223
226
  end
224
227
 
225
228
  def args(line); line.split(/\s+/); end
@@ -642,7 +645,7 @@ module Flor::Tools
642
645
  puts Flor.to_d(
643
646
  { key: @unit.conf[key] }, colour: true, indent: 1, width: true)
644
647
  elsif key
645
- # alreay done
648
+ # already done
646
649
  else
647
650
  page(Flor.to_d(@unit.conf, colour: true, indent: 1, width: true))
648
651
  end
@@ -1,7 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+
3
4
  module Flor
4
5
 
6
+ # As seen in docs/hooks.md
7
+ #
8
+ # ```ruby
9
+ # flor.hooker.add('journal', Flor::Journal)
10
+ # ```
11
+ #
12
+ #
5
13
  # A Journal hook, receives only "consumed" messages and
6
14
  # keeps a copy of all of them.
7
15
  #
@@ -46,19 +46,22 @@ module Flor
46
46
  @spooler =
47
47
  (Flor::Conf.get_class(@conf, 'spooler') || Flor::Spooler).new(self)
48
48
 
49
- @heart_rate = @conf[:sch_heart_rate] || 0.3
50
- @reload_after = @conf[:sch_reload_after] || 60
49
+ @heart_rate = @conf['sch_heart_rate'] || 0.3
50
+ @reload_after = @conf['sch_reload_after'] || 60
51
51
  #
52
52
  @wake_up = true
53
53
  @next_time = nil
54
54
  @reloaded_at = Time.now
55
55
 
56
- @msg_max_res_time = @conf[:sch_msg_max_res_time] || 10 * 60
56
+ @msg_max_res_time = @conf['sch_msg_max_res_time'] || 10 * 60
57
57
 
58
58
  @idle_count = 0
59
59
 
60
- @max_executors = @conf[:sch_max_executors] || 1
61
- #
60
+ @max_executor_count =
61
+ @conf['sch_max_executor_count'] ||
62
+ @conf['sch_max_executors'] ||
63
+ 7
64
+
62
65
  @executors = []
63
66
 
64
67
  c = @conf['constant']
@@ -367,20 +370,22 @@ module Flor
367
370
  @wake_up = true
368
371
  end
369
372
 
370
- def notify(executor, o)
373
+ def notify(executor, message)
371
374
 
372
375
  if executor
373
- @hooker.notify(executor, o)
376
+ @hooker.notify(executor, message)
374
377
  else
375
- @hooker.wlist.notify(nil, o)
378
+ @hooker.wlist.notify(nil, message)
376
379
  end
377
380
 
378
- rescue => err
379
- puts '-sch' * 19
380
- puts "+ error in #{self.class}#notify"
381
- p err
382
- puts err.backtrace
383
- puts ('-sch' * 19) + ' .'
381
+ # NO, let it fail...
382
+ #
383
+ #rescue => err
384
+ # puts '-sch' * 19
385
+ # puts "+ error in #{self.class}#notify"
386
+ # p err
387
+ # puts err.backtrace
388
+ # puts ('-sch' * 19) + ' .'
384
389
  end
385
390
 
386
391
  def trap(node, tra)
@@ -710,18 +715,20 @@ module Flor
710
715
  @storage.trigger_timers
711
716
  end
712
717
 
718
+ def free_executor_count
719
+
720
+ [ 0, @max_executor_count - @executors.count ].max
721
+ end
722
+
713
723
  def trigger_executions
714
724
 
715
725
  @executors.select! { |e| e.alive? }
716
726
  # drop done executors
717
727
 
718
- free_executor_count = @max_executors - @executors.size
719
-
720
- return if free_executor_count < 1
721
-
722
- messages = @storage.load_messages(free_executor_count)
728
+ @storage.load_messages(free_executor_count).each do |exid, ms|
729
+ # remember that load_messages load count + 2 executions...
723
730
 
724
- messages.each do |exid, ms|
731
+ break if free_executor_count < 1
725
732
 
726
733
  next unless @storage.reserve_all_messages(ms)
727
734
 
@@ -821,7 +821,8 @@ module Flor
821
821
  @db[:flor_pointers]
822
822
  .import(
823
823
  POINTER_COLUMNS,
824
- pointers)
824
+ pointers,
825
+ skip_transaction: true)
825
826
 
826
827
  callback(:pointers, :update, exid)
827
828
  end
data/lib/flor.rb CHANGED
@@ -16,7 +16,7 @@ require 'dense'
16
16
 
17
17
  module Flor
18
18
 
19
- VERSION = '1.6.2'
19
+ VERSION = '1.6.4'
20
20
  end
21
21
 
22
22
  require 'flor/colours'
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flor
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.2
4
+ version: 1.6.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Mettraux
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2023-12-03 00:00:00.000000000 Z
10
+ date: 2025-09-18 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: munemo
@@ -44,14 +43,20 @@ dependencies:
44
43
  requirements:
45
44
  - - "~>"
46
45
  - !ruby/object:Gem::Version
47
- version: '1.2'
46
+ version: '1.10'
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: 1.11.1
48
50
  type: :runtime
49
51
  prerelease: false
50
52
  version_requirements: !ruby/object:Gem::Requirement
51
53
  requirements:
52
54
  - - "~>"
53
55
  - !ruby/object:Gem::Version
54
- version: '1.2'
56
+ version: '1.10'
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: 1.11.1
55
60
  - !ruby/object:Gem::Dependency
56
61
  name: dense
57
62
  requirement: !ruby/object:Gem::Requirement
@@ -175,6 +180,7 @@ files:
175
180
  - lib/flor/pcore/cond.rb
176
181
  - lib/flor/pcore/cursor.rb
177
182
  - lib/flor/pcore/define.rb
183
+ - lib/flor/pcore/del.rb
178
184
  - lib/flor/pcore/detect.rb
179
185
  - lib/flor/pcore/do_return.rb
180
186
  - lib/flor/pcore/each.rb
@@ -290,7 +296,6 @@ metadata:
290
296
  mailing_list_uri: https://groups.google.com/forum/#!forum/floraison
291
297
  homepage_uri: https://github.com/floraison/flor
292
298
  source_code_uri: https://github.com/floraison/flor
293
- post_install_message:
294
299
  rdoc_options: []
295
300
  require_paths:
296
301
  - lib
@@ -305,8 +310,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
305
310
  - !ruby/object:Gem::Version
306
311
  version: '0'
307
312
  requirements: []
308
- rubygems_version: 3.2.33
309
- signing_key:
313
+ rubygems_version: 3.6.2
310
314
  specification_version: 4
311
315
  summary: A Ruby workflow engine
312
316
  test_files: []