flor 0.14.0 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +9 -0
  3. data/CREDITS.md +21 -0
  4. data/LICENSE.txt +4 -1
  5. data/Makefile +4 -0
  6. data/README.md +8 -0
  7. data/flor.gemspec +10 -10
  8. data/lib/flor.rb +2 -2
  9. data/lib/flor/changes.rb +3 -3
  10. data/lib/flor/colours.rb +14 -8
  11. data/lib/flor/conf.rb +63 -58
  12. data/lib/flor/core.rb +4 -4
  13. data/lib/flor/core/executor.rb +65 -29
  14. data/lib/flor/core/node.rb +37 -20
  15. data/lib/flor/core/procedure.rb +182 -40
  16. data/lib/flor/core/texecutor.rb +125 -52
  17. data/lib/flor/djan.rb +111 -82
  18. data/lib/flor/dollar.rb +31 -30
  19. data/lib/flor/flor.rb +314 -237
  20. data/lib/flor/id.rb +7 -2
  21. data/lib/flor/log.rb +250 -245
  22. data/lib/flor/parser.rb +72 -38
  23. data/lib/flor/pcore/_arr.rb +10 -10
  24. data/lib/flor/pcore/_att.rb +49 -14
  25. data/lib/flor/pcore/_coll.rb +18 -0
  26. data/lib/flor/pcore/_obj.rb +23 -7
  27. data/lib/flor/pcore/_pat_.rb +1 -1
  28. data/lib/flor/pcore/_pat_guard.rb +8 -0
  29. data/lib/flor/pcore/_pat_obj.rb +3 -3
  30. data/lib/flor/pcore/_pat_or.rb +4 -0
  31. data/lib/flor/pcore/_pat_regex.rb +24 -0
  32. data/lib/flor/pcore/_skip.rb +4 -0
  33. data/lib/flor/pcore/_val.rb +0 -1
  34. data/lib/flor/pcore/all.rb +111 -0
  35. data/lib/flor/pcore/any.rb +83 -0
  36. data/lib/flor/pcore/arith.rb +35 -6
  37. data/lib/flor/pcore/break.rb +39 -1
  38. data/lib/flor/pcore/case.rb +82 -4
  39. data/lib/flor/pcore/cmp.rb +7 -7
  40. data/lib/flor/pcore/collect.rb +50 -0
  41. data/lib/flor/pcore/cond.rb +17 -3
  42. data/lib/flor/pcore/cursor.rb +8 -2
  43. data/lib/flor/pcore/detect.rb +45 -0
  44. data/lib/flor/pcore/each.rb +52 -0
  45. data/lib/flor/pcore/empty.rb +60 -0
  46. data/lib/flor/pcore/filter.rb +94 -0
  47. data/lib/flor/pcore/find.rb +67 -0
  48. data/lib/flor/pcore/for_each.rb +65 -0
  49. data/lib/flor/pcore/includes.rb +32 -0
  50. data/lib/flor/pcore/inject.rb +55 -0
  51. data/lib/flor/pcore/iterator.rb +151 -0
  52. data/lib/flor/pcore/keys.rb +60 -0
  53. data/lib/flor/pcore/length.rb +34 -7
  54. data/lib/flor/pcore/logo.rb +18 -0
  55. data/lib/flor/pcore/loop.rb +4 -0
  56. data/lib/flor/pcore/map.rb +77 -46
  57. data/lib/flor/pcore/match.rb +8 -2
  58. data/lib/flor/pcore/matchr.rb +4 -5
  59. data/lib/flor/pcore/move.rb +3 -3
  60. data/lib/flor/pcore/noeval.rb +13 -0
  61. data/lib/flor/pcore/not.rb +16 -0
  62. data/lib/flor/pcore/on.rb +172 -0
  63. data/lib/flor/pcore/on_cancel.rb +54 -0
  64. data/lib/flor/pcore/on_error.rb +68 -0
  65. data/lib/flor/pcore/rand.rb +2 -2
  66. data/lib/flor/pcore/range.rb +2 -1
  67. data/lib/flor/pcore/reduce.rb +124 -0
  68. data/lib/flor/pcore/reverse.rb +46 -0
  69. data/lib/flor/pcore/select.rb +72 -0
  70. data/lib/flor/pcore/set.rb +8 -0
  71. data/lib/flor/pcore/stall.rb +10 -0
  72. data/lib/flor/pcore/to_array.rb +61 -0
  73. data/lib/flor/pcore/until.rb +34 -0
  74. data/lib/flor/punit/cancel.rb +30 -5
  75. data/lib/flor/punit/ccollect.rb +11 -0
  76. data/lib/flor/punit/cmap.rb +10 -5
  77. data/lib/flor/punit/concurrence.rb +42 -51
  78. data/lib/flor/punit/cron.rb +33 -0
  79. data/lib/flor/punit/do_trap.rb +42 -0
  80. data/lib/flor/punit/every.rb +48 -13
  81. data/lib/flor/punit/graft.rb +3 -3
  82. data/lib/flor/punit/on_timeout.rb +38 -0
  83. data/lib/flor/punit/schedule.rb +69 -6
  84. data/lib/flor/punit/signal.rb +54 -0
  85. data/lib/flor/punit/sleep.rb +1 -1
  86. data/lib/flor/punit/task.rb +4 -1
  87. data/lib/flor/punit/trap.rb +188 -13
  88. data/lib/flor/tools/shell.rb +408 -62
  89. data/lib/flor/tools/shell_out.rb +31 -0
  90. data/lib/flor/unit.rb +1 -1
  91. data/lib/flor/unit/caller.rb +177 -0
  92. data/lib/flor/unit/executor.rb +1 -0
  93. data/lib/flor/unit/ganger.rb +15 -21
  94. data/lib/flor/unit/hook.rb +1 -1
  95. data/lib/flor/unit/hooker.rb +22 -10
  96. data/lib/flor/unit/loader.rb +22 -22
  97. data/lib/flor/unit/logger.rb +63 -36
  98. data/lib/flor/unit/models.rb +6 -1
  99. data/lib/flor/unit/models/execution.rb +12 -1
  100. data/lib/flor/unit/models/message.rb +7 -0
  101. data/lib/flor/unit/models/trap.rb +31 -17
  102. data/lib/flor/unit/scheduler.rb +18 -10
  103. data/lib/flor/unit/storage.rb +83 -23
  104. data/lib/flor/unit/waiter.rb +1 -2
  105. metadata +96 -52
  106. data/lib/flor/deep.rb +0 -144
  107. data/lib/flor/punit/on.rb +0 -57
  108. data/lib/flor/unit/runner.rb +0 -84
  109. data/match.md +0 -22
