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
@@ -0,0 +1,134 @@
1
+
2
+ class Flor::Pro::Merge < Flor::Procedure
3
+ #
4
+ # Merges objects or arrays.
5
+ #
6
+ # With objects:
7
+ # ```
8
+ # merge {} # => {}
9
+ # merge { a: 0 } # => { 'a' => 0 }
10
+ # {}; merge _ # => {}
11
+ # { a: 0 }; merge _ # => { 'a' => 0 }
12
+ #
13
+ # merge { a: 0 b: 1 } { b: 'B' c: 'C' }
14
+ # # => { 'a' => 0, 'b' => 'B', 'c' => 'C' }
15
+ # merge { b: 'B' c: 'C' } { a: 0 b: 1 }
16
+ # # => { 'a' => 0, 'b' => 1, 'c' => 'C' },
17
+ # ```
18
+ #
19
+ # With arrays:
20
+ # ```
21
+ # merge [ 0 1 2 3 ] [ 'a' 'b' 'c' ]
22
+ # # => [ 'a', 'b', 'c', 3 ],
23
+ #
24
+ # merge [] # => []
25
+ # merge [ 0 1 2 ] # => [ 0, 1, 2 ]
26
+ # []; merge _ # => []
27
+ # [ 0 1 2 ]; merge _ # => [ 0, 1, 2 ]
28
+ # ```
29
+ #
30
+ # It determines if it has to deal with arrays or objects by looking at its
31
+ # first argument (not the incoming ret).
32
+ #
33
+ # It fails if the arguments are not all objects or not all arrays.
34
+ #
35
+ # If the attribute `lax:` (or `loose:`) is set to `true`, it doesn't care
36
+ # about non matching arguments and merges anyway:
37
+ # ```
38
+ # merge { a: 0 } { b: 1 } 'nada' { c: 2 } lax: true
39
+ # # => { 'a' => 0, 'b' => 1, 'c' => 2 }
40
+ # merge { a: 0 } { b: 1 } { c: 2 }
41
+ # # => { 'a' => 0, 'b' => 1, 'c' => 2 }
42
+ # merge { a: 0 } { b: 1 } 'nada' { c: 2 } tags: 'xxx' loose: true
43
+ # # => { 'a' => 0, 'b' => 1, 'c' => 2 }
44
+ # ```
45
+ #
46
+ # `strict: false` is OK as well:
47
+ # ```
48
+ # merge { a: 0 } { b: 1 } 'nada' { c: 2 } strict: false
49
+ # # => { 'a' => 0, 'b' => 1, 'c' => 2 }
50
+ # ```
51
+ #
52
+ # ## incoming ret
53
+ #
54
+ # "merge" only draws in the incoming ret if necessary. If there are
55
+ # enough arguments to perform a merge, the incoming ret will not be taken
56
+ # into account.
57
+ # You can add the incoming to the merge simply with `f.ret`.
58
+ #
59
+ # ```
60
+ # [ 0 1 2 ]; merge [ 0 1 'deux' 3 ] # => [ 0 1 'deux' 3 ]
61
+ # [ 0 1 2 3 4 ]; merge [ 0 1 2 3 ] [ 0 'un' 2 ] # => [ 0 'un' 2 3 ]
62
+ # [ 0 1 2 3 4 ]; merge f.ret [ 0 1 2 3 ] [ 0 'un' 2 ] # => [ 0 'un' 2 3 ]
63
+ # [ 0 1 2 3 4 ]; merge [ 0 1 2 3 ] [ 0 'un' 2 ] f.ret # => [ 0 1 2 3 4 ]
64
+ # [ 0 ]; merge { a: 1 } { a: 'one' } # => { a: 'one' }
65
+ # ```
66
+ #
67
+ # ## see also
68
+ #
69
+ # reverse, length, keys
70
+
71
+ name 'merge'
72
+
73
+ def pre_execute
74
+
75
+ @node['atts'] = []
76
+ @node['rets'] = []
77
+
78
+ unatt_unkeyed_children
79
+ end
80
+
81
+ def receive_last
82
+
83
+ indexes = @node['rets']
84
+ .each_with_index
85
+ .inject({ array: [], object: [], other: [] }) { |is, (e, i)|
86
+ case e
87
+ when Array then is[:array]
88
+ when Hash then is[:object]
89
+ else is[:other]
90
+ end << i
91
+ is }
92
+
93
+ a0 = indexes[:array].first || @node['rets'].length
94
+ o0 = indexes[:object].first || @node['rets'].length
95
+
96
+ kln = a0 < o0 ? :array : :object
97
+ okln = kln == :array ? :object : :array
98
+
99
+ cols = indexes[kln].collect { |i| @node['rets'][i] }
100
+
101
+ cols.unshift(node_payload_ret) \
102
+ if cols.length == 1 && Flor.type(node_payload_ret) == kln
103
+
104
+ fail Flor::FlorError.new('found no array or object to merge', self) \
105
+ if cols.empty?
106
+
107
+ unless att('lax', 'loose') == true || att('strict') == false
108
+
109
+ others = (indexes[:other] + indexes[okln]).sort
110
+
111
+ fail Flor::FlorError.new(
112
+ "found a non-#{kln} item (#{Flor.type(@node['rets'][others[0]])} item)",
113
+ self
114
+ ) if others.any?
115
+ end
116
+
117
+ wrap('ret' => send(kln == :array ? :merge_arrays : :merge_objects, cols))
118
+ end
119
+
120
+ protected
121
+
122
+ def merge_arrays(as)
123
+
124
+ as
125
+ .inject([]) { |r, a| a.each_with_index { |e, i| r[i] = e }; r }
126
+ end
127
+
128
+ def merge_objects(os)
129
+
130
+ os
131
+ .inject({}) { |r, o| r.merge(o) }
132
+ end
133
+ end
134
+
@@ -31,7 +31,7 @@ class Flor::Pro::Move < Flor::Procedure
31
31
 
