flor 0.15.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +14 -1
  3. data/CREDITS.md +1 -0
  4. data/LICENSE.txt +1 -1
  5. data/Makefile +6 -2
  6. data/README.md +2 -1
  7. data/flor.gemspec +12 -2
  8. data/lib/flor.rb +3 -3
  9. data/lib/flor/colours.rb +1 -1
  10. data/lib/flor/conf.rb +3 -4
  11. data/lib/flor/core/executor.rb +31 -61
  12. data/lib/flor/core/node.rb +213 -96
  13. data/lib/flor/core/procedure.rb +194 -75
  14. data/lib/flor/core/texecutor.rb +6 -7
  15. data/lib/flor/djan.rb +41 -22
  16. data/lib/flor/flor.rb +137 -42
  17. data/lib/flor/id.rb +77 -59
  18. data/lib/flor/log.rb +43 -22
  19. data/lib/flor/migrations/0001_tables.rb +7 -7
  20. data/lib/flor/parser.rb +271 -74
  21. data/lib/flor/pcore/_apply.rb +108 -0
  22. data/lib/flor/pcore/_atom.rb +2 -4
  23. data/lib/flor/pcore/_att.rb +54 -37
  24. data/lib/flor/pcore/_dmute.rb +18 -0
  25. data/lib/flor/pcore/_dol.rb +17 -0
  26. data/lib/flor/pcore/_dqs.rb +35 -0
  27. data/lib/flor/pcore/_head.rb +25 -0
  28. data/lib/flor/pcore/_obj.rb +1 -3
  29. data/lib/flor/pcore/_pat_guard.rb +1 -1
  30. data/lib/flor/pcore/_pat_obj.rb +11 -3
  31. data/lib/flor/pcore/_pat_regex.rb +16 -2
  32. data/lib/flor/pcore/_ref.rb +51 -0
  33. data/lib/flor/pcore/_rxs.rb +27 -0
  34. data/lib/flor/pcore/_val.rb +11 -6
  35. data/lib/flor/pcore/{logo.rb → andor.rb} +4 -6
  36. data/lib/flor/pcore/apply.rb +72 -2
  37. data/lib/flor/pcore/arith.rb +16 -4
  38. data/lib/flor/pcore/array_qmark.rb +100 -0
  39. data/lib/flor/pcore/break.rb +1 -2
  40. data/lib/flor/pcore/case.rb +1 -1
  41. data/lib/flor/pcore/cmp.rb +3 -2
  42. data/lib/flor/pcore/collect.rb +2 -2
  43. data/lib/flor/pcore/cond.rb +19 -1
  44. data/lib/flor/pcore/cursor.rb +12 -11
  45. data/lib/flor/pcore/define.rb +30 -4
  46. data/lib/flor/pcore/do_return.rb +3 -0
  47. data/lib/flor/pcore/flatten.rb +39 -0
  48. data/lib/flor/pcore/if.rb +15 -5
  49. data/lib/flor/pcore/includes.rb +5 -2
  50. data/lib/flor/pcore/inject.rb +1 -1
  51. data/lib/flor/pcore/iterator.rb +28 -18
  52. data/lib/flor/pcore/keys.rb +2 -2
  53. data/lib/flor/pcore/map.rb +19 -1
  54. data/lib/flor/pcore/match.rb +2 -2
  55. data/lib/flor/pcore/matchr.rb +18 -5
  56. data/lib/flor/pcore/max.rb +51 -0
  57. data/lib/flor/pcore/merge.rb +134 -0
  58. data/lib/flor/pcore/move.rb +1 -1
  59. data/lib/flor/pcore/noret.rb +1 -1
  60. data/lib/flor/pcore/not.rb +15 -1
  61. data/lib/flor/pcore/on.rb +11 -0
  62. data/lib/flor/pcore/on_cancel.rb +5 -1
  63. data/lib/flor/pcore/on_error.rb +69 -4
  64. data/lib/flor/pcore/push.rb +4 -9
  65. data/lib/flor/pcore/range.rb +5 -5
  66. data/lib/flor/pcore/reduce.rb +5 -18
  67. data/lib/flor/pcore/return.rb +26 -0
  68. data/lib/flor/pcore/reverse.rb +4 -0
  69. data/lib/flor/pcore/sequence.rb +8 -1
  70. data/lib/flor/pcore/set.rb +74 -15
  71. data/lib/flor/pcore/shuffle.rb +71 -0
  72. data/lib/flor/pcore/slice.rb +137 -0
  73. data/lib/flor/pcore/sort.rb +244 -0
  74. data/lib/flor/pcore/sort_by.rb +67 -0
  75. data/lib/flor/pcore/split.rb +39 -0
  76. data/lib/flor/pcore/stall.rb +1 -1
  77. data/lib/flor/pcore/strings.rb +123 -0
  78. data/lib/flor/pcore/timestamp.rb +34 -0
  79. data/lib/flor/pcore/to_array.rb +2 -3
  80. data/lib/flor/pcore/twig.rb +1 -1
  81. data/lib/flor/pcore/type_of.rb +37 -0
  82. data/lib/flor/pcore/until.rb +3 -3
  83. data/lib/flor/punit/cancel.rb +3 -3
  84. data/lib/flor/punit/ccollect.rb +29 -0
  85. data/lib/flor/punit/cmap.rb +76 -20
  86. data/lib/flor/punit/concurrence.rb +440 -33
  87. data/lib/flor/punit/cron.rb +1 -1
  88. data/lib/flor/punit/every.rb +1 -1
  89. data/lib/flor/punit/graft.rb +2 -3
  90. data/lib/flor/punit/on_timeout.rb +5 -1
  91. data/lib/flor/punit/part.rb +63 -0
  92. data/lib/flor/punit/schedule.rb +1 -1
  93. data/lib/flor/punit/task.rb +52 -10
  94. data/lib/flor/punit/trap.rb +4 -5
  95. data/lib/flor/tools/shell.rb +37 -18
  96. data/lib/flor/unit/caller.rb +23 -11
  97. data/lib/flor/unit/executor.rb +33 -12
  98. data/lib/flor/unit/ganger.rb +10 -1
  99. data/lib/flor/unit/hook.rb +2 -1
  100. data/lib/flor/unit/hooker.rb +13 -2
  101. data/lib/flor/unit/loader.rb +7 -7
  102. data/lib/flor/unit/logger.rb +15 -17
  103. data/lib/flor/unit/models.rb +4 -2
  104. data/lib/flor/unit/models/execution.rb +83 -38
  105. data/lib/flor/unit/models/message.rb +16 -0
  106. data/lib/flor/unit/models/pointer.rb +24 -0
  107. data/lib/flor/unit/models/timer.rb +25 -4
  108. data/lib/flor/unit/models/trace.rb +14 -0
  109. data/lib/flor/unit/models/trap.rb +39 -14
  110. data/lib/flor/unit/scheduler.rb +11 -7
  111. data/lib/flor/unit/storage.rb +55 -39
  112. data/lib/flor/unit/taskers.rb +17 -14
  113. data/lib/flor/unit/waiter.rb +4 -3
  114. metadata +40 -10
  115. data/lib/flor/changes.rb +0 -26
  116. data/lib/flor/dollar.rb +0 -224
  117. data/lib/flor/unit/hooks.rb +0 -37