@@ -0,0 +1,31 @@
1
+
2
+ module Flor::Tools
3
+
4
+ class ShellOut
5
+
6
+ def initialize(unit)
7
+
8
+ @file = File.open('.log.txt', 'ab')
9
+ @file.sync = true
10
+
11
+ #@unit.conf['fls_file']
12
+ #@unit.conf['fls_file_mode']
13
+ end
14
+
15
+ def tty?
16
+
17
+ $stdout.tty?
18
+ end
19
+
20
+ def puts(s)
21
+
22
+ $stdout.puts(s)
23
+ @file.puts(s)
24
+ end
25
+
26
+ def method_missing(*args)
27
+ p [ 'MISSING>>>', args ]
28
+ end
29
+ end
30
+ end
31
+
@@ -7,7 +7,7 @@ require 'fugit'
7
7
  require 'flor'
8
8
  require 'flor/unit/hook'
9
9
  require 'flor/unit/hooker'
10
- require 'flor/unit/runner'
10
+ require 'flor/unit/caller'
11
11
  require 'flor/unit/wlist'
12
12
  require 'flor/unit/logger'
13
13
  require 'flor/unit/journal'
@@ -0,0 +1,177 @@
1
+
2
+ module Flor
3
+
4
+ # The caller calls Ruby or other scripts.
5
+ #
6
+ class Caller
7
+
8
+ # NB: tasker configuration entries start with "cal_"
9
+
10
+ def initialize(unit)
11
+
12
+ @unit = unit
13
+ end
14
+
15
+ def shutdown
16
+ end
17
+
18
+ def call(service, conf, message)
19
+
20
+ return ruby_call(service, conf, message) \
21
+ if conf['class'] || conf['module']
22
+ return cmd_call(service, conf, message) \
23
+ if conf['cmd']
24
+
25
+ fail ArgumentError.new("don't know how to call item at #{conf['_path']}")
26
+
27
+ rescue => err
28
+
29
+ [ Flor.to_error(err) ]
30
+ end
31
+
32
+ protected
33
+
34
+ def conf
35
+
36
+ @unit ? @unit.conf : {}
37
+ end
38
+
39
+ def fjoin(root, path)
40
+
41
+ root == '.' ? path : File.join(root, path)
42
+ end
43
+
44
+ def ruby_call(service, conf, message)
45
+
46
+ root = File.dirname(conf['_path'] || '.')
47
+
48
+ Flor.h_fetch_a(conf, 'require').each { |pa|
49
+ fail ArgumentError.new('".." not allowed in paths') if pa =~ /\.\./
50
+ require(fjoin(root, pa)) }
51
+ Flor.h_fetch_a(conf, 'load').each { |pa|
52
+ fail ArgumentError.new('".." not allowed in paths') if pa =~ /\.\./
53
+ load(fjoin(root, pa)) }
54
+
55
+ k = Flor.const_lookup(conf['class'] || conf['module'])
56
+
57
+ o =
58
+ if k.class == Module
59
+ k
60
+ else
61
+ case k.instance_method(:initialize).arity
62
+ when 1 then k.new(service)
63
+ when 2 then k.new(service, conf)
64
+ when 3 then k.new(service, conf, message)
65
+ when -1 then k.new({
66
+ service: service, configuration: conf, message: message })
67
+ else k.new
68
+ end
69
+ end
70
+
71
+ p = message['point']
72
+ m = :on
73
+ m = "on_#{p}" if ! o.respond_to?(m)
74
+ m = p if ! o.respond_to?(m)
75
+ m = :cancel if m == 'detask' && ! o.respond_to?(m)
76
+
77
+ fail(
78
+ "#{k.class.to_s.downcase} #{k} doesn't respond to on, on_#{p} or #{p}"
79
+ ) if ! o.respond_to?(m)
80
+
81
+ r =
82
+ case o.method(m).arity
83
+ when 1 then o.send(m, message)
84
+ when 2 then o.send(m, conf, message)
85
+ when 3 then o.send(m, executor, conf, message)
86
+ when -1 then o.send(m, {
87
+ service: service, configuration: conf, message: message })
88
+ else o.send(m)
89
+ end
90
+
91
+ to_messages(r)
92
+ end
93
+
94
+ def cmd_call(service, conf, message)
95
+
96
+ h = conf.dup # shallow
97
+ h['m'] = message
98
+ h['f'] = message['payload']
99
+ h['v'] = message['vars']
100
+ h['tag'] = (message['tags'] || []).first
101
+
102
+ cmd = h['cmd']
103
+
104
+ m = encode(conf, message)
105
+ out, _ = spawn(cmd, m)
106
+ r = decode(conf, out)
107
+
108
+ to_messages(r)
109
+ end
110
+
111
+ def encode(context, message)
112
+
113
+ coder =
114
+ Flor.h_fetch(context, 'encoder', 'coder') ||
115
+ Flor.h_fetch(conf, 'cal_encoder', 'cal_coder') ||
116
+ '::JSON'
117
+
118
+ Flor.const_get(coder)
119
+ .dump(message)
120
+ end
121
+
122
+ def decode(context, data)
123
+
124
+ coder =
125
+ Flor.h_fetch(context, 'decoder', 'coder', 'encoder') ||
126
+ Flor.h_fetch(conf, 'cal_decoder', 'cal_coder', 'cal_encoder') ||
127
+ '::JSON'
128
+
129
+ Flor.const_get(coder)
130
+ .load(data)
131
+ end
132
+
133
+ def spawn(cmd, data)
134
+
135
+ i, o = IO.pipe # _ / stdout
136
+ f, e = IO.pipe # _ / stderr
137
+ r, w = IO.pipe # stdin / _
138
+
139
+ pid = Kernel.spawn(cmd, in: r, out: o, err: e)
140
+ w.write(data)
141
+ w.close
142
+ o.close
143
+ e.close
144
+ _, status = Process.wait2(pid)
145
+
146
+ fail SpawnError.new(status, i.read, f.read) if status.exitstatus != 0
147
+
148
+ [ i.read, status ]
149
+ end
150
+
151
+ class SpawnError < StandardError
152
+
153
+ attr_reader :status, :out, :err
154
+
155
+ def initialize(status, out, err)
156
+
157
+ @status = status
158
+ @out = out
159
+ @err = err
160
+
161
+ msg = err.strip.split("\n").last
162
+
163
+ super("(code: #{status.exitstatus}, pid: #{status.pid}) #{msg}")
164
+ end
165
+ end
166
+
167
+ def to_messages(o)
168
+
169
+ case o
170
+ when Hash then [ o ]
171
+ when Array then o
172
+ else []
173
+ end
174
+ end
175
+ end
176
+ end
177
+
@@ -141,6 +141,7 @@ module Flor
141
141
  def trigger(message)