32
32
  wrap_cancel(
33
33
  'nid' => nid,
34
- 'flavour' => @node['heap'], # "move"
34
+ 'flavour' => heap, # "move"
35
35
  'payload' => rep.any? ? payload.copy_current : payload.current,
36
36
  'to' => to) +
37
37
  rep
@@ -1,7 +1,7 @@
1
1
 
2
2
  class Flor::Pro::NoRet < Flor::Procedure
3
3
  #
4
- # executes its children, but doesn't alter the received f.ret
4
+ # Executes its children, but doesn't alter the received f.ret
5
5
  #
6
6
  # ```
7
7
  # sequence
@@ -1,7 +1,7 @@
1
1
 
2
2
  class Flor::Pro::Not < Flor::Procedure
3
3
  #
4
- # `not` negates its last child (or its last unkeyed attribute)
4
+ # Negates its last child (or its last unkeyed attribute)
5
5
  #
6
6
  # ```
7
7
  # not _ # --> true
@@ -16,6 +16,20 @@ class Flor::Pro::Not < Flor::Procedure
16
16
  # true
17
17
  # false # --> true
18
18
  # ```
19
+ #
20
+ # ```
21
+ # not true false # --> true
22
+ # ```
23
+ #
24
+ # ## Warning
25
+ #
26
+ # ```
27
+ # and not(false) not(false) # --> false
28
+ # ```
29
+ # It is recommended to use:
30
+ # ```
31
+ # and (not false) (not false) # --> true
32
+ # ```
19
33
 
20
34
  name 'not'
21
35
 
@@ -44,6 +44,17 @@ class Flor::Pro::On < Flor::Macro
44
44
  # Please note that "error" in `on error` is not quoted, nor double quoted.
45
45
  # If it were, it would trap the signal named "error".
46
46
  #
47
+ # `on error` accepts the same criteria as [on_error](on_error.md), as in:
48
+ # ```
49
+ # sequence
50
+ # on error (/timeout/)
51
+ # charly "it timed out"
52
+ # on error
53
+ # charly "it failed", err
54
+ # alice 'do this'
55
+ # bob 'do that'
56
+ # ```
57
+ #
47
58
  #
48
59
  # ## cancel
49
60
  #
@@ -48,7 +48,11 @@ class Flor::Pro::OnCancel < Flor::Procedure
48
48
 
49
49
  store_on(:cancel)
50
50
 
51
- super
51
+ ms = super
52
+
53
+ ms.first['from_on'] = 'cancel'
54
+
55
+ ms
52
56
  end
53
57
  end
54
58
 
@@ -16,7 +16,7 @@ class Flor::Pro::OnError < Flor::Procedure
16
16
  # push f.l 1
17
17
  # ```
18
18
  # Where the field `l` ends up containing
19
- # `[ 0, "don't know how to apply \"x\"" ]`.
19
+ # `[ 0, "cannot find \"x\"" ]`.
20
20
  #
21
21
  # ```
