flor 0.15.0 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +14 -1
- data/CREDITS.md +1 -0
- data/LICENSE.txt +1 -1
- data/Makefile +6 -2
- data/README.md +2 -1
- data/flor.gemspec +12 -2
- data/lib/flor.rb +3 -3
- data/lib/flor/colours.rb +1 -1
- data/lib/flor/conf.rb +3 -4
- data/lib/flor/core/executor.rb +31 -61
- data/lib/flor/core/node.rb +213 -96
- data/lib/flor/core/procedure.rb +194 -75
- data/lib/flor/core/texecutor.rb +6 -7
- data/lib/flor/djan.rb +41 -22
- data/lib/flor/flor.rb +137 -42
- data/lib/flor/id.rb +77 -59
- data/lib/flor/log.rb +43 -22
- data/lib/flor/migrations/0001_tables.rb +7 -7
- data/lib/flor/parser.rb +271 -74
- data/lib/flor/pcore/_apply.rb +108 -0
- data/lib/flor/pcore/_atom.rb +2 -4
- data/lib/flor/pcore/_att.rb +54 -37
- data/lib/flor/pcore/_dmute.rb +18 -0
- data/lib/flor/pcore/_dol.rb +17 -0
- data/lib/flor/pcore/_dqs.rb +35 -0
- data/lib/flor/pcore/_head.rb +25 -0
- data/lib/flor/pcore/_obj.rb +1 -3
- data/lib/flor/pcore/_pat_guard.rb +1 -1
- data/lib/flor/pcore/_pat_obj.rb +11 -3
- data/lib/flor/pcore/_pat_regex.rb +16 -2
- data/lib/flor/pcore/_ref.rb +51 -0
- data/lib/flor/pcore/_rxs.rb +27 -0
- data/lib/flor/pcore/_val.rb +11 -6
- data/lib/flor/pcore/{logo.rb → andor.rb} +4 -6
- data/lib/flor/pcore/apply.rb +72 -2
- data/lib/flor/pcore/arith.rb +16 -4
- data/lib/flor/pcore/array_qmark.rb +100 -0
- data/lib/flor/pcore/break.rb +1 -2
- data/lib/flor/pcore/case.rb +1 -1
- data/lib/flor/pcore/cmp.rb +3 -2
- data/lib/flor/pcore/collect.rb +2 -2
- data/lib/flor/pcore/cond.rb +19 -1
- data/lib/flor/pcore/cursor.rb +12 -11
- data/lib/flor/pcore/define.rb +30 -4
- data/lib/flor/pcore/do_return.rb +3 -0
- data/lib/flor/pcore/flatten.rb +39 -0
- data/lib/flor/pcore/if.rb +15 -5
- data/lib/flor/pcore/includes.rb +5 -2
- data/lib/flor/pcore/inject.rb +1 -1
- data/lib/flor/pcore/iterator.rb +28 -18
- data/lib/flor/pcore/keys.rb +2 -2
- data/lib/flor/pcore/map.rb +19 -1
- data/lib/flor/pcore/match.rb +2 -2
- data/lib/flor/pcore/matchr.rb +18 -5
- data/lib/flor/pcore/max.rb +51 -0
- data/lib/flor/pcore/merge.rb +134 -0
- data/lib/flor/pcore/move.rb +1 -1
- data/lib/flor/pcore/noret.rb +1 -1
- data/lib/flor/pcore/not.rb +15 -1
- data/lib/flor/pcore/on.rb +11 -0
- data/lib/flor/pcore/on_cancel.rb +5 -1
- data/lib/flor/pcore/on_error.rb +69 -4
- data/lib/flor/pcore/push.rb +4 -9
- data/lib/flor/pcore/range.rb +5 -5
- data/lib/flor/pcore/reduce.rb +5 -18
- data/lib/flor/pcore/return.rb +26 -0
- data/lib/flor/pcore/reverse.rb +4 -0
- data/lib/flor/pcore/sequence.rb +8 -1
- data/lib/flor/pcore/set.rb +74 -15
- data/lib/flor/pcore/shuffle.rb +71 -0
- data/lib/flor/pcore/slice.rb +137 -0
- data/lib/flor/pcore/sort.rb +244 -0
- data/lib/flor/pcore/sort_by.rb +67 -0
- data/lib/flor/pcore/split.rb +39 -0
- data/lib/flor/pcore/stall.rb +1 -1
- data/lib/flor/pcore/strings.rb +123 -0
- data/lib/flor/pcore/timestamp.rb +34 -0
- data/lib/flor/pcore/to_array.rb +2 -3
- data/lib/flor/pcore/twig.rb +1 -1
- data/lib/flor/pcore/type_of.rb +37 -0
- data/lib/flor/pcore/until.rb +3 -3
- data/lib/flor/punit/cancel.rb +3 -3
- data/lib/flor/punit/ccollect.rb +29 -0
- data/lib/flor/punit/cmap.rb +76 -20
- data/lib/flor/punit/concurrence.rb +440 -33
- data/lib/flor/punit/cron.rb +1 -1
- data/lib/flor/punit/every.rb +1 -1
- data/lib/flor/punit/graft.rb +2 -3
- data/lib/flor/punit/on_timeout.rb +5 -1
- data/lib/flor/punit/part.rb +63 -0
- data/lib/flor/punit/schedule.rb +1 -1
- data/lib/flor/punit/task.rb +52 -10
- data/lib/flor/punit/trap.rb +4 -5
- data/lib/flor/tools/shell.rb +37 -18
- data/lib/flor/unit/caller.rb +23 -11
- data/lib/flor/unit/executor.rb +33 -12
- data/lib/flor/unit/ganger.rb +10 -1
- data/lib/flor/unit/hook.rb +2 -1
- data/lib/flor/unit/hooker.rb +13 -2
- data/lib/flor/unit/loader.rb +7 -7
- data/lib/flor/unit/logger.rb +15 -17
- data/lib/flor/unit/models.rb +4 -2
- data/lib/flor/unit/models/execution.rb +83 -38
- data/lib/flor/unit/models/message.rb +16 -0
- data/lib/flor/unit/models/pointer.rb +24 -0
- data/lib/flor/unit/models/timer.rb +25 -4
- data/lib/flor/unit/models/trace.rb +14 -0
- data/lib/flor/unit/models/trap.rb +39 -14
- data/lib/flor/unit/scheduler.rb +11 -7
- data/lib/flor/unit/storage.rb +55 -39
- data/lib/flor/unit/taskers.rb +17 -14
- data/lib/flor/unit/waiter.rb +4 -3
- metadata +40 -10
- data/lib/flor/changes.rb +0 -26
- data/lib/flor/dollar.rb +0 -224
- data/lib/flor/unit/hooks.rb +0 -37
@@ -2,6 +2,30 @@
|
|
2
2
|
module Flor
|
3
3
|
|
4
4
|
class Pointer < FlorModel
|
5
|
+
|
6
|
+
#create_table :flor_pointers do
|
7
|
+
#
|
8
|
+
# primary_key :id, type: :Integer
|
9
|
+
# String :domain, null: false
|
10
|
+
# String :exid, null: false
|
11
|
+
# String :nid, null: false
|
12
|
+
# String :type, null: false # task, tasked, tag, var
|
13
|
+
# String :name, null: false # task name, tasked name, tag name, var name
|
14
|
+
# String :value
|
15
|
+
# String :ctime, null: false
|
16
|
+
#
|
17
|
+
# # no :status, no :mtime
|
18
|
+
#
|
19
|
+
# index :exid
|
20
|
+
# index [ :exid, :nid ]
|
21
|
+
# index [ :type, :name, :value ]
|
22
|
+
#
|
23
|
+
# String :cunit
|
24
|
+
# String :munit
|
25
|
+
#
|
26
|
+
# #unique [ :exid, :type, :name, :value ]
|
27
|
+
# # we don't care, pointers are cleaned anyway when the flow dies
|
28
|
+
#end
|
5
29
|
end
|
6
30
|
end
|
7
31
|
|
@@ -3,6 +3,29 @@ module Flor
|
|
3
3
|
|
4
4
|
class Timer < FlorModel
|
5
5
|
|
6
|
+
#create_table :flor_timers do
|
7
|
+
#
|
8
|
+
# primary_key :id, type: :Integer
|
9
|
+
# String :domain, null: false
|
10
|
+
# String :exid, null: false
|
11
|
+
# String :nid, null: false
|
12
|
+
# String :type, null: false # 'at', 'in', 'cron', 'every', ...
|
13
|
+
# String :schedule, null: false # '20141128.103239' or '00 23 * * *'
|
14
|
+
# String :ntime # next time
|
15
|
+
# File :content # JSON msg to trigger
|
16
|
+
# Integer :count, null: false
|
17
|
+
# String :status, null: false
|
18
|
+
# String :ctime, null: false
|
19
|
+
# String :mtime, null: false
|
20
|
+
# String :cunit
|
21
|
+
# String :munit
|
22
|
+
# String :onid, null: false
|
23
|
+
# String :bnid, null: false
|
24
|
+
#
|
25
|
+
# index :exid
|
26
|
+
# index [ :exid, :nid ]
|
27
|
+
#end
|
28
|
+
|
6
29
|
def to_trigger_message
|
7
30
|
|
8
31
|
d = self.data(false)
|
@@ -12,8 +35,7 @@ module Flor
|
|
12
35
|
|
13
36
|
sm = d['m']
|
14
37
|
|
15
|
-
{
|
16
|
-
'point' => 'trigger',
|
38
|
+
{ 'point' => 'trigger',
|
17
39
|
'exid' => self.exid,
|
18
40
|
'nid' => self.onid,
|
19
41
|
'bnid' => self.nid,
|
@@ -21,8 +43,7 @@ module Flor
|
|
21
43
|
'schedule' => self.schedule,
|
22
44
|
'timer_id' => self.id,
|
23
45
|
'message' => m,
|
24
|
-
'sm' => sm
|
25
|
-
}
|
46
|
+
'sm' => sm }
|
26
47
|
end
|
27
48
|
|
28
49
|
def ntime_t
|
@@ -2,6 +2,20 @@
|
|
2
2
|
module Flor
|
3
3
|
|
4
4
|
class Trace < FlorModel
|
5
|
+
|
6
|
+
#create_table :flor_traces do
|
7
|
+
#
|
8
|
+
# primary_key :id, type: :Integer
|
9
|
+
# String :domain, null: false
|
10
|
+
# String :exid, null: false
|
11
|
+
# String :nid, null: true
|
12
|
+
# String :tracer, null: false # 'executor', 'trace'
|
13
|
+
# String :text, null: false # 'blah blah blah'
|
14
|
+
# String :ctime, null: false
|
15
|
+
# String :cunit
|
16
|
+
#
|
17
|
+
# index :exid
|
18
|
+
#end
|
5
19
|
end
|
6
20
|
end
|
7
21
|
|
@@ -3,6 +3,36 @@ module Flor
|
|
3
3
|
|
4
4
|
class Trap < FlorModel
|
5
5
|
|
6
|
+
#create_table :flor_traps do
|
7
|
+
#
|
8
|
+
# primary_key :id, type: :Integer
|
9
|
+
# String :domain, null: false
|
10
|
+
# String :exid, null: false
|
11
|
+
# String :onid, null: false
|
12
|
+
# String :nid, null: false
|
13
|
+
# #
|
14
|
+
# TrueClass :tconsumed, null: false, default: false
|
15
|
+
# String :trange, null: false
|
16
|
+
# String :tpoints, null: true
|
17
|
+
# String :ttags, null: true
|
18
|
+
# String :theats, null: true
|
19
|
+
# String :theaps, null: true
|
20
|
+
# #
|
21
|
+
# File :content # JSON msg to trigger
|
22
|
+
# #
|
23
|
+
# String :status, null: false
|
24
|
+
# String :ctime, null: false
|
25
|
+
# String :mtime, null: false
|
26
|
+
#
|
27
|
+
# String :cunit
|
28
|
+
# String :munit
|
29
|
+
#
|
30
|
+
# String :bnid, null: false
|
31
|
+
#
|
32
|
+
# index :exid
|
33
|
+
# index [ :exid, :nid ]
|
34
|
+
#end
|
35
|
+
|
6
36
|
def to_hook
|
7
37
|
|
8
38
|
opts = {}
|
@@ -72,19 +102,14 @@ module Flor
|
|
72
102
|
|
73
103
|
msg['trap_id'] = self.id
|
74
104
|
|
75
|
-
|
76
|
-
|
77
|
-
k = vs.keys.find { |k| k != 'arguments' } || 'msg'
|
78
|
-
vs[k] = message
|
105
|
+
args = msg['arguments'] = [ [ 'msg', message ] ]
|
79
106
|
|
80
|
-
|
81
|
-
|
82
|
-
end
|
107
|
+
if sig = (message['point'] == 'signal' && message['name'])
|
108
|
+
args << [ 'sig', sig ]
|
83
109
|
end
|
84
|
-
|
85
110
|
if dat['pl'] == 'event'
|
86
|
-
|
87
|
-
msg['payload'] = Flor.dup(message['payload'])
|
111
|
+
args << [ 'payload', msg['payload'] ]
|
112
|
+
msg['payload'] = Flor.dup(message['payload']) # FIXME try without this line...
|
88
113
|
end
|
89
114
|
|
90
115
|
{ 'point' => 'trigger',
|
@@ -95,15 +120,15 @@ module Flor
|
|
95
120
|
'trap_id' => self.id,
|
96
121
|
'message' => msg,
|
97
122
|
'sm' => message['m'] }
|
98
|
-
|
99
|
-
|
100
|
-
|
123
|
+
#.tap { |m| pp m }
|
124
|
+
#.tap { |m| pp m['message'] }
|
125
|
+
#'dbg' => xx }
|
101
126
|
end
|
102
127
|
|
103
128
|
def to_hash
|
104
129
|
|
105
130
|
values
|
106
|
-
.inject({}) { |h, (k, v)| h[k.to_s
|
131
|
+
.inject({}) { |h, (k, v)| h[k.to_s] = v if k != :content; h }
|
107
132
|
end
|
108
133
|
|
109
134
|
def do_split(v)
|
data/lib/flor/unit/scheduler.rb
CHANGED
@@ -150,7 +150,7 @@ module Flor
|
|
150
150
|
@thread_status = :running
|
151
151
|
|
152
152
|
@thread =
|
153
|
-
if @thread
|
153
|
+
if defined?(@thread) && @thread
|
154
154
|
@thread.run
|
155
155
|
else
|
156
156
|
Thread.new do
|
@@ -242,12 +242,16 @@ module Flor
|
|
242
242
|
|
243
243
|
def return(message)
|
244
244
|
|
245
|
-
|
246
|
-
'point'
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
245
|
+
m =
|
246
|
+
if message['point'] == 'failed'
|
247
|
+
message
|
248
|
+
else
|
249
|
+
message
|
250
|
+
.select { |k, _| %w[ exid nid payload tasker ].include?(k) }
|
251
|
+
.merge!('point' => 'return')
|
252
|
+
end
|
253
|
+
|
254
|
+
queue(m)
|
251
255
|
|
252
256
|
nil
|
253
257
|
end
|
data/lib/flor/unit/storage.rb
CHANGED
@@ -73,7 +73,15 @@ module Flor
|
|
73
73
|
end
|
74
74
|
end
|
75
75
|
|
76
|
-
def migrate(to=nil, from=nil)
|
76
|
+
def migrate(to=nil, from=nil, opts=nil)
|
77
|
+
|
78
|
+
opts = [ to, from, opts ].find { |e| e.is_a?(Hash) } || {}
|
79
|
+
opts[:target] ||= to if to.is_a?(Integer)
|
80
|
+
opts[:current] ||= from if from.is_a?(Integer)
|
81
|
+
#
|
82
|
+
# defaults for the migration version table:
|
83
|
+
#:table => :schema_info
|
84
|
+
#:column => :version
|
77
85
|
|
78
86
|
dir =
|
79
87
|
@unit.conf['db_migrations'] ||
|
@@ -83,13 +91,7 @@ module Flor
|
|
83
91
|
|
84
92
|
synchronize do
|
85
93
|
|
86
|
-
Sequel::Migrator.run(
|
87
|
-
@db, dir,
|
88
|
-
:target => to, :current => from)
|
89
|
-
|
90
|
-
# defaults for the migration version table:
|
91
|
-
#:table => :schema_info
|
92
|
-
#:column => :version
|
94
|
+
Sequel::Migrator.run(@db, dir, opts)
|
93
95
|
end
|
94
96
|
end
|
95
97
|
|
@@ -107,9 +109,8 @@ module Flor
|
|
107
109
|
.first(exid: exid) # status active or terminated doesn't matter
|
108
110
|
|
109
111
|
return {
|
110
|
-
'exid' => exid, 'nodes' => {}, '
|
111
|
-
'
|
112
|
-
'size' => -1
|
112
|
+
'exid' => exid, 'nodes' => {}, 'counters' => {},
|
113
|
+
'start' => Flor.tstamp, 'size' => 0
|
113
114
|
} unless e
|
114
115
|
|
115
116
|
ex = from_blob(e[:content])
|
@@ -117,7 +118,7 @@ module Flor
|
|
117
118
|
fail("couldn't parse execution (db id #{e[:id].to_i})") unless ex
|
118
119
|
|
119
120
|
ex['id'] = e[:id].to_i
|
120
|
-
ex['size'] = e[:content].
|
121
|
+
ex['size'] = e[:content].size
|
121
122
|
|
122
123
|
ex
|
123
124
|
end
|
@@ -141,7 +142,8 @@ module Flor
|
|
141
142
|
end
|
142
143
|
|
143
144
|
data = to_blob(exe)
|
144
|
-
exe['size'] = data.
|
145
|
+
exe['size'] = data.size
|
146
|
+
|
145
147
|
u = @unit.identifier
|
146
148
|
|
147
149
|
transync do
|
@@ -179,6 +181,7 @@ module Flor
|
|
179
181
|
end
|
180
182
|
|
181
183
|
exe
|
184
|
+
# return the execution hash
|
182
185
|
|
183
186
|
rescue => err
|
184
187
|
|
@@ -309,14 +312,29 @@ module Flor
|
|
309
312
|
|
310
313
|
synchronize(syn) do
|
311
314
|
|
315
|
+
stored, unstored = ms.partition { |m| m['mid'] }
|
316
|
+
|
317
|
+
#
|
318
|
+
# de-reserve any previously stored message, might happen
|
319
|
+
# for "terminated" messages that got queued back to let
|
320
|
+
# other messages get processed
|
321
|
+
|
322
|
+
@db[:flor_messages]
|
323
|
+
.where(id: stored.collect { |m| m['mid'] })
|
324
|
+
.update(status: 'created', mtime: n, munit: u) \
|
325
|
+
if stored.any?
|
326
|
+
|
327
|
+
#
|
328
|
+
# store new messages
|
329
|
+
|
312
330
|
@db[:flor_messages]
|
313
331
|
.import(
|
314
332
|
[ :domain, :exid, :point, :content,
|
315
333
|
:status, :ctime, :mtime, :cunit, :munit ],
|
316
|
-
|
334
|
+
unstored.map { |m|
|
317
335
|
[ Flor.domain(m['exid']), m['exid'], m['point'], to_blob(m),
|
318
|
-
'created', n, n, u, u ]
|
319
|
-
|
336
|
+
'created', n, n, u, u ] }) \
|
337
|
+
if unstored.any?
|
320
338
|
end
|
321
339
|
|
322
340
|
@unit.wake_up
|
@@ -646,7 +664,8 @@ module Flor
|
|
646
664
|
|
647
665
|
def update_pointers(exe, status, now)
|
648
666
|
|
649
|
-
# TODO archive old pointers
|
667
|
+
# TODO should we archive old pointers?
|
668
|
+
|
650
669
|
exid = exe['exid']
|
651
670
|
|
652
671
|
if status == 'terminated'
|
@@ -666,30 +685,27 @@ module Flor
|
|
666
685
|
dom = Flor.domain(exid)
|
667
686
|
u = @unit.identifier
|
668
687
|
|
669
|
-
pointers =
|
670
|
-
|
688
|
+
pointers = exe['nodes']
|
689
|
+
.inject([]) { |a, (nid, node)|
|
690
|
+
|
671
691
|
ts = node['tags']
|
672
|
-
ts.each { |t|
|
673
|
-
|
674
|
-
}
|
692
|
+
ts.each { |t|
|
693
|
+
a << [ dom, exid, nid, 'tag', t, nil, now, u ] } if ts
|
675
694
|
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
695
|
+
vs = nid == '0' ? node['vars'] : nil
|
696
|
+
vs.each { |k, v|
|
697
|
+
case v; when Integer, String, TrueClass, FalseClass
|
698
|
+
a << [ dom, exid, '0', 'var', k, v.to_s, now, u ]
|
699
|
+
when NilClass
|
700
|
+
a << [ dom, exid, '0', 'var', k, nil, now, u ]
|
701
|
+
end } if vs
|
702
|
+
|
703
|
+
#ta = node['heap'] == 'task' ? node['task'] : nil
|
704
|
+
ta = node['task']
|
705
|
+
a << [ dom, exid, nid, 'tasker', ta['tasker'], ta['name'], now, u ] \
|
706
|
+
if ta
|
686
707
|
|
687
|
-
|
688
|
-
exe['tasks']
|
689
|
-
.reject { |_, v|
|
690
|
-
v['tasker'] == nil }
|
691
|
-
.collect { |nid, v|
|
692
|
-
[ dom, exid, nid, 'tasker', v['tasker'], v['name'], now, u ] }
|
708
|
+
a }
|
693
709
|
|
694
710
|
cps = @db[:flor_pointers] # current pointers
|
695
711
|
.where(exid: exid)
|
@@ -770,7 +786,7 @@ module Flor
|
|
770
786
|
|
771
787
|
@db.extension(:connection_validator)
|
772
788
|
@db.pool.connection_validation_timeout = to
|
773
|
-
# NB: -1 means "check
|
789
|
+
# NB: -1 means "check at every use"
|
774
790
|
end
|
775
791
|
|
776
792
|
@db_logger = DbLogger.new(@unit)
|
data/lib/flor/unit/taskers.rb
CHANGED
@@ -14,12 +14,6 @@ module Flor
|
|
14
14
|
|
15
15
|
protected
|
16
16
|
|
17
|
-
def return(force=false)
|
18
|
-
|
19
|
-
@ganger.return(@message) if force || @ganger
|
20
|
-
end
|
21
|
-
alias reply return
|
22
|
-
|
23
17
|
def exid; @message['exid']; end
|
24
18
|
def nid; @message['nid']; end
|
25
19
|
|
@@ -46,22 +40,31 @@ module Flor
|
|
46
40
|
|
47
41
|
if name.is_a?(String)
|
48
42
|
|
49
|
-
[
|
50
|
-
Flor.dup_and_merge(
|
43
|
+
[ Flor.dup_and_merge(
|
51
44
|
@message,
|
52
45
|
'tasker' => name, 'original_tasker' => @message['tasker'],
|
53
|
-
'routed' => true)
|
54
|
-
]
|
46
|
+
'routed' => true) ]
|
55
47
|
|
56
48
|
else
|
57
49
|
|
58
|
-
[
|
59
|
-
Flor.dup_and_merge(
|
50
|
+
[ Flor.dup_and_merge(
|
60
51
|
@message,
|
61
|
-
'routed' => !! name)
|
62
|
-
]
|
52
|
+
'routed' => !! name) ]
|
63
53
|
end
|
64
54
|
end
|
55
|
+
|
56
|
+
def reply(message=@message, force=false)
|
57
|
+
|
58
|
+
@ganger.return(message) if force || @ganger
|
59
|
+
|
60
|
+
[] # very important, return no further messages
|
61
|
+
end
|
62
|
+
|
63
|
+
def reply_with_error(error)
|
64
|
+
|
65
|
+
reply(
|
66
|
+
Flor.to_error_message(@message, error))
|
67
|
+
end
|
65
68
|
end
|
66
69
|
end
|
67
70
|
|
data/lib/flor/unit/waiter.rb
CHANGED
@@ -63,7 +63,8 @@ module Flor
|
|
63
63
|
|
64
64
|
if @queue.empty?
|
65
65
|
fail RuntimeError.new(
|
66
|
-
"timeout for #{self.to_s}"
|
66
|
+
"timeout for #{self.to_s}"
|
67
|
+
) if @on_timeout == 'fail'
|
67
68
|
return { 'exid' => @exid, 'timed_out' => @on_timeout }
|
68
69
|
end
|
69
70
|
end
|
@@ -123,8 +124,8 @@ module Flor
|
|
123
124
|
return s if s.is_a?(Array) && s.collect(&:class).uniq == [ Array ]
|
124
125
|
|
125
126
|
(s.is_a?(String) ? s.split(';') : s)
|
126
|
-
.collect { |
|
127
|
-
ni, pt =
|
127
|
+
.collect { |ss|
|
128
|
+
ni, pt = ss.strip.match(/\A([0-9_\-]+)? *([a-z|, ]+)\z/)[1, 2]
|
128
129
|
[ ni, pt.split(/[|,]/).collect(&:strip) ]
|
129
130
|
}
|
130
131
|
end
|