142
142
 
143
143
  m = message['message']
144
+ m['cause'] = message['cause']
144
145
 
145
146
  m['nid'] = Flor.sub_nid(m['nid'], counter_next('subs')) \
146
147
  if m['point'] == 'execute'
@@ -1,6 +1,11 @@
1
1
 
2
2
  module Flor
3
3
 
4
+ # ganger | ˈɡaNGər | \ noun British \ the foreman of a gang of laborers.
5
+ #
6
+ # The ganger receives the tasks from the flor executor, decides what
7
+ # tasker will be invoked and hands it the task.
8
+ #
4
9
  class Ganger
5
10
 
6
11
  # NB: tasker configuration entries start with "gan_"
@@ -23,8 +28,10 @@ module Flor
23
28
 
24
29
  d = Flor.domain(exid)
25
30
 
26
- #!! @unit.loader.tasker(d, name)
27
- !! (@unit.loader.tasker(d, 'tasker') || @unit.loader.tasker(d, name))
31
+ !! (
32
+ @unit.loader.tasker(d, 'ganger') ||
33
+ @unit.loader.tasker(d, 'tasker') ||
34
+ @unit.loader.tasker(d, name))
28
35
  end
29
36
 
30
37
  def task(executor, message)
@@ -33,10 +40,10 @@ module Flor
33
40
  tname = message['tasker']