22
22
  # set f.l []
@@ -47,6 +47,48 @@ class Flor::Pro::OnError < Flor::Procedure
47
47
  # # ...
48
48
  # ```
49
49
  #
50
+ # ## on_error kriteria
51
+ #
52
+ # Similarly to Ruby, one may catch certain types of errors.
53
+ #
54
+ # In the follow example, Charly is tasked with "it failed" and the err
55
+ # if there is a Ruby error of class `RuntimeError` happens in the sequence:
56
+ # ```
57
+ # sequence
58
+ # on_error class: 'RuntimeError' (def err \ charly "it failed", err)
59
+ # alice 'do this'
60
+ # bob 'do that'
61
+ # ```
62
+ #
63
+ # One can shorten it to:
64
+ # ```
65
+ # sequence
66
+ # on_error 'RuntimeError' (def err \ charly "it failed", err)
67
+ # alice 'do this'
68
+ # bob 'do that'
69
+ # ```
70
+ # But this short version will check the Ruby class name and then the error
71
+ # message.
72
+ #
73
+ # In the next example, Charly is tasked with "it timed out" if the error
74
+ # class or the error message match the regex `/timeout/`:
75
+ # ```
76
+ # sequence
77
+ # on_error (/timeout/) (def err \ charly "it timed out")
78
+ # on_error (def err \ charly "it failed", err)
79
+ # alice 'do this'
80
+ # bob 'do that'
81
+ # ```
82
+ # Order matters.
83
+ #
84
+ # Please note that you can't set a criteria when you're using the `on_error:`
85
+ # attribute, as in:
86
+ # ```
87
+ # sequence on_error: (def err \ charly "it failed", err)
88
+ # alice 'do this'
89
+ # bob 'do that'
90
+ # ```
91
+ #
50
92
  # ## see also
51
93
  #
52
94
  # On, on_cancel.
@@ -56,13 +98,36 @@ class Flor::Pro::OnError < Flor::Procedure
56
98
  def pre_execute
57
99
 
58
100
  unatt_unkeyed_children
101
+
102
+ @node['atts'] = []
103
+ @node['rets'] = []
59
104
  end
60
105
 
61
- def receive_non_att
106
+ def receive_last
107
+
108
+ prc = @node['rets'].find { |r| Flor.is_func_tree?(r) }
109
+
110
+ line = tree[2]
111
+
112
+ cri = []
113
+ if cla = att('class', 'klass')
114
+ cri << [ 'class', cla, line ]
115
+ end
116
+ if str = @node['rets'].find { |r| r.is_a?(String) }
117
+ cri << [ 'string', str, line ]
118
+ end
119
+ if rex = @node['rets'].find { |r| Flor.is_regex_tree?(r) }
120
+ cri << [ 'regex', *rex[1..-1] ]
121
+ end
122
+ cri << '*' if cri.empty?
123
+
124
+ store_on(:error, prc, cri)
125
+
126
+ ms = super
62
127
 
63
- store_on(:error)
128
+ ms.first['from_on'] = 'error'
64
129
 
65
- super
130
+ ms
66
131
  end
67
132
  end
68
133
 
@@ -39,7 +39,7 @@ class Flor::Pro::Push < Flor::Procedure
39
39
  def pre_execute
40
40
 
41
41
  unatt_unkeyed_children
42
- stringify_first_child
42
+ rep_first_child
43
43
  end
44
44
 
45
45
  def receive_non_att
@@ -47,7 +47,7 @@ class Flor::Pro::Push < Flor::Procedure
47
47
  if ! @node['arr']
48
48
  @node['arr'] = payload['ret']
49
49
  else
50
- @node['to_push'] = payload['ret']
50
+ @node['val'] = payload['ret']
51
51
  end
52
52
 
53
53
  super
@@ -55,7 +55,7 @@ class Flor::Pro::Push < Flor::Procedure
55
55
 
56
56
  def receive_last
57
57
 
58
- push(@node.has_key?('to_push') ? @node['to_push'] : node_payload_ret)
58
+ push(@node.has_key?('val') ? @node['val'] : node_payload_ret)
59
59
 
60
60
  payload['ret'] = node_payload_ret \
61
61
  unless tree[0] == 'pushr'
@@ -67,12 +67,7 @@ class Flor::Pro::Push < Flor::Procedure
67
67
 
68
68
  def push(val)
69
69
 
