flor 0.0.1 → 0.9.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.
Files changed (84) hide show
  1. data/CHANGELOG.md +13 -0
  2. data/LICENSE.txt +1 -1
  3. data/Makefile +66 -0
  4. data/README.md +57 -0
  5. data/fail.txt +7 -0
  6. data/flor.gemspec +12 -9
  7. data/intercepted.txt +123 -0
  8. data/lib/flor/colours.rb +140 -0
  9. data/lib/flor/conf.rb +88 -0
  10. data/lib/flor/core/executor.rb +473 -0
  11. data/lib/flor/core/node.rb +397 -0
  12. data/lib/flor/core/procedure.rb +600 -0
  13. data/lib/flor/core/texecutor.rb +209 -0
  14. data/lib/flor/core.rb +93 -0
  15. data/lib/flor/dollar.rb +248 -0
  16. data/lib/flor/errors.rb +36 -0
  17. data/lib/flor/flor.rb +556 -0
  18. data/lib/flor/log.rb +336 -0
  19. data/lib/flor/migrations/0001_tables.rb +122 -0
  20. data/lib/flor/parser.rb +414 -0
  21. data/lib/flor/pcore/_arr.rb +49 -0
  22. data/lib/flor/pcore/_atom.rb +43 -0
  23. data/lib/flor/pcore/_att.rb +160 -0
  24. data/lib/flor/pcore/_dump.rb +60 -0
  25. data/lib/flor/pcore/_err.rb +30 -0
  26. data/lib/flor/pcore/_happly.rb +73 -0
  27. data/lib/flor/pcore/_obj.rb +65 -0
  28. data/lib/flor/pcore/_skip.rb +63 -0
  29. data/lib/flor/pcore/apply.rb +60 -0
  30. data/lib/flor/pcore/arith.rb +46 -0
  31. data/lib/flor/pcore/break.rb +71 -0
  32. data/lib/flor/pcore/cmp.rb +72 -0
  33. data/lib/flor/pcore/cond.rb +57 -0
  34. data/lib/flor/pcore/cursor.rb +223 -0
  35. data/lib/flor/pcore/define.rb +96 -0
  36. data/lib/flor/pcore/fail.rb +45 -0
  37. data/lib/flor/pcore/ife.rb +56 -0
  38. data/lib/flor/pcore/loop.rb +53 -0
  39. data/lib/flor/pcore/map.rb +75 -0
  40. data/lib/flor/pcore/match.rb +70 -0
  41. data/lib/flor/pcore/move.rb +65 -0
  42. data/lib/flor/pcore/noeval.rb +46 -0
  43. data/lib/flor/pcore/noret.rb +47 -0
  44. data/lib/flor/pcore/push.rb +69 -0
  45. data/lib/flor/pcore/sequence.rb +39 -0
  46. data/lib/flor/pcore/set.rb +76 -0
  47. data/lib/flor/pcore/stall.rb +35 -0
  48. data/lib/flor/pcore/until.rb +122 -0
  49. data/lib/flor/pcore/val.rb +40 -0
  50. data/lib/flor/punit/cancel.rb +69 -0
  51. data/lib/flor/punit/cmap.rb +76 -0
  52. data/lib/flor/punit/concurrence.rb +149 -0
  53. data/lib/flor/punit/every.rb +46 -0
  54. data/lib/flor/punit/on.rb +81 -0
  55. data/lib/flor/punit/schedule.rb +68 -0
  56. data/lib/flor/punit/signal.rb +47 -0
  57. data/lib/flor/punit/sleep.rb +53 -0
  58. data/lib/flor/punit/task.rb +109 -0
  59. data/lib/flor/punit/trace.rb +51 -0
  60. data/lib/flor/punit/trap.rb +100 -0
  61. data/lib/flor/to_string.rb +81 -0
  62. data/lib/flor/tools/env.rb +103 -0
  63. data/lib/flor/tools/repl.rb +231 -0
  64. data/lib/flor/unit/executor.rb +260 -0
  65. data/lib/flor/unit/hooker.rb +186 -0
  66. data/lib/flor/unit/journal.rb +52 -0
  67. data/lib/flor/unit/loader.rb +181 -0
  68. data/lib/flor/unit/logger.rb +181 -0
  69. data/lib/flor/unit/models/execution.rb +105 -0
  70. data/lib/flor/unit/models/pointer.rb +31 -0
  71. data/lib/flor/unit/models/timer.rb +52 -0
  72. data/lib/flor/unit/models/trace.rb +31 -0
  73. data/lib/flor/unit/models/trap.rb +130 -0
  74. data/lib/flor/unit/models.rb +106 -0
  75. data/lib/flor/unit/scheduler.rb +419 -0
  76. data/lib/flor/unit/storage.rb +633 -0
  77. data/lib/flor/unit/tasker.rb +191 -0
  78. data/lib/flor/unit/waiter.rb +146 -0
  79. data/lib/flor/unit/wlist.rb +77 -0
  80. data/lib/flor/unit.rb +50 -0
  81. data/lib/flor.rb +40 -3
  82. metadata +152 -22
  83. checksums.yaml +0 -7
  84. data/Rakefile +0 -52