34
41
 
35
42
  tconf =
36
- ( ! message['routed'] && @unit.loader.tasker(domain, 'tasker')) ||
37
- @unit.loader.tasker(domain, tname)
38
- #
39
- # TODO `.tasker(domain, 'ganger')`
43
+ ( ! message['routed'] &&
44
+ (@unit.loader.tasker(domain, 'ganger', message) ||
45
+ @unit.loader.tasker(domain, 'tasker', message))) ||
46
+ @unit.loader.tasker(domain, tname, message)
40
47
 
41
48
  fail ArgumentError.new(
42
49
  "tasker #{tname.inspect} not found"
@@ -55,13 +62,7 @@ module Flor
55
62
 
56
63
  message['vars'] = gather_vars(executor, tconf, message)
57
64
 
58
- r = @unit.runner.run(self, tconf, message)
59
-
60
- if is_a_message_array?(r)
61
- r
62
- else
63
- []
64
- end
65
+ @unit.caller.call(self, tconf, message)
65
66
  end
66
67
 
67
68
  def return(message)
@@ -71,13 +72,6 @@ module Flor
71
72
 
72
73
  protected
73
74
 
74
- def is_a_message_array?(o)
75
-
76
- o.is_a?(Array) &&
77
- o.first.is_a?(Hash) &&
78
- o.first['point'].is_a?(String)
79
- end
80
-
81
75
  def var_match(k, filter)
82
76
 
83
77
  filter.each { |f| return true if (f.is_a?(String) ? k == f : f.match(k)) }
@@ -108,7 +102,7 @@ module Flor
108
102
 
109
103
  def gather_vars(executor, tconf, message)
110
104
 
111
- # try to return before calling executor.vars(nid) which my be costly...
105
+ # try to return before a potentially costly call to executor.vars(nid)
112
106
 
113
107
  return nil if (tconf.keys & %w[ include_vars exclude_vars ]).empty?
114
108
  # default behaviour, don't pass variables to taskers
@@ -32,7 +32,7 @@ module Flor
32
32
 
33
33
  def notify(executor, message)
34
34
 
35
- @unit.runner.run(executor, @h, message)
35
+ @unit.caller.call(executor, @h, message)
36
36
  end
37
37
 
38
38
  # Avoid the proc/cancel problem upstreams in ConfExecutor, by ignoring
@@ -40,9 +40,9 @@ module Flor
40
40
 
41
41
  args.each do |arg|
42
42
  case arg
43
- when String then name = arg
44
- when Hash then opts = arg
45
- else hook = arg
43
+ when String then name = arg
44
+ when Hash then opts = arg
45
+ else hook = arg
46
46
  end
47
47
  end
48
48
 
@@ -56,7 +56,7 @@ module Flor
56
56
  (
57
57
  @hooks + executor.traps_and_hooks
58
58
  )
59
- .inject([]) do |a, (_, opts, hook, block)|
59
+ .inject([]) { |a, (_, opts, hook, block)|
60
60
  # name of hook is piped into "_" oblivion
61
61
 
62
62
  a.concat(
@@ -68,8 +68,7 @@ module Flor
68
68
  executor.trigger_hook(hook, message)
69
69
  else
70
70
  executor.trigger_block(block, opts, message)
71
- end)
72
- end
71
+ end) }
73
72
  end