70
- arr = @node['arr']
71
-
72
- if arr.is_a?(String)
73
- payload.copy if arr[0, 1] == 'f'
74
- arr = lookup(arr)
75
- end
70
+ arr = lookup_value(@node['arr']) rescue nil
76
71
 
77
72
  fail Flor::FlorError.new(
78
73
  "cannot push to given target (#{arr.class})", self
@@ -1,7 +1,7 @@
1
1
 
2
2
  class Flor::Pro::Range < Flor::Procedure
3
3
  #
4
- # "range" is a procedure to generate ranges of integers.
4
+ # Generates ranges of integers.
5
5
  #
6
6
  # ```
7
7
  # # range {end}
@@ -41,15 +41,15 @@ class Flor::Pro::Range < Flor::Procedure
41
41
  edn = rets[1] || rets[0] || 0
42
42
  ste = rets[2] || ((sta > edn) ? -1 : 1)
43
43
 
44
- asta = att('start') || att('from')
45
- aedn = att('end') || att('to')
46
- aste = att('step') || att('by') || att('inc')
44
+ asta = att('start', 'from')
45
+ aedn = att('end', 'to')
46
+ aste = att('step', 'by', 'inc')
47
47
 
48
48
  sta = asta if asta
49
49
  edn = aedn if aedn
50
50
  ste = aste if aste
51
51
 
52
- fail Flor::FlorError.new("#{@node['heat0']} step is 0", self) \
52
+ fail Flor::FlorError.new("#{heap} step is 0", self) \
53
53
  if ste == 0
54
54
 
55
55
  #payload['ret'] = (sta..edn - 1).step(ste).to_a
@@ -1,7 +1,7 @@
1
1
 
2
2
  class Flor::Pro::Reduce < Flor::Pro::Iterator
3
3
  #
4
- # Reduce takes a collection and a function. It reduces the collection
4
+ # Takes a collection and a function, reduces the collection
5
5
  # to a single result thanks to the function.
6
6
  #
7
7
  # ```
@@ -54,7 +54,7 @@ class Flor::Pro::Reduce < Flor::Pro::Iterator
54
54
  @node['fun'] ||= a
55
55
  elsif Flor.is_proc_tree?(a)
56
56
  @node['fun'] ||= proc_to_fun(a)
57
- elsif a.is_a?(Array) || a.is_a?(Hash)
57
+ elsif Flor.is_collection?(a)
58
58
  @node['ocol'] ||= a
59
59
  else
60
60
  @node['res'] ||= a
@@ -68,29 +68,16 @@ class Flor::Pro::Reduce < Flor::Pro::Iterator
68
68
  ) unless @node['fun']
69
69
  fail Flor::FlorError.new(
70
70
  "collection not given to #{heap.inspect}", self
71
- ) unless ocol.is_a?(Array) || ocol.is_a?(Hash)
71
+ ) unless Flor.is_collection?(ocol)
72
72
 
73
73
  @node['col'] = Flor.to_coll(@node['ocol'])
74
-
75
74
  @node['res'] ||= @node['col'].shift
76
-
77
75
  @node['args'] = nil
78
76
  end
79
77
 
80
- def determine_iteration_vars
81
-
82
- res = @node['res']
83
- idx = @node['idx']
84
- elt = @node['col'][idx]
85
- len = @node['col'].length
78
+ def determine_iteration_args
86
79
 
87
- if @node['ocol'].is_a?(Array)
88
- { 'res' => res, 'elt' => elt,
89
- 'idx' => idx, 'len' => len }
90
- else
91
- { 'res' => res, 'key' => elt[0], 'val' => elt[1],
92
- 'idx' => idx, 'len' => len }
93
- end
80
+ super.unshift([ 'res', @node['res'] ])
94
81
  end
95
82
 
96
83
  def receive_iteration
@@ -0,0 +1,26 @@
1
+
2
+ class Flor::Pro::Return < Flor::Procedure
3
+
4
+ name 'return'
5
+
6
+ def receive_last
7
+
8
+ si = Flor.sub_nid(nid)
9
+ n = @node
10
+
11
+ target =
12
+ loop do
13
+ pn = parent_node(n)
14
+ break nil unless pn
15
+ psi = Flor.sub_nid(pn['nid'])
16
+ break n['nid'] if psi != si
17
+ n = pn
18
+ end
19
+
20
+ fail Flor::FlorError.new('"return" outside of function', self) \
21
+ unless target
22
+
23
+ wrap_cancel('nid' => target, 'flavour' => 'return')
24
+ end
25
+ end
26
+