@@ -4,7 +4,7 @@ require 'flor/punit/schedule'
4
4
 
5
5
  class Flor::Pro::Cron < Flor::Macro::Schedule
6
6
  #
7
- # "cron" is a macro procedure.
7
+ # A macro-procedure, rewriting itself to `schedule cron: ...`.
8
8
  #
9
9
  # ```
10
10
  # cron '0 0 1 jan *'
@@ -4,7 +4,7 @@ require 'flor/punit/schedule'
4
4
 
5
5
  class Flor::Pro::Every < Flor::Macro::Schedule
6
6
  #
7
- # "every" is a macro procedure.
7
+ # A macro-procedure, rewriting itself to `schedule every: ...`.
8
8
  #
9
9
  # ```
10
10
  # every 'day at midnight'
@@ -1,7 +1,7 @@
1
1
 
2
2
  class Flor::Pro::Graft < Flor::Procedure
3
3
  #
4
- # Graft a subtree into the current flo
4
+ # Graft a subtree into the current execution.
5
5
  #
6
6
  # Given
7
7
  # ```
@@ -65,8 +65,7 @@ class Flor::Pro::Graft < Flor::Procedure
65
65
  # graft subtree into parent node
66
66
 
67
67
  parent_tree = lookup_tree(parent)
68
- cid = Flor.child_id(nid)
69
- parent_tree[1][cid] = tree
68
+ parent_tree[1][child_id] = tree
70
69
 