74
73
 
75
74
  protected
@@ -86,6 +85,12 @@ module Flor
86
85
  array ? Array(r) : r
87
86
  end
88
87
 
88
+ def includes?(arr, value)
89
+
90
+ Array(value)
91
+ .find { |v| arr.find { |e| e.is_a?(Regexp) ? e.match(v) : e == v } }
92
+ end
93
+
89
94
  def match?(executor, hook, opts, message)
90
95
 
91
96
  opts = hook.opts if hook.respond_to?(:opts) && opts.empty?
@@ -128,11 +133,18 @@ module Flor
128
133
 
129
134
  if ts = o(opts, :tag, :t, [])
130
135
  return false unless %w[ entered left ].include?(message['point'])
131
- return false unless (message['tags'] & ts).any?
136
+ return false unless includes?(ts, message['tags'])
132
137
  end
133
138
 
134
139
  if ns = o(opts, :name, :n)
135
- return false unless ns.include?(message['name'])
140
+ name = message['name']
141
+ return false \
142
+ unless ns.find { |n|
143
+ case n
144
+ when String then name == n
145
+ when Array then name.match(Flor.to_regex(n))
146
+ else false
147
+ end }
136
148
  end
137
149
 
138
150
  node = nil
@@ -148,12 +160,12 @@ module Flor
148
160
 
