flor 1.2.1 → 1.4.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 +22 -0
- data/lib/flor/core/node.rb +1 -1
- data/lib/flor/core/procedure.rb +8 -4
- data/lib/flor/log.rb +1 -1
- data/lib/flor/parser.rb +2 -0
- data/lib/flor/tools/flotojson.rb +64 -0
- data/lib/flor/unit/models/execution.rb +63 -14
- data/lib/flor/unit/scheduler.rb +9 -7
- data/lib/flor/unit/storage.rb +55 -27
- data/lib/flor.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 298a6b2c9d3752b561ee8933ae00ed7a3f63682fb758dbe899097c6f7f56c00d
|
4
|
+
data.tar.gz: 3e2602737b9de7a452549d0f8ed34752618b1322a5508d178abe6559d549c045
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 28ae10c78676391537dde7cd7e0082338650c6aeba60fbcffa2ca5a4cb2dd2f168d7ff61019472c5f6f3d2216cabfe6e9bccb7cdae4856b513e0663f4d4ec362
|
7
|
+
data.tar.gz: 6379738cddfc5b74d37a30fa23260924649866b49dc670f37a0540a47a5079062288851ce07306350e227d73d0fd873f2199c3ea01b3399bce61572b8cebe497
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,28 @@
|
|
2
2
|
# CHANGELOG.md
|
3
3
|
|
4
4
|
|
5
|
+
## flor 1.4.0 released 2021-11-10
|
6
|
+
|
7
|
+
* Add :tree to Execution#to_h
|
8
|
+
* Scaffold bin/flotojson
|
9
|
+
|
10
|
+
|
11
|
+
## flor 1.3.1 released 2021-04-19
|
12
|
+
|
13
|
+
* Fix flor_pointers var deletion mechanism (type = ' var ')
|
14
|
+
|
15
|
+
|
16
|
+
## flor 1.3.0 released 2021-04-13
|
17
|
+
|
18
|
+
* Insert a row flor_pointers for 'failure'
|
19
|
+
|
20
|
+
|
21
|
+
## flor 1.2.2 released 2021-03-29
|
22
|
+
|
23
|
+
* Include data in flor_pointers
|
24
|
+
* Ensure flor_pointers name is a string
|
25
|
+
|
26
|
+
|
5
27
|
## flor 1.2.1 released 2021-03-22
|
6
28
|
|
7
29
|
* If conf sto_db_logger is false, do not attach a logger to the db connection
|
data/lib/flor/core/node.rb
CHANGED
@@ -149,7 +149,7 @@ class Flor::Node
|
|
149
149
|
tree = lookup_tree(Flor.parent_nid(nid))
|
150
150
|
return tree[1][cid] if tree
|
151
151
|
|
152
|
-
#tree = lookup_tree(Flor.parent_nid(nid, true))
|
152
|
+
#tree = lookup_tree(Flor.parent_nid(nid, remove_subnid=true))
|
153
153
|
#return tree[1][cid] if tree
|
154
154
|
#
|
155
155
|
# might become necessary at some point
|
data/lib/flor/core/procedure.rb
CHANGED
@@ -5,7 +5,7 @@ class Flor::Procedure < Flor::Node
|
|
5
5
|
# "Returning vars" variables to pass back to pass upon reply.
|
6
6
|
# In the 'receive' messages, it's a hash under the key 'rvars'.
|
7
7
|
#
|
8
|
-
RVARS = %w[ idx ]
|
8
|
+
RVARS = %w[ idx ].freeze
|
9
9
|
|
10
10
|
# Attributes that when given alone are turned to "true" attributes.
|
11
11
|
#
|
@@ -13,7 +13,7 @@ class Flor::Procedure < Flor::Node
|
|
13
13
|
#
|
14
14
|
# The transformation occurs in Flor::Pro::Att ("_att").
|
15
15
|
#
|
16
|
-
TRUE_ATTS = %w[ flank off disabled ]
|
16
|
+
TRUE_ATTS = %w[ flank off disabled ].freeze
|
17
17
|
|
18
18
|
class << self
|
19
19
|
|
@@ -550,6 +550,8 @@ class Flor::Procedure < Flor::Node
|
|
550
550
|
wrap_reply
|
551
551
|
end
|
552
552
|
|
553
|
+
IF_UNLESS = %w[ _if _unless ].freeze
|
554
|
+
|
553
555
|
# Grab on_error proc from incoming payload and stores it into parent node.
|
554
556
|
#
|
555
557
|
# Has no effect if there is no parent node.
|
@@ -562,7 +564,7 @@ class Flor::Procedure < Flor::Node
|
|
562
564
|
@node; loop do
|
563
565
|
pnode = parent_node(pnode)
|
564
566
|
return unless pnode
|
565
|
-
break unless
|
567
|
+
break unless IF_UNLESS.include?(pnode['heap'])
|
566
568
|
end
|
567
569
|
|
568
570
|
flavour = "on_#{key}"
|
@@ -598,6 +600,8 @@ class Flor::Procedure < Flor::Node
|
|
598
600
|
wrap('point' => 'entered', 'nid' => nid, 'tags' => ret)
|
599
601
|
end
|
600
602
|
|
603
|
+
WRAP_KEYS = %w[ error cancel timeout ].freeze
|
604
|
+
|
601
605
|
def wrap(h={})
|
602
606
|
|
603
607
|
m = {}
|
@@ -642,7 +646,7 @@ class Flor::Procedure < Flor::Node
|
|
642
646
|
# was considering passing the whole vars back (as 'varz'), but
|
643
647
|
# it got in the way... and it might be heavy
|
644
648
|
|
645
|
-
|
649
|
+
WRAP_KEYS
|
646
650
|
.each { |k|
|
647
651
|
co = @node["child_on_#{k}"]
|
648
652
|
next unless co
|
data/lib/flor/log.rb
CHANGED
@@ -343,7 +343,7 @@ module Flor
|
|
343
343
|
o.puts(tree_to_s(node.lookup_tree(nid), nid, out: o)) if node
|
344
344
|
|
345
345
|
o.puts "#{_c.dg}node:#{_c.yl}"
|
346
|
-
o.puts YAML.dump(n.merge('tree' => '(above)'))
|
346
|
+
o.puts n ? YAML.dump(n.merge('tree' => '(above)')) : 'nil'
|
347
347
|
|
348
348
|
o.puts "#{_c.dg}nodes:#{_c.yl}"
|
349
349
|
o.puts nods_to_s(executor, m, opts)
|
data/lib/flor/parser.rb
CHANGED
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# flotojson.rb
|
4
|
+
|
5
|
+
require 'flor'
|
6
|
+
|
7
|
+
FLAGS_WITH_VALUE = []
|
8
|
+
|
9
|
+
flags = {}
|
10
|
+
files = []
|
11
|
+
|
12
|
+
if (ARGV & [ '-h', '--help']).any?
|
13
|
+
puts
|
14
|
+
puts "bin/flotojson [flags] filename"
|
15
|
+
puts
|
16
|
+
puts " turns a flor .flo process definition to its tree representation"
|
17
|
+
puts
|
18
|
+
puts " flags:"
|
19
|
+
puts " --pp pretty prints instead of dumping as JSON"
|
20
|
+
puts
|
21
|
+
exit 0
|
22
|
+
end
|
23
|
+
|
24
|
+
args = ARGV.dup
|
25
|
+
|
26
|
+
loop do
|
27
|
+
|
28
|
+
a = args.shift; break unless a
|
29
|
+
|
30
|
+
if a.size > 1 && a[0, 1] == '-'
|
31
|
+
flags[a] = FLAGS_WITH_VALUE.include?(a) ? a.shift : true
|
32
|
+
else
|
33
|
+
files << a
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
#STDERR.puts flags.inspect
|
38
|
+
#STDERR.puts files.inspect
|
39
|
+
|
40
|
+
# t =
|
41
|
+
# tree.is_a?(String) ?
|
42
|
+
# Flor.parse(tree, opts[:fname] || opts[:path], opts) :
|
43
|
+
# tree
|
44
|
+
#
|
45
|
+
# unless t
|
46
|
+
#
|
47
|
+
# #h = opts.merge(prune: false, rewrite: false, debug: 0)
|
48
|
+
# #Raabro.pp(Flor.parse(tree, h[:fname], h))
|
49
|
+
# # TODO re-parse and indicate what went wrong...
|
50
|
+
#
|
51
|
+
# fail ArgumentError.new(
|
52
|
+
# "flow parsing failed: " + tree.inspect[0, 35] + '...')
|
53
|
+
# end
|
54
|
+
|
55
|
+
fname = files.first
|
56
|
+
content = File.read(fname)
|
57
|
+
tree = Flor.parse(content, fname, {})
|
58
|
+
|
59
|
+
if flags['--pp']
|
60
|
+
pp tree
|
61
|
+
else
|
62
|
+
puts JSON.dump(tree)
|
63
|
+
end
|
64
|
+
|
@@ -20,7 +20,29 @@ module Flor
|
|
20
20
|
#end
|
21
21
|
|
22
22
|
def nodes; data['nodes']; end
|
23
|
+
|
23
24
|
def zero_node; nodes['0']; end
|
25
|
+
|
26
|
+
# Returns the nids, the lower in the tree, the earlier in the returned
|
27
|
+
# array.
|
28
|
+
#
|
29
|
+
def sorted_nids
|
30
|
+
|
31
|
+
nodes.keys
|
32
|
+
.inject([]) { |a, nid|
|
33
|
+
l = nid.split('_').length
|
34
|
+
(a[l] ||= []) << nid
|
35
|
+
a }
|
36
|
+
.compact
|
37
|
+
.collect(&:sort)
|
38
|
+
.flatten(1)
|
39
|
+
end
|
40
|
+
|
41
|
+
def lowest_node
|
42
|
+
|
43
|
+
nodes[sorted_nids.first]
|
44
|
+
end
|
45
|
+
|
24
46
|
def closing_messages; data['closing_messages']; end
|
25
47
|
|
26
48
|
def execution(reload=false); self; end
|
@@ -47,14 +69,16 @@ module Flor
|
|
47
69
|
|
48
70
|
def full_tree
|
49
71
|
|
50
|
-
|
72
|
+
nids = sorted_nids
|
73
|
+
nid0 = nids.shift
|
51
74
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
75
|
+
return nil unless nid0
|
76
|
+
|
77
|
+
tree = Flor.dup(nodes[nid0]['tree'])
|
78
|
+
|
79
|
+
nids.each { |nid|
|
80
|
+
next unless nid.split('_', 2).first == nid0
|
81
|
+
replace_sub_tree(tree, nid, nodes[nid]['tree']) }
|
58
82
|
|
59
83
|
tree
|
60
84
|
end
|
@@ -80,21 +104,22 @@ module Flor
|
|
80
104
|
cs = m[:counts] = {}
|
81
105
|
is = m[:nids] = { tasks: [], failures: [] }
|
82
106
|
|
83
|
-
|
84
|
-
|
107
|
+
cs[:failures] = 0
|
108
|
+
cs[:tasks] = 0
|
109
|
+
cs[:nodes] = nodes.count
|
110
|
+
#
|
85
111
|
nodes.each do |k, v|
|
86
112
|
if v['task']
|
87
|
-
|
113
|
+
cs[:tasks] += 1
|
88
114
|
is[:tasks] << k
|
89
115
|
end
|
90
116
|
if v['failure']
|
91
|
-
|
117
|
+
cs[:failures] += 1
|
92
118
|
is[:failures] << k
|
93
119
|
end
|
94
120
|
end
|
95
|
-
|
96
|
-
|
97
|
-
cs[:tasks] = ts
|
121
|
+
|
122
|
+
h[:tree] = full_tree
|
98
123
|
|
99
124
|
h
|
100
125
|
end
|
@@ -149,6 +174,30 @@ module Flor
|
|
149
174
|
lookup_node(query, opts)['nid']
|
150
175
|
end
|
151
176
|
|
177
|
+
protected
|
178
|
+
|
179
|
+
def replace_sub_tree(tree, nid, t)
|
180
|
+
|
181
|
+
return unless t
|
182
|
+
return if nid.index('-') # stay vanilla
|
183
|
+
|
184
|
+
snid = nid.split('_').collect(&:to_i)[1..-1]
|
185
|
+
a = get_child_array(tree, snid)
|
186
|
+
|
187
|
+
return unless a # shouldn't we fail?
|
188
|
+
|
189
|
+
a[snid.first] = Flor.dup(t)
|
190
|
+
end
|
191
|
+
|
192
|
+
def get_child_array(tree, snid)
|
193
|
+
|
194
|
+
return nil if tree.nil?
|
195
|
+
return nil if snid.length < 1
|
196
|
+
return nil unless tree[1].is_a?(Array)
|
197
|
+
return tree[1] if snid.length == 1
|
198
|
+
n = snid.shift; get_child_array(tree[1][n], snid)
|
199
|
+
end
|
200
|
+
|
152
201
|
class << self
|
153
202
|
|
154
203
|
def by_status(s)
|
data/lib/flor/unit/scheduler.rb
CHANGED
@@ -242,18 +242,18 @@ module Flor
|
|
242
242
|
end
|
243
243
|
end
|
244
244
|
|
245
|
+
RETURN_KEYS = %w[ exid nid payload tasker cause ].freeze
|
246
|
+
|
245
247
|
def return(message)
|
246
248
|
|
247
|
-
|
249
|
+
queue(
|
248
250
|
if message['point'] == 'failed'
|
249
251
|
message
|
250
252
|
else
|
251
253
|
message
|
252
|
-
.select { |k, _|
|
254
|
+
.select { |k, _| RETURN_KEYS.include?(k) }
|
253
255
|
.merge!('point' => 'return')
|
254
|
-
end
|
255
|
-
|
256
|
-
queue(m)
|
256
|
+
end)
|
257
257
|
|
258
258
|
nil
|
259
259
|
end
|
@@ -432,7 +432,7 @@ module Flor
|
|
432
432
|
ex ? ex.execution : nil
|
433
433
|
end
|
434
434
|
|
435
|
-
DUMP_KEYS = %w[ timestamp executions timers traps pointers ]
|
435
|
+
DUMP_KEYS = %w[ timestamp executions timers traps pointers ].freeze
|
436
436
|
|
437
437
|
# Dumps all or some of the executions to a JSON string.
|
438
438
|
# See Scheduler#load for importing.
|
@@ -598,6 +598,8 @@ module Flor
|
|
598
598
|
puts(on_start_exc(ex))
|
599
599
|
end
|
600
600
|
|
601
|
+
PREP_KEYS = %w[ exid name nid payload on_receive_last ].freeze
|
602
|
+
|
601
603
|
def prepare_message(point, args)
|
602
604
|
|
603
605
|
h = args
|
@@ -612,7 +614,7 @@ module Flor
|
|
612
614
|
opts = {}
|
613
615
|
|
614
616
|
h.each do |k, v|
|
615
|
-
if
|
617
|
+
if PREP_KEYS.include?(k)
|
616
618
|
msg[k] = v
|
617
619
|
else
|
618
620
|
opts[k.to_sym] = v
|
data/lib/flor/unit/storage.rb
CHANGED
@@ -12,8 +12,9 @@ module Flor
|
|
12
12
|
:status, :ctime, :mtime, :cunit, :munit
|
13
13
|
].freeze
|
14
14
|
POINTER_COLUMNS = [
|
15
|
-
:domain, :exid, :nid, :type, :name, :value, :ctime, :cunit
|
16
|
-
|
15
|
+
:domain, :exid, :nid, :type, :name, :value, :ctime, :cunit,
|
16
|
+
:content
|
17
|
+
].freeze
|
17
18
|
|
18
19
|
attr_reader :unit, :db, :models
|
19
20
|
|
@@ -219,6 +220,9 @@ module Flor
|
|
219
220
|
raise err
|
220
221
|
end
|
221
222
|
|
223
|
+
CRECON_STATUSES = %w[ created consumed ].freeze
|
224
|
+
RESCON_STATUSES = %w[ reserved consumed ].freeze
|
225
|
+
|
222
226
|
def load_messages(exe_count)
|
223
227
|
|
224
228
|
exe_count += 2
|
@@ -229,12 +233,12 @@ module Flor
|
|
229
233
|
_exids_being_processed =
|
230
234
|
@db[:flor_messages]
|
231
235
|
.select(:exid)
|
232
|
-
.exclude(status:
|
236
|
+
.exclude(status: CRECON_STATUSES)
|
233
237
|
_exids =
|
234
238
|
@db[:flor_messages]
|
235
239
|
.select(:exid)
|
236
240
|
.exclude(exid: _exids_being_processed)
|
237
|
-
.exclude(status:
|
241
|
+
.exclude(status: RESCON_STATUSES)
|
238
242
|
.limit(exe_count)
|
239
243
|
@db[:flor_messages]
|
240
244
|
.where(exid: _exids, status: 'created')
|
@@ -317,7 +321,7 @@ module Flor
|
|
317
321
|
[]
|
318
322
|
end
|
319
323
|
|
320
|
-
POINTS_TO_ARCHIVE = %w[ terminated failed ceased ]
|
324
|
+
POINTS_TO_ARCHIVE = %w[ terminated failed ceased ].freeze
|
321
325
|
|
322
326
|
def consume(messages)
|
323
327
|
|
@@ -685,6 +689,8 @@ module Flor
|
|
685
689
|
# done in update_pointers
|
686
690
|
end
|
687
691
|
|
692
|
+
FP_TYPES = %w[ var ].freeze
|
693
|
+
|
688
694
|
def update_pointers(exe, status, now)
|
689
695
|
|
690
696
|
# Q Should we archive old pointers?
|
@@ -700,7 +706,7 @@ module Flor
|
|
700
706
|
|
701
707
|
@db[:flor_pointers]
|
702
708
|
.where(exid: exid)
|
703
|
-
.where(Sequel.|({ type:
|
709
|
+
.where(Sequel.|({ type: FP_TYPES }, Sequel.~(nid: exe['nodes'].keys)))
|
704
710
|
.delete
|
705
711
|
#
|
706
712
|
# Delete all pointer to vars, their value might have changed,
|
@@ -713,28 +719,57 @@ module Flor
|
|
713
719
|
pointers = exe['nodes']
|
714
720
|
.inject([]) { |a, (nid, node)|
|
715
721
|
|
722
|
+
# add a pointer for each tag
|
723
|
+
|
716
724
|
ts = node['tags']
|
717
725
|
ts.each { |t|
|
718
726
|
a << [ dom, exid, nid, 'tag', t, nil, now, u, nil ] } if ts
|
719
727
|
|
728
|
+
# add a pointer for each var (if nid == '0')
|
729
|
+
|
720
730
|
vs = nid == '0' ? node['vars'] : nil
|
721
731
|
vs.each { |k, v|
|
722
732
|
case v; when Numeric, String, TrueClass, FalseClass, NilClass
|
723
|
-
a << [ dom, exid, '0', 'var', k, v.to_s, now, u,
|
733
|
+
a << [ dom, exid, '0', 'var', k, v.to_s, now, u, v ]
|
724
734
|
when Array, Hash
|
725
735
|
s = '(array)'; s = '(object)' if v.is_a?(Hash)
|
726
|
-
a << [ dom, exid, '0', 'var', k, s, now, u,
|
736
|
+
a << [ dom, exid, '0', 'var', k, s, now, u, v ]
|
727
737
|
else
|
728
|
-
a << [ dom, exid, '0', 'var', k, nil, now, u,
|
738
|
+
a << [ dom, exid, '0', 'var', k, nil, now, u, v ]
|
729
739
|
end } if vs
|
730
740
|
|
741
|
+
# add a pointer for the task if any
|
742
|
+
|
731
743
|
if ta = node['task']
|
732
744
|
tasker = ta['tasker']
|
733
|
-
|
745
|
+
n = ta['name']; name = n.is_a?(String) ? n : JSON.dump(n)
|
734
746
|
content = { message: node['message'], atts: node['atts'] }
|
735
747
|
a << [ dom, exid, nid, 'tasker', tasker, name, now, u, content ]
|
736
748
|
end
|
737
749
|
|
750
|
+
# add a pointer for the error if any
|
751
|
+
|
752
|
+
if fa = node['failure']
|
753
|
+
|
754
|
+
#puts "-" * 80; pp node; puts "-" * 80
|
755
|
+
a <<
|
756
|
+
if er = fa['error']
|
757
|
+
ni = fa['from'] || nid # not nid /!\
|
758
|
+
nam = "#{er['kla']} l#{er['lin']}"
|
759
|
+
val = er['msg']
|
760
|
+
con = { error: fa, nid: ni }
|
761
|
+
[ dom, exid, ni, 'failure', nam, val, now, u, con ]
|
762
|
+
else
|
763
|
+
nam = fa['tasker'] || 'failure'
|
764
|
+
val = [ fa['attl'] || [], fa['attd'] || {} ]
|
765
|
+
.collect(&:inspect).join(' ')
|
766
|
+
con = { error: fa, nid: nid }
|
767
|
+
[ dom, exid, nid, 'failure', nam, val, now, u, con ]
|
768
|
+
end
|
769
|
+
end
|
770
|
+
|
771
|
+
# done
|
772
|
+
|
738
773
|
a }
|
739
774
|
|
740
775
|
cps = @db[:flor_pointers] # current pointers
|
@@ -746,29 +781,22 @@ module Flor
|
|
746
781
|
#
|
747
782
|
# don't insert when already inserted
|
748
783
|
|
749
|
-
|
750
|
-
pointers.each { |ptr|
|
751
|
-
c = ptr[8]; ptr[8] = to_blob(c) if c }
|
752
|
-
else
|
753
|
-
pointers.each { |ptr|
|
754
|
-
ptr.pop }
|
755
|
-
end
|
784
|
+
pointers.each { |ptr| c = ptr[8]; ptr[8] = to_blob(c) if c }
|
756
785
|
|
757
786
|
@db[:flor_pointers]
|
758
787
|
.import(
|
759
|
-
|
788
|
+
POINTER_COLUMNS,
|
760
789
|
pointers)
|
761
790
|
end
|
762
791
|
|
763
|
-
def pointer_columns
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
end
|
792
|
+
#def pointer_columns
|
793
|
+
# @pointer_columns ||=
|
794
|
+
# if @db[:flor_pointers].columns.include?(:content)
|
795
|
+
# POINTER_COLUMNS + [ :content ]
|
796
|
+
# else
|
797
|
+
# POINTER_COLUMNS
|
798
|
+
# end
|
799
|
+
#end
|
772
800
|
|
773
801
|
def determine_type_and_schedule(message)
|
774
802
|
|
data/lib/flor.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Mettraux
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-11-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: munemo
|
@@ -249,6 +249,7 @@ files:
|
|
249
249
|
- lib/flor/to_string.rb
|
250
250
|
- lib/flor/tools/env.rb
|
251
251
|
- lib/flor/tools/firb.rb
|
252
|
+
- lib/flor/tools/flotojson.rb
|
252
253
|
- lib/flor/tools/shell.rb
|
253
254
|
- lib/flor/tools/shell_out.rb
|
254
255
|
- lib/flor/tt.rb
|