71
70
  # re-apply self with subtree
72
71
 
@@ -32,7 +32,11 @@ class Flor::Pro::OnTimeout < Flor::Procedure
32
32
 
33
33
  store_on(:timeout)
34
34
 
35
- super
35
+ ms = super
36
+
37
+ ms.first['from_on'] = 'timeout'
38
+
39
+ ms
36
40
  end
37
41
  end
38
42
 
@@ -0,0 +1,63 @@
1
+
2
+ class Flor::Pro::Part < Flor::Procedure
3
+
4
+ names %w[ part flank ]
5
+
6
+ # +-------------------+--------------------+
7
+ # ruote flor | replies to parent | cancellable |
8
+ # +-------+-------+------------+-------------------+--------------------+
9
+ # | fork | part | part | immediately | no (not reachable) |
10
+ # | | flunk | r: false | never | no (not reachable) |
11
+ # | flank | flank | flank | immediately | yes |
12
+ # | lose | norep | r: false | never | yes |
13
+ # +-------+-------+------------+-------------------+--------------------+
14
+ #
15
+ # reply/r: false, cancellable/c: false
16
+ #
17
+ # to part, to flank, the subject is the diverging branch
18
+
19
+ def pre_execute
20
+
21
+ @node['atts'] = []
22
+ end
23
+
24
+ def receive_last_att
25
+
26
+ @node['tree'] = Flor.dup(tree)
27
+ @node['replyto'] = nil
28
+
29
+ rep = true
30
+ @node['can'] = (heap == 'flank')
31
+
32
+ if (r = att('reply', 'rep', 'r')) != nil
33
+ rep = r
34
+ end
35
+ if (c = att('cancellable', 'can', 'c')) != nil
36
+ @node['can'] = c
37
+ end
38
+
39
+ fla = @node['can'] ? 'flank' : 'part'
40
+ # so it is possible to have `flank r: true c: false` (iow: `part`)...
41
+
42
+ (rep ? wrap('flavour' => fla, 'nid' => parent, 'ret' => nid) : []) +
43
+ super
44
+ end
45
+
46
+ def cancel
47
+
48
+ if @node['can'] || from != parent
49
+ #
50
+ # if the node is cancellable or the cancel comes not from the parent
51
+ # the cancelling goes on...
52
+ #
53
+ super
54
+
55
+ else
56
+ #
57
+ # else the cancelling does not proceed further (down)
58
+ #
59
+ []
60
+ end
61
+ end
62
+ end
63
+
@@ -1,7 +1,7 @@
1
1
 
2
2
  class Flor::Pro::Schedule < Flor::Procedure
3
3
  #
4
- # Schedules a function
4
+ # Schedules a function in time.
5
5
  #
6
6
  # ```
7
7
  # schedule cron: '0 0 1 jan *' # every 1st day of the year, check systems
@@ -1,5 +1,39 @@
1
1
 
2
2
  class Flor::Pro::Task < Flor::Procedure