@@ -0,0 +1,186 @@
1
+ #--
2
+ # Copyright (c) 2015-2017, John Mettraux, jmettraux+flor@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+ module Flor
26
+
27
+ class Hooker
28
+
29
+ # NB: logger configuration entries start with "hok_"
30
+
31
+ def initialize(unit)
32
+
33
+ @unit = unit
34
+
35
+ @hooks = []
36
+ end
37
+
38
+ def shutdown
39
+
40
+ @hooks.each do |n, o, hook, b|
41
+
42
+ hook.shutdown if hook.respond_to?(:shutdown)
43
+ end
44
+ end
45
+
46
+ def [](name)
47
+
48
+ h = @hooks.find { |n, o, h, b| n == name }
49
+
50
+ h ? h[2] || h[3] : nil
51
+ end
52
+
53
+ def add(*args, &block)
54
+
55
+ name = nil
56
+ hook = nil
57
+ opts = {}
58
+
59
+ args.each do |arg|
60
+ case arg
61
+ when String then name = arg
62
+ when Hash then opts = arg
63
+ else hook = arg
64
+ end
65
+ end
66
+
67
+ hook = hook.new(@unit) if hook.is_a?(Class)
68
+
69
+ @hooks << [ name, opts, hook, block ]
70
+ end
71
+
72
+ def notify(executor, message)
73
+
74
+ (@hooks + executor.traps.collect(&:to_hook))
75
+ .inject([]) do |a, (_, opts, hook, block)|
76
+ # name of hook is piped into "_" oblivion
77
+
78
+ a.concat(
79
+ if ! match?(executor, hook, opts, message)
80
+ []
81
+ elsif hook.is_a?(Flor::Trap)
82
+ executor.trigger_trap(hook, message)
83
+ elsif hook
84
+ executor.trigger_hook(hook, message)
85
+ else # if block
86
+ r =
87
+ if block.arity == 1
88
+ block.call(message)
89
+ elsif block.arity == 2
90
+ block.call(message, opts)
91
+ else
92
+ block.call(executor, message, opts)
93
+ end
94
+ r.is_a?(Array) && r.all? { |e| e.is_a?(Hash) } ? r : []
95
+ # be lenient with block hooks, help them return an array
96
+ end)
97
+ end
98
+ end
99
+
100
+ protected
101
+
102
+ def o(opts, *keys)
103
+
104
+ array = false
105
+ array = keys.pop if keys.last == []
106
+
107
+ r = nil
108
+ keys.each { |k| break r = opts[k] if opts.has_key?(k) }
109
+
110
+ return nil if r == nil
111
+ array ? Array(r) : r
112
+ end
113
+
114
+ def match?(executor, hook, opts, message)
115
+
116
+ #p opts if hook.is_a?(Flor::Trap)
117
+ opts = hook.opts if hook.respond_to?(:opts) && opts.empty?
118
+
119
+ c = o(opts, :consumed, :c)
120
+ return false if c == true && ! message['consumed']
121
+ return false if c == false && message['consumed']
122
+
123
+ if hook.is_a?(Flor::Trap)
124
+ return false if message['point'] == 'trigger'
125
+ return false if hook.within_itself?(executor, message)
126
+ end
127
+
128
+ #p :xxx if hook.is_a?(Flor::Trap)
129
+ ps = o(opts, :point, :p, [])
130
+ return false if ps && ! ps.include?(message['point'])
131
+
132
+ if exi = o(opts, :exid)
133
+ return false \
134
+ unless message['exid'] == exi
135
+ end
136
+
137
+ dm = Flor.domain(message['exid'])
138
+
139
+ if dm && ds = o(opts, :domain, :d, [])
140
+ return false \
141
+ unless ds.find { |d| d.is_a?(Regexp) ? (!! d.match(dm)) : (d == dm) }
142
+ end
143
+
144
+ if dm && sds = o(opts, :subdomain, :sd, [])
145
+ return false \
146
+ unless sds.find do |sd|
147
+ dm[0, sd.length] == sd
148
+ end
149
+ end
150
+
151
+ if ts = o(opts, :tag, :t, [])
152
+ return false unless %w[ entered left ].include?(message['point'])
153
+ return false unless (message['tags'] & ts).any?
154
+ end
155
+
156
+ if ns = o(opts, :name, :n)
157
+ return false unless ns.include?(message['name'])
158
+ end
159
+
160
+ node = nil
161
+
162
+ if hook.is_a?(Flor::Trap) && o(opts, :subnid)
163
+ if node = executor.node(message['nid'], true)
164
+ return false unless node.descendant_of?(hook.nid, true)
165
+ node = node.h
166
+ else
167
+ return false if hook.nid != '0'
168
+ end
169
+ end
170
+
171
+ if hps = o(opts, :heap, :hp, [])
172
+ return false unless node ||= executor.node(message['nid'])
173
+ return false unless hps.include?(node['heap'])
174
+ end
175
+ #p :yyy if hook.is_a?(Flor::Trap)
176
+
177
+ if hts = o(opts, :heat, :ht, [])
178
+ return false unless node ||= executor.node(message['nid'])
179
+ return false unless hts.include?(node['heat0'])
180
+ end
181
+
182
+ true
183
+ end
184
+ end
185
+ end
186
+
@@ -0,0 +1,52 @@
1
+ #--
2
+ # Copyright (c) 2015-2017, John Mettraux, jmettraux+flor@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+ module Flor
26
+
27
+ class Journal
28
+
29
+ attr_reader :messages
30
+
31
+ def initialize(unit)
32
+
33
+ unit.singleton_class.instance_eval do
34
+ define_method(:journal) do
35
+ @hooker['journal'].messages
36
+ end
37
+ end
38
+
39
+ @messages = []
40
+ end
41
+
42
+ def opts; { consumed: true }; end
43
+
44
+ def notify(executor, message)
45
+
46
+ @messages << Flor.dup(message)
47
+
48
+ [] # no new messages
49
+ end
50
+ end
51
+ end
52
+
@@ -0,0 +1,181 @@
1
+ #--
2
+ # Copyright (c) 2015-2017, John Mettraux, jmettraux+flor@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+ module Flor
26
+
27
+ class Loader
28
+
29
+ # NB: tasker configuration entries start with "loa_"
30
+
31
+ def initialize(unit)
32
+
33
+ @unit = unit
34
+
35
+ @cache = {}
36
+ @mutex = Mutex.new
37
+ end
38
+
39
+ def shutdown
40
+ end
41
+
42
+ def variables(domain)
43
+
44
+ Dir[File.join(root, '**/*.json')]
45
+ .select { |f| f.index('/etc/variables/') }
46
+ .sort # just to be sure
47
+ .sort_by(&:length)
48
+ .select { |f| path_matches?(domain, f) }
49
+ .inject({}) { |vars, f| vars.merge!(interpret(f)) }
50
+ end
51
+
52
+ #def procedures(path)
53
+ #
54
+ # # TODO
55
+ # # TODO work with Flor.load_procedures
56
+ #end
57
+
58
+ def library(domain, name=nil)
59
+
60
+ domain, name = split_dn(domain, name)
61
+
62
+ path =
63
+ (Dir[File.join(root, '**/*.{flo,flor}')])
64
+ .sort
65
+ .sort_by(&:length)
66
+ .select { |f| f.index('/lib/') }
67
+ .select { |f| path_name_matches?(domain, name, f) }
68
+ .first
69
+
70
+ path ? File.read(path) : nil
71
+ end
72
+
73
+ # class FlowEnv
74
+ #
75
+ # attr_accessor :path, :domain, :flow_name, :source, :variables, :payload
76
+ # alias :flow :flow_name
77
+ #
78
+ # def initialize(loader, path)
79
+ #
80
+ # @path = path
81
+ #
82
+ # es = path.split('.')
83
+ # @domain, @flow_name = [ es[0..-2].join('.'), es[-1] ]
84
+ #
85
+ # @source = loader.library(@domain, @flow_name)
86
+ # @variables = loader.variables(@domain)
87
+ # #@payload = ... # TODO at some point, if necessary...
88
+ #
89
+ # fail ArgumentError.new(
90
+ # "could not find flow at #{@path.inspect}"
91
+ # ) unless @source
92
+ # end
93
+ #
94
+ # def to_a
95
+ #
96
+ # [ path, domain, flow, source, variables, payload ]
97
+ # end
98
+ # end
99
+ #
100
+ # def flow_environment(path)
101
+ #
102
+ # FlowEnv.new(self, path)
103
+ # end
104
+
105
+ def tasker(domain, name=nil)
106
+
107
+ domain, name = split_dn(domain, name)
108
+
109
+ path = Dir[File.join(root, '**/*.json')]
110
+ .select { |f| f.index('/lib/taskers/') }
111
+ .sort # just to be sure
112
+ .sort_by(&:length)
113
+ .select { |f| path_name_matches?(domain, name, f) }
114
+ .last
115
+
116
+ path ? interpret(path) : nil
117
+ end
118
+
119
+ protected
120
+
121
+ def split_dn(domain, name)
122
+
123
+ if name
124
+ [ domain, name ]
125
+ else
126
+ elts = domain.split('.')
127
+ [ elts[0..-2].join('.'), elts[-1] ]
128
+ end
129
+ end
130
+
131
+ def root
132
+
133
+ if lp = @unit.conf['lod_path']
134
+ File.absolute_path(lp)
135
+ else
136
+ File.dirname(File.absolute_path(@unit.conf['_path'] + '/..'))
137
+ end
138
+ end
139
+
140
+ def path_matches?(domain, f)
141
+
142
+ f = f[root.length..-1]
143
+ f = f[5..-1] if f[0, 5] == '/usr/'
144
+
145
+ f = f
146
+ .sub(/\/etc\/variables\//, '/')
147
+ .sub(/\/lib\/(flows|taskers)\//, '/')
148
+ .sub(/\/\z/, '')
149
+ .sub(/\/(flo|flor|dot)\.json\z/, '')
150
+ .sub(/\.(flo|flor|json)\z/, '')
151
+ .sub(/\A\//, '')
152
+ .gsub(/\//, '.')
153
+
154
+ #p [ :pm, domain[0, f.length], f, '=>', domain[0, f.length] == f ]
155
+ domain[0, f.length] == f
156
+ end
157
+
158
+ def path_name_matches?(domain, name, f)
159
+
160
+ f = f.sub(/\/(flo|flor|dot)\.json\z/, '.json')
161
+
162
+ return false if File.basename(f).split('.').first != name
163
+
164
+ path_matches?(domain, File.dirname(f) + '/')
165
+ end
166
+
167
+ def interpret(path)
168
+
169
+ @mutex.synchronize do
170
+
171
+ mt1 = File.mtime(path)
172
+ val, mt0 = @cache[path]
173
+ #p [ :cached, path ] if val && mt1 == mt0
174
+ return val if val && mt1 == mt0
175
+
176
+ (@cache[path] = [ Flor::ConfExecutor.interpret(path), mt1 ]).first
177
+ end
178
+ end
179
+ end
180
+ end
181
+
@@ -0,0 +1,181 @@
1
+ #--
2
+ # Copyright (c) 2015-2017, John Mettraux, jmettraux+flor@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+ module Flor
26
+
27
+ class Logger
28
+
29
+ # NB: logger configuration entries start with "log_"
30
+
31
+ def initialize(unit)
32
+
33
+ @unit = unit
34
+
35
+ @dir = @unit.conf['log_dir'] || 'tmp'
36
+ @dir = '.' unless @dir.is_a?(String) && File.exist?(@dir)
37
+
38
+ @fname = nil
39
+ @file = nil
40
+
41
+ @mutex = Mutex.new
42
+
43
+ @unit.singleton_class.instance_eval do
44
+ define_method(:logger) { @hooker['logger'] }
45
+ end
46
+
47
+ @uni = @unit.identifier
48
+ end
49
+
50
+ def opts; { consumed: true }; end
51
+
52
+ def shutdown
53
+
54
+ @file.close if @file
55
+ end
56
+
57
+ def debug(*m); log(:debug, *m); end
58
+ def error(*m); log(:error, *m); end
59
+ def info(*m); log(:info, *m); end
60
+ def warn(*m); log(:warn, *m); end
61
+
62
+ def log(level, *elts)
63
+
64
+ return if [ nil, 'null', false ].include?(@dir)
65
+
66
+ n = Time.now.utc
67
+ stp = Flor.tstamp(n)
68
+
69
+ out =
70
+ case @dir
71
+ when 'stdout' then $stdout
72
+ when 'stderr' then $stderr
73
+ else prepare_file(n)
74
+ end
75
+
76
+ lvl = level.to_s.upcase
77
+ txt = elts.collect(&:to_s).join(' ')
78
+ err = elts.find { |e| e.is_a?(Exception) }
79
+
80
+ line = "#{stp} #{@uni} #{lvl} #{txt}"
81
+
82
+ @mutex.synchronize do
83
+ if err
84
+ sts = ' ' * stp.length
85
+ lvs = ' ' * (@uni.length + 1 + lvl.length)
86
+ dig = lvl[0, 1] + Digest::MD5.hexdigest(line)[0, 4]
87
+ out.puts("#{stp} #{@uni} #{lvl} #{dig} #{txt}")
88
+ err.backtrace.each { |lin| out.puts("#{sts} #{lvs} #{dig} #{lin}") }
89
+ else
90
+ out.puts(line)
91
+ end
92
+ end
93
+ end
94
+
95
+ def notify(executor, msg)
96
+
97
+ if msg['rewritten'] && @unit.conf['log_tree_rw']
98
+
99
+ Flor.print_compact_tree(
100
+ msg['rewritten'], msg['nid'],
101
+ ind: 6, title: "rewrote #{msg['exid']} #{msg['nid']}")
102
+ Flor.print_compact_tree(
103
+ msg['tree'], msg['nid'],
104
+ ind: 6, title: "into #{msg['exid']} #{msg['nid']}",
105
+ close: true)
106
+ end
107
+
108
+ if @unit.conf['log_msg']
109
+
110
+ Flor.log_message(executor, msg)
111
+ end
112
+
113
+ []
114
+ end
115
+
116
+ def db_log(level, msg)
117
+
118
+ return unless @unit.conf['log_sto']
119
+
120
+ _c = Flor.colours
121
+
122
+ #m = msg.match(/ (INSERT|UPDATE) .+ (0?[xX]'?[a-fA-F0-9]+'?)/)
123
+ #msg = msg.sub(m[2], "#{m[2][0, 14]}(...len#{m[2].length})") if m
124
+ #
125
+ # with a fat blob, may lead to memory problems very quickly, hence:
126
+ #
127
+ msg = summarize_blob(msg)
128
+
129
+ puts "#{_c.blg}sto#{_c.rs} t#{Thread.current.object_id} #{level.upcase} #{msg}"
130
+ end
131
+
132
+ protected
133
+
134
+ def prepare_file(t)
135
+
136
+ @mutex.synchronize do
137
+
138
+ fn = File.join(
139
+ @dir,
140
+ "#{@unit.conf['env']}_#{t.strftime('%Y-%m-%d')}.log")
141
+
142
+ if fn != @fname
143
+ @file.close if @file
144
+ @file = nil
145
+ @fname = fn
146
+ end
147
+
148
+ @file ||= File.open(@fname, 'ab')
149
+ end
150
+ end
151
+
152
+ BLOB_CHARS = (('a'..'f').to_a + ('A'..'F').to_a + ('0'..'9').to_a).freeze
153
+
154
+ def summarize_blob(message)
155
+
156
+ #
157
+ # /!\ reminder: substitutes only one blob
158
+ #
159
+
160
+ i = message.index(' INSERT ') || message.index(' UPDATE ')
161
+ return message unless i
162
+
163
+ j = message.index(" x'") || message.index(" X'")
164
+ k = j || message.index(" 0x") || message.index(" 0X")
165
+ return message unless k
166
+
167
+ over = j ? [ "'" ] : [ ' ', nil ]
168
+
169
+ i = k + 3
170
+ loop do
171
+ c = message[i, 1]
172
+ break if over.include?(c)
173
+ return message unless BLOB_CHARS.index(c)
174
+ i = i + 1
175
+ end
176
+
177
+ message[0..k + 2 + 4] + "(...len#{i - (k + 2 + 1)})" + message[i..-1]
178
+ end
179
+ end
180
+ end
181
+
@@ -0,0 +1,105 @@
1
+ #--
2
+ # Copyright (c) 2015-2017, John Mettraux, jmettraux+flor@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+
26
+ module Flor
27
+
28
+ class Execution < FlorModel
29
+
30
+ def nodes; data['nodes']; end
31
+
32
+ def tags
33
+
34
+ data['nodes'].values.inject([]) do |a, n|
35
+ if ts = n['tags']; a.concat(ts); end
36
+ a
37
+ end
38
+ end
39
+
40
+ def failed?
41
+
42
+ !! nodes.values
43
+ .find { |n| n['failure'] && n['status'] != 'triggered-on-error' }
44
+ end
45
+
46
+ # class methods
47
+
48
+ def self.by_status(s)
49
+
50
+ self.where(status: s)
51
+ end
52
+
53
+ def self.terminated
54
+
55
+ by_status('terminated')
56
+ end
57
+
58
+ def self.by_tag(name)
59
+
60
+ exids = self.db[:flor_pointers]
61
+ .where(type: 'tag', name: name, value: nil)
62
+ .select(:exid)
63
+ .distinct
64
+
65
+ self.where(status: 'active', exid: exids)
66
+ end
67
+
68
+ def self.by_var(name, value=:no)
69
+
70
+ w = { type: 'var', name: name }
71
+
72
+ case value; when nil
73
+ w[:value] = nil
74
+ when :no
75
+ # no w[:value] "constraining"
76
+ else
77
+ w[:value] = value.to_s
78
+ end
79
+
80
+ exids = self.db[:flor_pointers]
81
+ .where(w)
82
+ .select(:exid)
83
+ .distinct
84
+
85
+ self.where(status: 'active', exid: exids)
86
+ end
87
+
88
+ def self.by_tasker(name, taskname=:no)
89
+
90
+ w = { type: 'tasker', name: name }
91
+ w[:value] = taskname if taskname != :no
92
+
93
+ exids = self.db[:flor_pointers]
94
+ .where(w)
95
+ .select(:exid)
96
+ .distinct
97
+
98
+ self.where(status: 'active', exid: exids)
99
+ end
100
+
101
+ # def self.by_task(name)
102
+ # end
103
+ end
104
+ end
105
+