flor 0.16.1 → 0.16.2
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 +8 -0
- data/CREDITS.md +1 -0
- data/Makefile +1 -1
- data/README.md +82 -6
- data/lib/flor.rb +1 -1
- data/lib/flor/conf.rb +19 -6
- data/lib/flor/core/executor.rb +45 -16
- data/lib/flor/core/node.rb +4 -4
- data/lib/flor/core/procedure.rb +40 -0
- data/lib/flor/djan.rb +5 -2
- data/lib/flor/flor.rb +92 -7
- data/lib/flor/id.rb +19 -0
- data/lib/flor/migrations/0001_tables.rb +6 -6
- data/lib/flor/migrations/0005_pointer_content.rb +20 -0
- data/lib/flor/pcore/_apply.rb +103 -57
- data/lib/flor/pcore/_att.rb +15 -1
- data/lib/flor/pcore/_ref.rb +2 -1
- data/lib/flor/pcore/arith.rb +46 -9
- data/lib/flor/pcore/break.rb +1 -1
- data/lib/flor/pcore/case.rb +41 -0
- data/lib/flor/pcore/collect.rb +1 -1
- data/lib/flor/pcore/cursor.rb +1 -1
- data/lib/flor/pcore/define.rb +32 -6
- data/lib/flor/pcore/iterator.rb +12 -0
- data/lib/flor/pcore/on_cancel.rb +1 -1
- data/lib/flor/pcore/set.rb +14 -4
- data/lib/flor/punit/{ccollect.rb → c_collect.rb} +2 -2
- data/lib/flor/punit/c_each.rb +11 -0
- data/lib/flor/punit/c_for_each.rb +41 -0
- data/lib/flor/punit/c_iterator.rb +160 -0
- data/lib/flor/punit/c_map.rb +43 -0
- data/lib/flor/punit/concurrence.rb +43 -200
- data/lib/flor/punit/graft.rb +3 -2
- data/lib/flor/punit/m_ram.rb +281 -0
- data/lib/flor/unit.rb +1 -0
- data/lib/flor/unit/caller.rb +6 -1
- data/lib/flor/unit/executor.rb +17 -4
- data/lib/flor/unit/ganger.rb +12 -1
- data/lib/flor/unit/hloader.rb +251 -0
- data/lib/flor/unit/hook.rb +74 -15
- data/lib/flor/unit/hooker.rb +9 -12
- data/lib/flor/unit/loader.rb +41 -17
- data/lib/flor/unit/models.rb +54 -18
- data/lib/flor/unit/models/execution.rb +15 -4
- data/lib/flor/unit/models/pointer.rb +11 -0
- data/lib/flor/unit/scheduler.rb +126 -30
- data/lib/flor/unit/spooler.rb +5 -3
- data/lib/flor/unit/storage.rb +40 -13
- data/lib/flor/unit/waiter.rb +165 -26
- data/lib/flor/unit/wlist.rb +98 -5
- metadata +10 -4
- data/lib/flor/punit/cmap.rb +0 -112
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0c913a70a19f7d01588c3cd927cb529d64538177
|
4
|
+
data.tar.gz: 25d6262fe8024c0159a172f392ef65b203f72a1a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1d9834e300f1d5158f42027e51966efcfc500edeedf3c7495a42985b77d24dbcd87aafdd1061776c04cc0713181ced907d535cc5c59032b13b355211fab2dad8
|
7
|
+
data.tar.gz: ecdd517230e0f0602fb8b617b9d57ce94938864a30ecd7df827dab486de1f85de55ef296f8d0964fc878ccd764685230d2d73bea0e3c9aa53e144ccf52df8dee
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,14 @@
|
|
2
2
|
# CHANGELOG.md
|
3
3
|
|
4
4
|
|
5
|
+
## flor 0.16.2 not yet released
|
6
|
+
|
7
|
+
- Allow for `[ 'he' 'll' 'o' ] | + join: '.'` (yields "he.ll.o")
|
8
|
+
- Allow for `[ 1 2 3 ] | + _` (yields `6`)
|
9
|
+
- Make "child_on_error:"/"children_on_error:" a common attribute
|
10
|
+
- Ensure "on_cancel" sets only one handler
|
11
|
+
|
12
|
+
|
5
13
|
## flor 0.16.1 released 2019-02-05
|
6
14
|
|
7
15
|
- Depend on Sequel 5 (Sequel 4 and 5 seem OK)
|
data/CREDITS.md
CHANGED
data/Makefile
CHANGED
@@ -71,7 +71,7 @@ mk:
|
|
71
71
|
$(RUBY) -Ilib -e "require 'flor/tools/env'; Flor::Tools::Env.make('tmp', '$(FLOR_ENV)', gitkeep: true)"
|
72
72
|
|
73
73
|
doc:
|
74
|
-
$(RUBY) -Imak -r '
|
74
|
+
$(RUBY) -Imak -r 'doc_procedures' -e "make_doc_procedures()"
|
75
75
|
doct:
|
76
76
|
@$(RUBY) mak/ptree.rb
|
77
77
|
|
data/README.md
CHANGED
@@ -10,10 +10,11 @@ Flor is a "Ruby workflow engine", if that makes any sense.
|
|
10
10
|
* [floraison mailing list](https://groups.google.com/forum/#!forum/floraison)
|
11
11
|
* [twitter.com/@flor_workflow](https://twitter.com/flor_workflow)
|
12
12
|
|
13
|
-
|
13
|
+
|
14
|
+
## design
|
14
15
|
|
15
16
|
* Strives to propose a scheming interpreter for long running executions
|
16
|
-
* Is written in Ruby a rather straightforward language with at least two
|
17
|
+
* Is written in Ruby, a rather straightforward language with at least two
|
17
18
|
wonderful implementations (MRI and JRuby, which is enterprise-friendly)
|
18
19
|
* Stores everything as JSON (if it breaks it's still readable)
|
19
20
|
* Stores in any database supported by [Sequel](http://sequel.jeremyevans.net/)
|
@@ -22,14 +23,89 @@ Flor is a "Ruby workflow engine", if that makes any sense.
|
|
22
23
|
* All in all should be easy to maintain (engine itself and executions running
|
23
24
|
on top of it)
|
24
25
|
|
25
|
-
## Quickstart
|
26
26
|
|
27
|
-
|
27
|
+
## quickstart
|
28
|
+
|
29
|
+
This quickstart sets up a flor unit tied to a SQLite database, resets the databse, binds two taskers and then launches a flow execution involving the two taskers. Finally, it prints out the resulting workitem as the execution has just terminated.
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
require 'flor/unit'
|
33
|
+
|
34
|
+
#ENV['FLOR_DEBUG'] = 'dbg,sto,stdout' # full sql + flor debug output
|
35
|
+
#ENV['FLOR_DEBUG'] = 'dbg,stdout' # flor debug output
|
36
|
+
# uncomment to see the flor activity
|
37
|
+
|
38
|
+
sto_uri = 'sqlite://flor_qs.db'
|
39
|
+
sto_uri = 'jdbc:sqlite://flor_qs.db' if RUBY_PLATFORM.match(/java/)
|
40
|
+
|
41
|
+
flor = Flor::Unit.new(loader: Flor::HashLoader, sto_uri: sto_uri)
|
42
|
+
# instantiate flor unit
|
43
|
+
|
44
|
+
flor.storage.delete_tables
|
45
|
+
flor.storage.migrate
|
46
|
+
# blank slate database
|
47
|
+
|
48
|
+
class DemoTasker < Flor::BasicTasker
|
49
|
+
def task(message)
|
50
|
+
(attd['times'] || 1).times do
|
51
|
+
message['payload']['log'] << "#{tasker}: #{task_name}"
|
52
|
+
end
|
53
|
+
reply
|
54
|
+
end
|
55
|
+
end
|
56
|
+
flor.add_tasker(:alice, DemoTasker)
|
57
|
+
flor.add_tasker(:bob, DemoTasker)
|
58
|
+
# a simple logging tasker implementation bound under
|
59
|
+
# two different tasker names
|
60
|
+
|
61
|
+
flor.start
|
62
|
+
# start the flor unit, so that it can process executions
|
63
|
+
|
64
|
+
exid = flor.launch(
|
65
|
+
%q{
|
66
|
+
sequence
|
67
|
+
alice 'hello' times: 2
|
68
|
+
bob 'world'
|
69
|
+
},
|
70
|
+
payload: { log: [ "started at #{Time.now}" ] })
|
71
|
+
# launch a new execution, one that chains alice and bob work
|
28
72
|
|
29
|
-
|
73
|
+
#r = flor.wait(exid, 'terminated')
|
74
|
+
r = flor.wait(exid)
|
75
|
+
# wait for the execution to terminate or to fail
|
76
|
+
|
77
|
+
p r['point']
|
78
|
+
# "terminated" hopefully
|
79
|
+
p r['payload']['log']
|
80
|
+
# [ "started at 2019-03-31 10:20:18 +0900",
|
81
|
+
# "alice: hello", "alice: hello",
|
82
|
+
# "bob: world" ]
|
83
|
+
```
|
84
|
+
|
85
|
+
This quickstart is at [doc/quickstart0/](doc/quickstart0/), it's a minimal, one-file Ruby quickstart.
|
86
|
+
|
87
|
+
There is also [doc/quickstart1/](doc/quickstart1/), a more complex example, that shows a flor setup, where taskers and flows are layed out in a flor directory tree.
|
88
|
+
|
89
|
+
|
90
|
+
## documentation
|
30
91
|
|
31
92
|
See [doc/](doc/).
|
32
93
|
|
94
|
+
* [doc/procedures/](doc/procedures/#procedures) - the basic building blocks of the flor language
|
95
|
+
* [doc/glossary](doc/glossary.md) - words and their meaning in the flor context
|
96
|
+
* [doc/patterns](doc/patterns.md) - workflow patterns and their flor (tentative) implementations
|
97
|
+
|
98
|
+
|
99
|
+
## related projects
|
100
|
+
|
101
|
+
* [mantor/floristry](https://github.com/mantor/floristry) - visualize and interact with flor through Rails facilities
|
102
|
+
* [floraison/pollen](https://github.com/floraison/pollen) - a set of flor hooks that emit over the http
|
103
|
+
* [floraison/florist](https://github.com/floraison/florist) - a flor worklist implementation
|
104
|
+
* [floraison/flack](https://github.com/floraison/flack) - a flor wrapping [Rack](https://github.com/rack/rack) app
|
105
|
+
* [floraison/fugit](https://github.com/floraison/fugit) - a time library for flor and [rufus-scheduler](https://github.com/jmettraux/rufus-scheduler)
|
106
|
+
* [floraison/raabro](https://github.com/floraison/raabro) - the PEG library flor uses for its parsing needs
|
107
|
+
|
108
|
+
|
33
109
|
## blog posts and presentations
|
34
110
|
|
35
111
|
* [the flor language](http://jmettraux.skepti.ch/20180927.html?t=the_flor_language) - on the flor workflow definition language itself
|
@@ -39,7 +115,7 @@ See [doc/](doc/).
|
|
39
115
|
* [flor 2017](https://speakerdeck.com/jmettraux/flor-2017) - q1 2017 - very dry deck
|
40
116
|
|
41
117
|
|
42
|
-
##
|
118
|
+
## license
|
43
119
|
|
44
120
|
MIT, see [LICENSE.txt](LICENSE.txt)
|
45
121
|
|
data/lib/flor.rb
CHANGED
data/lib/flor/conf.rb
CHANGED
@@ -48,6 +48,15 @@ module Flor
|
|
48
48
|
# "reserved" state, messages are put back in the "created" state
|
49
49
|
# (by a running unit (scheduler) if any).
|
50
50
|
#
|
51
|
+
# * :db_migration / :sto_migration / :db_migration_dir / :sto_migration_dir
|
52
|
+
# (Flor::Storage#migrate option)
|
53
|
+
# Points the migrator to its Ruby Sequel migration directory.
|
54
|
+
#
|
55
|
+
# * :db_sparse_migrations / :sto_sparse_migrations
|
56
|
+
# (Flor::Storage#migrate option)
|
57
|
+
# Setting this to true is equivalent to calling
|
58
|
+
# `unit.storage.migration(allow_missing_migration_files: true)`
|
59
|
+
#
|
51
60
|
# And finally:
|
52
61
|
#
|
53
62
|
# * :flor_debug or :debug
|
@@ -71,8 +80,12 @@ module Flor
|
|
71
80
|
|
72
81
|
def prepare(conf, over_conf)
|
73
82
|
|
74
|
-
c =
|
75
|
-
|
83
|
+
c =
|
84
|
+
case conf
|
85
|
+
when String then Flor::ConfExecutor.interpret_path_or_source(conf)
|
86
|
+
when Hash then Flor.to_string_keyed_hash(conf)
|
87
|
+
else conf
|
88
|
+
end
|
76
89
|
|
77
90
|
fail ArgumentError.new(
|
78
91
|
"cannot extract conf out of #{c.inspect} (#{conf.class})"
|
@@ -92,10 +105,10 @@ module Flor
|
|
92
105
|
|
93
106
|
def get_class(conf, key)
|
94
107
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
108
|
+
case v = conf[key]
|
109
|
+
when Class then v
|
110
|
+
when String then Flor.const_lookup(v)
|
111
|
+
else nil
|
99
112
|
end
|
100
113
|
end
|
101
114
|
|
data/lib/flor/core/executor.rb
CHANGED
@@ -69,18 +69,31 @@ module Flor
|
|
69
69
|
|
70
70
|
def trigger_hook(hook, message)
|
71
71
|
|
72
|
-
|
72
|
+
m =
|
73
|
+
case
|
74
|
+
when hook.respond_to?(:notify) then :notify
|
75
|
+
when hook.respond_to?(:on_message) then :on_message
|
76
|
+
else :on
|
77
|
+
end
|
78
|
+
as =
|
79
|
+
case hook.method(m).arity
|
80
|
+
when 3 then [ @unit, self, message ]
|
81
|
+
when 2 then [ self, message ]
|
82
|
+
else [ message ]
|
83
|
+
end
|
84
|
+
|
85
|
+
r = hook.send(m, *as)
|
86
|
+
|
87
|
+
Flor.is_array_of_messages?(r) ? r : []
|
73
88
|
end
|
74
89
|
|
75
90
|
def trigger_block(block, opts, message)
|
76
91
|
|
77
92
|
r =
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
else
|
83
|
-
block.call(self, message, opts)
|
93
|
+
case block.arity
|
94
|
+
when 1 then block.call(message)
|
95
|
+
when 2 then block.call(message, opts)
|
96
|
+
else block.call(self, message, opts)
|
84
97
|
end
|
85
98
|
|
86
99
|
r.is_a?(Array) && r.all? { |e| e.is_a?(Hash) } ? r : []
|
@@ -146,9 +159,10 @@ module Flor
|
|
146
159
|
# cnid: closure nid
|
147
160
|
# dbg: used to debug messages (useful @node['dbg'] when 'receive')
|
148
161
|
|
149
|
-
|
150
|
-
|
151
|
-
|
162
|
+
%w[ error cancel timeout ]
|
163
|
+
.each { |k|
|
164
|
+
h = message["on_#{k}_handler"]
|
165
|
+
node["on_#{k}"] = [ h ] if h }
|
152
166
|
|
153
167
|
@execution['nodes'][nid] = node
|
154
168
|
end
|
@@ -228,6 +242,19 @@ module Flor
|
|
228
242
|
heap = node['heap']
|
229
243
|
|
230
244
|
heac = Flor::Procedure[heap]
|
245
|
+
unless heac
|
246
|
+
puts "v" * 80
|
247
|
+
puts "===node:"
|
248
|
+
p node['nid']
|
249
|
+
p heap
|
250
|
+
puts "."
|
251
|
+
pp node
|
252
|
+
puts "===message:"
|
253
|
+
p message['point']
|
254
|
+
puts "."
|
255
|
+
pp message
|
256
|
+
puts "." * 80
|
257
|
+
end
|
231
258
|
fail NameError.new("unknown procedure #{heap.inspect}") unless heac
|
232
259
|
|
233
260
|
head = heac.new(self, node, message)
|
@@ -426,7 +453,7 @@ module Flor
|
|
426
453
|
ms = []
|
427
454
|
ms += @unit.notify(self, message) # pre
|
428
455
|
|
429
|
-
ms +=
|
456
|
+
ms += send(message['point'], message)
|
430
457
|
|
431
458
|
message['payload'] = message.delete('pld') if message.has_key?('pld')
|
432
459
|
message['consumed'] = Flor.tstamp
|
@@ -456,11 +483,6 @@ module Flor
|
|
456
483
|
[]
|
457
484
|
end
|
458
485
|
|
459
|
-
def entered(message); []; end
|
460
|
-
def left(message); []; end
|
461
|
-
|
462
|
-
def ceased(message); []; end
|
463
|
-
|
464
486
|
def terminated(message)
|
465
487
|
|
466
488
|
message['vars'] = @execution['nodes']['0']['vars']
|
@@ -496,6 +518,13 @@ module Flor
|
|
496
518
|
end
|
497
519
|
|
498
520
|
def signal(message); []; end
|
521
|
+
def entered(message); []; end
|
522
|
+
def left(message); []; end
|
523
|
+
def ceased(message); []; end
|
524
|
+
#
|
525
|
+
# Return an empty array of new messages. No direct effect.
|
526
|
+
#
|
527
|
+
# Some trap, hook, and/or waiter might lie in wait though.
|
499
528
|
|
500
529
|
def lookup_on_error_parent(message)
|
501
530
|
|
data/lib/flor/core/node.rb
CHANGED
@@ -441,12 +441,12 @@ class Flor::Node
|
|
441
441
|
if node == nil || mod == 'd'
|
442
442
|
|
443
443
|
return lookup_arg_container(key) \
|
444
|
-
if mod == '' && %w[ args argv argh ].include?(key)
|
444
|
+
if mod == '' && %w[ arga args argv argh argd ].include?(key)
|
445
445
|
|
446
|
-
if vwl = node['vwlist']
|
446
|
+
if vwl = node['vwlist'] # variable white list
|
447
447
|
return lookup_dvar_container(mod, key) unless var_match?(vwl, key)
|
448
448
|
end
|
449
|
-
if vbl = node['vblist']
|
449
|
+
if vbl = node['vblist'] # variable black list
|
450
450
|
return lookup_dvar_container(mod, key) if var_match?(vbl, key)
|
451
451
|
end
|
452
452
|
|
@@ -502,7 +502,7 @@ class Flor::Node
|
|
502
502
|
|
503
503
|
val =
|
504
504
|
case key
|
505
|
-
when 'args', 'argv' then args.collect(&:last)
|
505
|
+
when 'arga', 'args', 'argv' then args.collect(&:last)
|
506
506
|
else args.inject({}) { |h, (k, v)| h[k] = v if k; h }
|
507
507
|
end
|
508
508
|
|
data/lib/flor/core/procedure.rb
CHANGED
@@ -615,6 +615,13 @@ class Flor::Procedure < Flor::Node
|
|
615
615
|
# was considering passing the whole vars back (as 'varz'), but
|
616
616
|
# it got in the way... and it might be heavy
|
617
617
|
|
618
|
+
%w[ error cancel timeout ]
|
619
|
+
.each { |k|
|
620
|
+
co = @node["child_on_#{k}"]
|
621
|
+
next unless co
|
622
|
+
kri = [ '*' ]
|
623
|
+
m["on_#{k}_handler"] = [ kri, co ] }
|
624
|
+
|
618
625
|
[ m ]
|
619
626
|
end
|
620
627
|
|
@@ -787,6 +794,10 @@ class Flor::Procedure < Flor::Node
|
|
787
794
|
#
|
788
795
|
# an idea from "sort" apply, may be useful later on...
|
789
796
|
|
797
|
+
if pl = opts[:payload]
|
798
|
+
ms.first['payload'] = pl
|
799
|
+
end
|
800
|
+
|
790
801
|
ms
|
791
802
|
end
|
792
803
|
|
@@ -893,6 +904,35 @@ class Flor::Procedure < Flor::Node
|
|
893
904
|
wrap_cancel_children('flavour' => 'kill') +
|
894
905
|
wrap_cancelled
|
895
906
|
end
|
907
|
+
|
908
|
+
def do_add
|
909
|
+
|
910
|
+
return [] unless node_open?
|
911
|
+
# if the node is closed or ended, discard the add message
|
912
|
+
|
913
|
+
add
|
914
|
+
end
|
915
|
+
|
916
|
+
def add
|
917
|
+
|
918
|
+
fail Flor::FlorError.new(
|
919
|
+
"procedure does not accept add-iteration", self
|
920
|
+
) if message['elements']
|
921
|
+
|
922
|
+
# TODO fail if the procedure changed
|
923
|
+
# could the message contain a SHA for the node as was when the
|
924
|
+
# that message got emitted?
|
925
|
+
# well, could that be applied to other messages? too late?
|
926
|
+
|
927
|
+
#puts " === add " + ("=" * 40)
|
928
|
+
#pp message
|
929
|
+
#puts " === add. " + ("=" * 39)
|
930
|
+
t = tree
|
931
|
+
i = Flor.child_id(message['tnid'])
|
932
|
+
t[1].insert(i, *message['trees'])
|
933
|
+
|
934
|
+
[]
|
935
|
+
end
|
896
936
|
end
|
897
937
|
|
898
938
|
|
data/lib/flor/djan.rb
CHANGED
@@ -88,10 +88,13 @@ module Flor
|
|
88
88
|
|
89
89
|
if kt = opts[:keytab]
|
90
90
|
out << ' ' * kt
|
91
|
+
:indent
|
91
92
|
elsif opts[:indent]
|
92
93
|
newline(out, opts)
|
94
|
+
:newline
|
93
95
|
elsif ! opts[:compact]
|
94
96
|
space(out, opts)
|
97
|
+
:space
|
95
98
|
end
|
96
99
|
end
|
97
100
|
|
@@ -152,9 +155,9 @@ module Flor
|
|
152
155
|
c_inf(':', out, opts)
|
153
156
|
|
154
157
|
kt = key_max_len ? key_max_len - kl : nil
|
155
|
-
newline_or_space(out, opts.merge(keytab: kt))
|
158
|
+
r = newline_or_space(out, opts.merge(keytab: kt))
|
156
159
|
|
157
|
-
to_d(v, out, indent(opts, inc: 2, keytab: kt))
|
160
|
+
to_d(v, out, indent(opts, inc: 2, keytab: r == :newline ? kt : 1))
|
158
161
|
|
159
162
|
if ii < x.size - 1
|
160
163
|
c_inf(',', out, opts)
|
data/lib/flor/flor.rb
CHANGED
@@ -52,15 +52,29 @@ module Flor
|
|
52
52
|
end
|
53
53
|
def dupm(h, hh); self.dup_and_merge(h, hh); end
|
54
54
|
|
55
|
-
def
|
55
|
+
def deep_merge(o0, o1, in_place=false)
|
56
56
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
57
|
+
t0 = type(o0)
|
58
|
+
t1 = type(o1)
|
59
|
+
|
60
|
+
return o1 if t1 != t0
|
61
|
+
|
62
|
+
if t0 == :array
|
63
|
+
o1.each_with_index.inject(in_place ? o0 : o0.dup) { |a, (e1, i)|
|
64
|
+
a[i] = deep_merge(o0[i], e1, in_place)
|
65
|
+
a }
|
66
|
+
elsif t0 == :object
|
67
|
+
o1.inject(in_place ? o0 : o0.dup) { |h, (k, v1)|
|
68
|
+
h[k] = deep_merge(o0[k], v1, in_place)
|
69
|
+
h }
|
70
|
+
else
|
71
|
+
o1
|
61
72
|
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def deep_merge!(o0, o1)
|
62
76
|
|
63
|
-
|
77
|
+
deep_merge(o0, o1, true)
|
64
78
|
end
|
65
79
|
|
66
80
|
def false?(o)
|
@@ -314,7 +328,14 @@ module Flor
|
|
314
328
|
"not a sub domain #{sub.inspect}"
|
315
329
|
) unless potential_domain_name?(sub)
|
316
330
|
|
317
|
-
|
331
|
+
sub_domain?(dom, sub)
|
332
|
+
end
|
333
|
+
|
334
|
+
def sub_domain?(dom, sub)
|
335
|
+
|
336
|
+
dom == '' ||
|
337
|
+
sub == dom ||
|
338
|
+
sub[0, dom.length + 1] == dom + '.'
|
318
339
|
end
|
319
340
|
|
320
341
|
def split_domain_unit(s)
|
@@ -443,6 +464,36 @@ module Flor
|
|
443
464
|
o[1].match(/\A\/.*\/[a-zA-Z]*\z/)
|
444
465
|
end
|
445
466
|
|
467
|
+
def is_sqs_tree?(o)
|
468
|
+
|
469
|
+
o.is_a?(Array) &&
|
470
|
+
o[0] == '_sqs' &&
|
471
|
+
o[2].is_a?(Integer) &&
|
472
|
+
o[1].is_a?(String)
|
473
|
+
end
|
474
|
+
|
475
|
+
def is_num_tree?(o)
|
476
|
+
|
477
|
+
o.is_a?(Array) &&
|
478
|
+
o[0] == '_num' &&
|
479
|
+
o[2].is_a?(Integer) &&
|
480
|
+
o[1].is_a?(Numeric)
|
481
|
+
end
|
482
|
+
|
483
|
+
def is_ref_tree?(o)
|
484
|
+
|
485
|
+
o.is_a?(Array) &&
|
486
|
+
(o[0] == '_ref' || o[0] == '_reff') &&
|
487
|
+
o[2].is_a?(Integer) &&
|
488
|
+
o[1].is_a?(Array) &&
|
489
|
+
o[1].all? { |e| is_sqs_tree?(e) || is_num_tree?(e) }
|
490
|
+
end
|
491
|
+
|
492
|
+
def ref_to_path(t)
|
493
|
+
|
494
|
+
t[1].collect { |tt| tt[1].to_s }.join('.')
|
495
|
+
end
|
496
|
+
|
446
497
|
# Returns [ st, i ], the parent subtree for the final i index of the nid
|
447
498
|
# Used when inserting updated subtrees.
|
448
499
|
#
|
@@ -543,6 +594,40 @@ module Flor
|
|
543
594
|
|
544
595
|
indent == '' ? out.string : nil
|
545
596
|
end
|
597
|
+
|
598
|
+
def to_string_keyed_hash(o)
|
599
|
+
|
600
|
+
case o
|
601
|
+
when Array
|
602
|
+
o.collect { |e| to_string_keyed_hash(e) }
|
603
|
+
when Hash
|
604
|
+
o.inject({}) { |h, (k, v)| h[k.to_s] = to_string_keyed_hash(v); h }
|
605
|
+
else
|
606
|
+
o
|
607
|
+
end
|
608
|
+
end
|
609
|
+
|
610
|
+
def to_camel_case(s)
|
611
|
+
|
612
|
+
s.sub(/\A[a-z]/) { |m| m.upcase }.gsub(/_[a-z]/) { |m| m[1, 1].upcase }
|
613
|
+
end
|
614
|
+
|
615
|
+
# Available as `Flor.migration_dir`
|
616
|
+
#
|
617
|
+
def migration_dir
|
618
|
+
|
619
|
+
File.absolute_path(
|
620
|
+
File.join(
|
621
|
+
File.dirname(__FILE__), 'migrations'))
|
622
|
+
end
|
623
|
+
|
624
|
+
def caller_fname
|
625
|
+
|
626
|
+
caller
|
627
|
+
.find { |l| ! l.match(/\/lib\/flor\//) }
|
628
|
+
.match(/\A([^:]+:\d+)/)[1]
|
629
|
+
.strip
|
630
|
+
end
|
546
631
|
end
|
547
632
|
end
|
548
633
|
|