149
161
  if hps = o(opts, :heap, :hp, [])
150
162
  return false unless node ||= executor.node(message['nid'])
151
- return false unless hps.include?(node['heap'])
163
+ return false unless includes?(hps, node['heap'])
152
164
  end
153
165
 
154
166
  if hts = o(opts, :heat, :ht, [])
155
167
  return false unless node ||= executor.node(message['nid'])
156
- return false unless hts.include?(node['heat0'])
168
+ return false unless includes?(hts, node['heat0'])
157
169
  end
158
170
 
159
171
  true
@@ -25,7 +25,7 @@ module Flor
25
25
  .collect { |pa| [ pa, expose_d(pa, {}) ] }
26
26
  .select { |pa, d| is_subdomain?(domain, d) }
27
27
  .sort_by { |pa, d| d.count('.') }
28
- .inject({}) { |vars, (pa, d)| vars.merge!(interpret(pa)) }
28
+ .inject({}) { |vars, (pa, d)| vars.merge!(eval(pa, {})) }
29
29
  end
30
30
 
31
31
  #def procedures(path)
@@ -55,7 +55,7 @@ module Flor
55
55
  path ? [ Flor.relativize_path(path), File.read(path) ] : nil
56
56
  end
57
57
 
58
- def tasker(domain, name=nil)
58
+ def tasker(domain, name, message={})
59
59
 
60
60
  # NB: do not relativize path, because Ruby load path != cwd,
61
61
  # stay absolute for `require` and `load`
@@ -73,23 +73,18 @@ module Flor
73
73
 
74
74
  return nil unless path
75
75
 
76
- conf = interpret(path)
76
+ conf = eval(path, message)
77
77
 
78
78
  return conf if n == name
79
79
 
80
- con = conf[name]
80
+ conf = conf[name]
81
81
 
82
- return nil unless con
82
+ return nil unless conf
83
83
 
84
- pa = conf['_path']
84
+ (conf.is_a?(Array) ? conf : [ conf ])
85
+ .each { |h| h['_path'] = path }
85
86
 
86
- if con.is_a?(Array)
87
- con.each { |c| c['_path'] = pa }
88
- else
89
- con['_path'] = pa
90
- end
91
-
92
- con
87
+ conf
93
88
  end
94
89
 
95
90
  def hooks(domain, name=nil)
@@ -103,7 +98,7 @@ module Flor
103
98
  .select { |pa, d| is_subdomain?(domain, d) }
104
99
  .sort_by { |pa, d| d.count('.') }
105
100
  .collect { |pa, d|
106
- interpret(pa).each_with_index { |h, i|
101
+ eval(pa, {}).each_with_index { |h, i|
107
102
  h['_path'] = pa + ":#{i}" } }
108
103
  .flatten(1)
109
104
  end
@@ -166,17 +161,22 @@ module Flor
166
161
  end
167
162
  end
168
163
 
169
- def interpret(path)
164
+ def eval(path, context)
170
165
 
171
- @mutex.synchronize do
166
+ src =
167
+ @mutex.synchronize do
172
168
 
173
- mt1 = File.mtime(path)
174
- val, mt0 = @cache[path]
175
- #p [ :cached, path ] if val && mt1 == mt0
176
- return val if val && mt1 == mt0
169
+ mt1 = File.mtime(path)
170
+ val, mt0 = @cache[path]
177
171
 
178
- (@cache[path] = [ Flor::ConfExecutor.interpret(path), mt1 ]).first
179
- end
172
+ if val && mt1 == mt0
173
+ val
174
+ else
175
+ (@cache[path] = [ Flor::ConfExecutor.load(path), mt1 ]).first
176
+ end
177
+ end
178
+
179
+ Flor::ConfExecutor.interpret(path, src, context)
180
180
  end
181
181
  end
182
182
  end