3
+ #
4
+ # Tasks a tasker with a task.
5
+ #
6
+ # ```
7
+ # task 'clean up' by: 'alan'
8
+ # task 'clean up' for: 'alan'
9
+ # task 'clean up' assign: 'alan'
10
+ # task 'alan' with: 'clean up'
11
+ # alan task: 'clean up' # {tasker} task: {taskname}
12
+ # task 'alan'
13
+ # # ...
14
+ # ```
15
+ #
16
+ # Note that the quotes are the tasker name can be omitted:
17
+ # ```
18
+ # task 'clean up' by: alan
19
+ # task 'clean up' for: alan
20
+ # task 'clean up' assign: alan
21
+ # task alan with: 'clean up'
22
+ # alan task: 'clean up' # {tasker} task: {taskname}
23
+ # task alan
24
+ # # ...
25
+ # ```
26
+ #
27
+ # Tasking hands a task (a message hash serializable to JSON) to a tasker.
28
+ # See [tasks](../tasks.md) for more information.
29
+ #
30
+ # Since routing tasks among taskers is flor essential "task", this "task"
31
+ # procedure is very important.
32
+ #
33
+ # Note that "task" can be implicit, as in:
34
+ # ```
35
+ # alan task: 'clean up'
36
+ # ```
3
37
 
4
38
  name 'task'
5
39
 
@@ -10,11 +44,15 @@ class Flor::Pro::Task < Flor::Procedure
10
44
 
11
45
  def receive
12
46
 
13
- return wrap_reply('payload' => determine_reply_payload) \
14
- if point == 'receive' && from == nil
15
-
16
- super
47
+ return super if point != 'receive' || from != nil
17
48
  # which goes to #receive or #receive_when_status
49
+
50
+ pl = determine_reply_payload
51
+ #pl['ret'] = node_payload_ret
52
+ # No, let's leave it at last f.ret wins...
53
+
54
+ wrap_reply('payload' => pl)
55
+ # "task" done, reply to parent node
18
56
  end
19
57
 
20
58
  alias receive_when_closed receive_from_child_when_closed
@@ -25,21 +63,25 @@ class Flor::Pro::Task < Flor::Procedure
25
63
  # task 'clean up' for: 'alan'
26
64
  # task 'clean up' assign: 'alan'
27
65
  # task 'alan' with: 'clean up'
66
+ # alan task: 'clean up'
67
+ #
28
68
  # clean_up assign: 'alan'
29
69
  # "clean up" assign: 'alan'
30
- # alan task: 'clean up'
31
70
 
32
- ni = att(nil)
71
+ nis = atts(nil)
33
72
  ta = att('by', 'for', 'assign')
34
73
  tn = att('with', 'task')
35
74
 
36
- tasker = ta || ni
37
-
38
- taskname = tn || ni
39
- taskname = nil if ta == nil && tasker == ni
75
+ tasker = ta || nis.shift
76
+ tasker = tasker[1]['tasker'] if Flor.is_tasker_tree?(tasker)
77
+ #
78
+ taskname = tn || nis.shift
40
79
 
41
80
  attl, attd = determine_atts
42
81
 
82
+ @node['task'] =
83
+ { 'tasker' => tasker, 'name' => taskname }
84
+
43
85
  wrap(
44
86
  'point' => 'task',
45
87
  'exid' => exid, 'nid' => nid,
@@ -8,9 +8,9 @@ class Flor::Pro::Trap < Flor::Procedure
8
8
  #
9
9
  # Once the trap is set (once the execution interprets its branch), it
10
10
  # will trigger for any matching message, unless the `count:` attribute
11
- # is set.
11
+ # is set (it will trigger for the first {count} matching messages).
12
12
  #
13
- # When the execution terminates, the trap is removed as well.
13
+ # When the execution terminates, the trap is removed.
14
14
  #
15
15
  # By default, the observation range is the execution, only messages
16
16
  # in the execution where the trap was set are considered.
@@ -195,8 +195,6 @@ class Flor::Pro::Trap < Flor::Procedure
195
195
 
196
196
  def receive_non_att
197
197
 
198
- return execute_child(@ncid) if children[@ncid]
199
-
200
198
  fun = @fcid > 0 ? payload['ret'] : nil
201
199
 
202
200
  points = att_a('point', 'points', nil)
@@ -237,7 +235,7 @@ class Flor::Pro::Trap < Flor::Procedure
237
235
  count = 1 if fun == nil # blocking mode implies count: 1
238
236
  tra['count'] = count if count
239
237
 
240
- tra['range'] = att('range') || att('scope') || 'subnid'
238
+ tra['range'] = att('range', 'scope') || 'subnid'
241
239
 
242
240
  @node['trapped'] = true
243
241
 
@@ -253,6 +251,7 @@ class Flor::Pro::Trap < Flor::Procedure
253
251
  def receive
254
252
 
255
253
  return [] if @node['trapped']
254
+
256
255
  super
257
256
  end
258
257
 
@@ -217,9 +217,30 @@ module Flor::Tools
217
217
  def arg(line, index=1); args(line)[index]; end
218
218
  alias fname arg
219
219
 
220
+ def choose_execution_path(frag, option)
221
+
222
+ fail ArgumentError.new("'frag' argument missing") \
223
+ unless frag
224
+
225
+ exe = lookup_execution(frag)
226
+
227
+ fail ArgumentError.new("execution matching \"%#{frag}%\" not found") \
228
+ unless exe
229
+
230
+ d = File.join(@root, 'tmp')
231
+ FileUtils.mkdir_p(d)
232
+ fn = File.join(d, "exe__#{exe.exid}.rb")
233
+
234
+ File.open(fn, 'wb') { |f| PP.pp(exe.data, f, 79) } \
235
+ if ! File.exist?(fn) || option == 'force'
236
+
237
+ fn
238
+ end
239
+
220
240
  def choose_path(line)
221
241
 
222
242
  b = arg(line, 2)
243
+ c = arg(line, 3)
223
244
 
224
245
  case fname(line)
225
246
  when /\Av/,
@@ -232,14 +253,7 @@ module Flor::Tools
232
253
  when /\Ar/
233
254
  @ra_flow_path
234
255
  when /\Ae/
235
- exe = lookup_execution(b)
236
- #p exe
237
- #pp exe.data
238
- #puts JSON.pretty_generate(exe.data)
239
- #puts JSON.dump(exe.data)
240
- #puts JSON.generate(exe.data, indent: ' ', space: ' ', object_nl: "\n", array_nl: "\n")
241
- puts Flor.to_d(exe.data, width: true, colours: true)
242
- fail NotImplementedError
256
+ choose_execution_path(b, c)
243
257
  else
244
258
  @flow_path
245
259
  end
@@ -442,8 +456,8 @@ fail NotImplementedError
442
456
  def cmd_launch(line)
443
457
 
444
458
  flow = File.read(@flow_path)
445
- variables = Flor::ConfExecutor.interpret(@variables_path)
446
- payload = Flor::ConfExecutor.interpret(@payload_path)
459
+ variables = Flor::ConfExecutor.interpret_path(@variables_path)
460
+ payload = Flor::ConfExecutor.interpret_path(@payload_path)
447
461
  domain = 'shell'
448
462
 
449
463
  vars = Flor::ConfExecutor.interpret_line("\n" + line[6..-1]) rescue {}
@@ -480,7 +494,7 @@ fail NotImplementedError
480
494
  o.puts " #{@c.dark_gray(l)}"
481
495
  end
482
496
  end
483
- rescue => err
497
+ rescue
484
498
  fail ArgumentError.new("no 'manual' for #{cmd.inspect}")
485
499
  end
486
500
 
@@ -489,9 +503,9 @@ fail NotImplementedError
489
503
  o.puts
490
504
  o.puts "## available commands:"
491
505
  o.puts
492
- COMMANDS.each do |cmd|
493
- o.print "* #{@c.yellow(cmd)}"
494
- if hlp = (send("hlp_#{cmd}") rescue nil)
506
+ COMMANDS.each do |c|
507
+ o.print "* #{@c.yellow(c)}"
508
+ if hlp = (send("hlp_#{c}") rescue nil)
495
509
  o.print " - #{hlp.strip}"
496
510
  end
497
511
  o.puts
@@ -555,6 +569,10 @@ fail NotImplementedError
555
569
  edits the payload for the next execution
556
570
  * edit [f|flow]
557
571
  edits the flow for the next execution
572
+ * edit [e|exe] frag
573
+ edits the execution data
574
+ * edit [e|exe] frag force
575
+ edits the execution data (redump the data)
558
576
  * edit t|task frag
559
577
  edits a task currently under var/tasks/
560
578
  * edit r
@@ -583,7 +601,7 @@ fail NotImplementedError
583
601
  def cmd_cat(line)
584
602
 
585
603
  puts " # #{@flow_path}\n"
586
- File.readlines(@flow_path).each { |line| puts " #{line}" }
604
+ File.readlines(@flow_path).each { |l| puts " #{l}" }
587
605
  end
588
606
 
589
607
  def hlp_conf
@@ -624,7 +642,8 @@ fail NotImplementedError
624
642
  %{ prints the file hierarchy for #{@root} }
625
643
  end
626
644
  def cmd_t(line)
627
- page(`tree -C #{@root}`)
645
+ #page(`tree -C #{@root}`)
646
+ page(`tree #{@root}`)
628
647
  end
629
648
 
630
649
  def hlp_tasks
@@ -829,7 +848,7 @@ fail NotImplementedError
829
848
  def cmd_hook(line)
830
849
 
831
850
  @hook =
832
- case (a = arg(line))
851
+ case arg(line)
833
852
  when 'true', 'on'
834
853
  'on'
835
854
  when 'false', 'off'
@@ -1140,7 +1159,7 @@ Once edited (or not), a nato tasker task can be returned to flor (to the schedul
1140
1159
  }
1141
1160
  #Readline.completion_append_character =
1142
1161
  # " "
1143
- rescue LoadError => le
1162
+ rescue LoadError
1144
1163
  def prompt_and_read
1145
1164
  print(prompt)
1146
1165
  ($stdin.readline rescue false)
@@ -26,7 +26,7 @@ module Flor
26
26
 
27
27
  rescue => err
28
28
 
29
- [ Flor.to_error(err) ]
29
+ [ Flor.to_error_message(message, err) ]
30
30
  end
31
31
 
32
32
  protected
@@ -52,31 +52,36 @@ module Flor
52
52
  fail ArgumentError.new('".." not allowed in paths') if pa =~ /\.\./
53
53
  load(fjoin(root, pa)) }
54
54
 
55
+ #
56
+ # initialize
57
+
55
58
  k = Flor.const_lookup(conf['class'] || conf['module'])
56
59
 
57
60
  o =
58
61
  if k.class == Module
59
62
  k
60
63
  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)
64
+ case i = k.instance_method(:initialize).arity
65
+ when 1, 2, 3 then k.new(
66
+ *[ service, conf, message ][0, i])
65
67
  when -1 then k.new({
66
68
  service: service, configuration: conf, message: message })
67
69
  else k.new
68
70
  end
69
71
  end
70
72
 
73
+ #
74
+ # call
75
+
71
76
  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)
77
+ ms = [ "on_#{p}", :on_message, :on, p ]
78
+ ms = ms + [ :on_cancel, :cancel ] if p == 'detask'
79
+ m = ms.find { |mm| o.respond_to?(mm) }
76
80
 
77
81
  fail(
78
- "#{k.class.to_s.downcase} #{k} doesn't respond to on, on_#{p} or #{p}"
79
- ) if ! o.respond_to?(m)
82
+ "#{k.class.to_s.downcase} #{k} doesn't respond to " +
83
+ ms[0..-2].collect { |e| "##{e}" }.join(', ') + ", or ##{ms[-1]}"
84
+ ) unless m
80
85
 
81
86
  r =
82
87
  case o.method(m).arity
@@ -88,6 +93,9 @@ module Flor
88
93
  else o.send(m)
89
94
  end
90
95
 
96
+ #
97
+ # reply
98
+
91
99
  to_messages(r)
92
100
  end
93
101
 
@@ -146,6 +154,10 @@ module Flor
146
154
  fail SpawnError.new(status, i.read, f.read) if status.exitstatus != 0
147
155
 
148
156
  [ i.read, status ]
157
+
158
+ ensure
159
+
160
+ [ i, o, f, e, r, w ].each { |x| x.close rescue nil }
149
161
  end
150
162
 
151
163
  class SpawnError < StandardError