aws-codedeploy-agent 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. data/aws-codedeploy-agent.gemspec +5 -5
  2. data/certs/host-agent-deployment-signer-ca-chain.pem +30 -0
  3. data/conf/codedeployagent.yml +0 -1
  4. data/lib/instance_agent.rb +1 -13
  5. data/lib/instance_agent/agent/base.rb +38 -12
  6. data/lib/instance_agent/agent/plugin.rb +21 -0
  7. data/lib/instance_agent/config.rb +2 -1
  8. data/lib/instance_agent/platform/linux_util.rb +4 -0
  9. data/lib/instance_agent/plugins/codedeploy/application_specification/ace_info.rb +133 -0
  10. data/lib/instance_agent/plugins/codedeploy/application_specification/acl_info.rb +163 -0
  11. data/lib/instance_agent/plugins/codedeploy/application_specification/application_specification.rb +143 -0
  12. data/lib/instance_agent/plugins/codedeploy/application_specification/context_info.rb +23 -0
  13. data/lib/instance_agent/plugins/codedeploy/application_specification/file_info.rb +23 -0
  14. data/lib/instance_agent/plugins/codedeploy/application_specification/linux_permission_info.rb +121 -0
  15. data/lib/instance_agent/plugins/codedeploy/application_specification/mode_info.rb +66 -0
  16. data/lib/instance_agent/plugins/codedeploy/application_specification/range_info.rb +134 -0
  17. data/lib/instance_agent/plugins/codedeploy/application_specification/script_info.rb +27 -0
  18. data/lib/instance_agent/plugins/codedeploy/codedeploy_control.rb +100 -0
  19. data/lib/instance_agent/plugins/codedeploy/command_executor.rb +359 -0
  20. data/lib/instance_agent/plugins/codedeploy/command_poller.rb +178 -0
  21. data/lib/instance_agent/plugins/codedeploy/deployment_specification.rb +161 -0
  22. data/lib/instance_agent/plugins/codedeploy/hook_executor.rb +226 -0
  23. data/lib/instance_agent/plugins/codedeploy/install_instruction.rb +389 -0
  24. data/lib/instance_agent/plugins/codedeploy/installer.rb +147 -0
  25. data/lib/instance_agent/plugins/codedeploy/onpremise_config.rb +42 -0
  26. data/lib/instance_agent/plugins/codedeploy/register_plugin.rb +17 -0
  27. data/lib/instance_agent/runner/child.rb +20 -5
  28. data/lib/instance_agent/runner/master.rb +2 -15
  29. data/lib/instance_metadata.rb +2 -2
  30. data/test/certificate_helper.rb +1 -1
  31. data/test/helpers/instance_agent_helper.rb +1 -0
  32. data/test/instance_agent/agent/base_test.rb +16 -3
  33. data/test/instance_agent/config_test.rb +2 -1
  34. data/test/instance_agent/plugins/codedeploy/application_specification_test.rb +1713 -0
  35. data/test/instance_agent/{codedeploy_plugin → plugins/codedeploy}/codedeploy_control_test.rb +1 -1
  36. data/test/instance_agent/{codedeploy_plugin → plugins/codedeploy}/command_executor_test.rb +32 -9
  37. data/test/instance_agent/{codedeploy_plugin → plugins/codedeploy}/command_poller_test.rb +13 -14
  38. data/test/instance_agent/{codedeploy_plugin → plugins/codedeploy}/deployment_specification_test.rb +98 -25
  39. data/test/instance_agent/{codedeploy_plugin → plugins/codedeploy}/hook_executor_test.rb +83 -15
  40. data/test/instance_agent/plugins/codedeploy/install_instruction_test.rb +568 -0
  41. data/test/instance_agent/{codedeploy_plugin → plugins/codedeploy}/installer_test.rb +12 -9
  42. data/test/instance_agent/plugins/codedeploy/onpremise_config_test.rb +72 -0
  43. data/test/instance_agent/runner/child_test.rb +1 -1
  44. data/vendor/gems/.codedeploy-commands-1.0.0.created.rid +1 -1
  45. data/vendor/gems/codedeploy-commands/lib/aws/plugins/deploy_control_endpoint.rb +4 -0
  46. data/vendor/gems/jmespath-1.0.1/lib/jmespath.rb +41 -0
  47. data/vendor/gems/jmespath-1.0.1/lib/jmespath/caching_parser.rb +30 -0
  48. data/vendor/gems/jmespath-1.0.1/lib/jmespath/errors.rb +17 -0
  49. data/vendor/gems/jmespath-1.0.1/lib/jmespath/expr_node.rb +15 -0
  50. data/vendor/gems/jmespath-1.0.1/lib/jmespath/lexer.rb +116 -0
  51. data/vendor/gems/jmespath-1.0.1/lib/jmespath/parser.rb +347 -0
  52. data/vendor/gems/jmespath-1.0.1/lib/jmespath/runtime.rb +71 -0
  53. data/vendor/gems/jmespath-1.0.1/lib/jmespath/token.rb +41 -0
  54. data/vendor/gems/jmespath-1.0.1/lib/jmespath/token_stream.rb +60 -0
  55. data/vendor/gems/jmespath-1.0.1/lib/jmespath/tree_interpreter.rb +523 -0
  56. data/vendor/gems/jmespath-1.0.1/lib/jmespath/version.rb +3 -0
  57. data/vendor/gems/process_manager/lib/process_manager/master.rb +16 -5
  58. data/vendor/specifications/{aws-sdk-core-2.0.5.gemspec → aws-sdk-core-2.0.42.gemspec} +9 -11
  59. data/vendor/specifications/builder-3.2.2.gemspec +1 -1
  60. data/vendor/specifications/codedeploy-commands-1.0.0.gemspec +7 -6
  61. data/vendor/specifications/gli-2.5.6.gemspec +1 -1
  62. data/vendor/specifications/jmespath-1.0.1.gemspec +29 -0
  63. data/vendor/specifications/little-plugger-1.1.3.gemspec +1 -1
  64. data/vendor/specifications/logging-1.8.1.gemspec +1 -1
  65. data/vendor/specifications/multi_json-1.7.7.gemspec +1 -1
  66. data/vendor/specifications/multi_json-1.8.4.gemspec +1 -1
  67. data/vendor/specifications/multi_xml-0.5.5.gemspec +1 -1
  68. data/vendor/specifications/process_manager-0.0.13.gemspec +1 -1
  69. data/vendor/specifications/simple_pid-0.2.1.gemspec +1 -1
  70. metadata +76 -63
  71. data/lib/instance_agent/codedeploy_plugin/application_specification/ace_info.rb +0 -133
  72. data/lib/instance_agent/codedeploy_plugin/application_specification/acl_info.rb +0 -163
  73. data/lib/instance_agent/codedeploy_plugin/application_specification/application_specification.rb +0 -142
  74. data/lib/instance_agent/codedeploy_plugin/application_specification/context_info.rb +0 -23
  75. data/lib/instance_agent/codedeploy_plugin/application_specification/file_info.rb +0 -23
  76. data/lib/instance_agent/codedeploy_plugin/application_specification/linux_permission_info.rb +0 -121
  77. data/lib/instance_agent/codedeploy_plugin/application_specification/mode_info.rb +0 -66
  78. data/lib/instance_agent/codedeploy_plugin/application_specification/range_info.rb +0 -134
  79. data/lib/instance_agent/codedeploy_plugin/application_specification/script_info.rb +0 -27
  80. data/lib/instance_agent/codedeploy_plugin/codedeploy_control.rb +0 -72
  81. data/lib/instance_agent/codedeploy_plugin/command_executor.rb +0 -357
  82. data/lib/instance_agent/codedeploy_plugin/command_poller.rb +0 -170
  83. data/lib/instance_agent/codedeploy_plugin/deployment_specification.rb +0 -150
  84. data/lib/instance_agent/codedeploy_plugin/hook_executor.rb +0 -206
  85. data/lib/instance_agent/codedeploy_plugin/install_instruction.rb +0 -374
  86. data/lib/instance_agent/codedeploy_plugin/installer.rb +0 -143
  87. data/lib/instance_agent/codedeploy_plugin/request_helper.rb +0 -28
  88. data/test/instance_agent/codedeploy_plugin/application_specification_test.rb +0 -1710
  89. data/test/instance_agent/codedeploy_plugin/install_instruction_test.rb +0 -566
  90. data/test/instance_agent/codedeploy_plugin/request_helper_test.rb +0 -37
  91. data/vendor/specifications/jamespath-0.5.1.gemspec +0 -35
@@ -0,0 +1,71 @@
1
+ module JMESPath
2
+ # @api private
3
+ class Runtime
4
+
5
+ # @api private
6
+ DEFAULT_PARSER = CachingParser.new
7
+
8
+ # Constructs a new runtime object for evaluating JMESPath expressions.
9
+ #
10
+ # runtime = JMESPath::Runtime.new
11
+ # runtime.search(expression, data)
12
+ # #=> ...
13
+ #
14
+ # ## Caching
15
+ #
16
+ # When constructing a {Runtime}, the default parser caches expressions.
17
+ # This significantly speeds up calls to {#search} multiple times
18
+ # with the same expression but different data. To disable caching, pass
19
+ # `:cache_expressions => false` to the constructor or pass a custom
20
+ # `:parser`.
21
+ #
22
+ # @example Re-use a Runtime, caching enabled by default
23
+ #
24
+ # runtime = JMESPath::Runtime.new
25
+ # runtime.parser
26
+ # #=> #<JMESPath::CachingParser ...>
27
+ #
28
+ # @example Disable caching
29
+ #
30
+ # runtime = JMESPath::Runtime.new(cache_expressions: false)
31
+ # runtime.parser
32
+ # #=> #<JMESPath::Parser ...>
33
+ #
34
+ # @option options [Boolean] :cache_expressions (true) When `false`, a non
35
+ # caching parser will be used. When `true`, a shared instance of
36
+ # {CachingParser} is used. Defaults to `true`.
37
+ #
38
+ # @option options [Parser,CachingParser] :parser
39
+ #
40
+ # @option options [Interpreter] :interpreter
41
+ #
42
+ def initialize(options = {})
43
+ @parser = options[:parser] || default_parser(options)
44
+ @interpreter = options[:interpreter] || TreeInterpreter.new
45
+ end
46
+
47
+ # @return [Parser, CachingParser]
48
+ attr_reader :parser
49
+
50
+ # @return [Interpreter]
51
+ attr_reader :interpreter
52
+
53
+ # @param [String<JMESPath>] expression
54
+ # @param [Hash] data
55
+ # @return [Mixed,nil]
56
+ def search(expression, data)
57
+ @interpreter.visit(@parser.parse(expression), data)
58
+ end
59
+
60
+ private
61
+
62
+ def default_parser(options)
63
+ if options[:cache_expressions] == false
64
+ Parser.new(options)
65
+ else
66
+ DEFAULT_PARSER
67
+ end
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,41 @@
1
+ module JMESPath
2
+ # @api private
3
+ class Token < Struct.new(:type, :value, :position, :binding_power)
4
+
5
+ # @api private
6
+ NULL_TOKEN = Token.new(:eof, '', nil)
7
+
8
+ # binding power
9
+ # @api private
10
+ BINDING_POWER = {
11
+ :eof => 0,
12
+ :quoted_identifier => 0,
13
+ :identifier => 0,
14
+ :rbracket => 0,
15
+ :rparen => 0,
16
+ :comma => 0,
17
+ :rbrace => 0,
18
+ :number => 0,
19
+ :current => 0,
20
+ :expref => 0,
21
+ :pipe => 1,
22
+ :comparator => 2,
23
+ :or => 5,
24
+ :flatten => 6,
25
+ :star => 20,
26
+ :dot => 40,
27
+ :lbrace => 50,
28
+ :filter => 50,
29
+ :lbracket => 50,
30
+ :lparen => 60,
31
+ }
32
+
33
+ # @param [Symbol] type
34
+ # @param [Mixed] value
35
+ # @param [Integer] position
36
+ def initialize(type, value, position)
37
+ super(type, value, position, BINDING_POWER[type])
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,60 @@
1
+ module JMESPath
2
+ # @api private
3
+ class TokenStream
4
+
5
+ # @param [String<JMESPath>] expression
6
+ # @param [Array<Token>] tokens
7
+ def initialize(expression, tokens)
8
+ @expression = expression
9
+ @tokens = tokens
10
+ @token = nil
11
+ @position = -1
12
+ self.next
13
+ end
14
+
15
+ # @return [String<JMESPath>]
16
+ attr_reader :expression
17
+
18
+ # @return [Token]
19
+ attr_reader :token
20
+
21
+ # @return [Integer]
22
+ attr_reader :position
23
+
24
+ # @option options [Array<Symbol>] :match Requires the next token to be
25
+ # one of the given symbols or an error is raised.
26
+ def next(options = {})
27
+ validate_match(_next, options[:match])
28
+ end
29
+
30
+ def lookahead(count)
31
+ @tokens[@position + count] || Token::NULL_TOKEN
32
+ end
33
+
34
+ # @api private
35
+ def inspect
36
+ str = []
37
+ @tokens.each do |token|
38
+ str << "%3d %-15s %s" %
39
+ [token.position, token.type, token.value.inspect]
40
+ end
41
+ str.join("\n")
42
+ end
43
+
44
+ private
45
+
46
+ def _next
47
+ @position += 1
48
+ @token = @tokens[@position] || Token::NULL_TOKEN
49
+ end
50
+
51
+ def validate_match(token, match)
52
+ if match && !match.include?(token.type)
53
+ raise Errors::SyntaxError, "type missmatch"
54
+ else
55
+ token
56
+ end
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,523 @@
1
+ module JMESPath
2
+ # @api private
3
+ class TreeInterpreter
4
+
5
+ def visit(node, data)
6
+ dispatch(node, data)
7
+ end
8
+
9
+ # @api private
10
+ def method_missing(method_name, *args)
11
+ if matches = method_name.to_s.match(/^function_(.*)/)
12
+ raise Errors::UnknownFunctionError, "unknown function #{matches[1]}()"
13
+ else
14
+ super
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def dispatch(node, value)
21
+ case node[:type]
22
+
23
+ when :field
24
+ # hash_like?
25
+ key = node[:key]
26
+ case value
27
+ when Hash then value.key?(key) ? value[key] : value[key.to_sym]
28
+ when Struct then value.respond_to?(key) ? value[key] : nil
29
+ else nil
30
+ end
31
+
32
+ when :subexpression
33
+ dispatch(node[:children][1], dispatch(node[:children][0], value))
34
+
35
+ when :index
36
+ if Array === value
37
+ value[node[:index]]
38
+ else
39
+ nil
40
+ end
41
+
42
+ when :projection
43
+ # Interprets a projection node, passing the values of the left
44
+ # child through the values of the right child and aggregating
45
+ # the non-null results into the return value.
46
+ left = dispatch(node[:children][0], value)
47
+ if node[:from] == :object && hash_like?(left)
48
+ projection(left.values, node)
49
+ elsif node[:from] == :object && left == []
50
+ projection(left, node)
51
+ elsif node[:from] == :array && Array === left
52
+ projection(left, node)
53
+ else
54
+ nil
55
+ end
56
+
57
+ when :flatten
58
+ value = dispatch(node[:children][0], value)
59
+ if Array === value
60
+ value.inject([]) do |values, v|
61
+ values + (Array === v ? v : [v])
62
+ end
63
+ else
64
+ nil
65
+ end
66
+
67
+ when :literal
68
+ node[:value]
69
+
70
+ when :current
71
+ value
72
+
73
+ when :or
74
+ result = dispatch(node[:children][0], value)
75
+ if result.nil? or result.empty?
76
+ dispatch(node[:children][1], value)
77
+ else
78
+ result
79
+ end
80
+
81
+ when :pipe
82
+ dispatch(node[:children][1], dispatch(node[:children][0], value))
83
+
84
+ when :multi_select_list
85
+ if value.nil?
86
+ value
87
+ else
88
+ node[:children].map { |n| dispatch(n, value) }
89
+ end
90
+
91
+ when :multi_select_hash
92
+ if value.nil?
93
+ nil
94
+ else
95
+ node[:children].each.with_object({}) do |child, hash|
96
+ hash[child[:key]] = dispatch(child[:children][0], value)
97
+ end
98
+ end
99
+
100
+
101
+ when :comparator
102
+ left = dispatch(node[:children][0], value)
103
+ right = dispatch(node[:children][1], value)
104
+ case node[:relation]
105
+ when '==' then compare_values(left, right)
106
+ when '!=' then !compare_values(left, right)
107
+ when '>' then is_int(left) && is_int(right) && left > right
108
+ when '>=' then is_int(left) && is_int(right) && left >= right
109
+ when '<' then is_int(left) && is_int(right) && left < right
110
+ when '<=' then is_int(left) && is_int(right) && left <= right
111
+ end
112
+
113
+ when :condition
114
+ true == dispatch(node[:children][0], value) ?
115
+ dispatch(node[:children][1], value) :
116
+ nil
117
+
118
+ when :function
119
+ args = node[:children].map { |child| dispatch(child, value) }
120
+ send("function_#{node[:fn]}", *args)
121
+
122
+ when :slice
123
+ function_slice(value, *node[:args])
124
+
125
+ when :expression
126
+ ExprNode.new(self, node[:children][0])
127
+
128
+ else
129
+ raise NotImplementedError
130
+ end
131
+ end
132
+
133
+ def hash_like?(value)
134
+ Hash === value || Struct === value
135
+ end
136
+
137
+ def projection(values, node)
138
+ values.inject([]) do |list, v|
139
+ list << dispatch(node[:children][1], v)
140
+ end.compact
141
+ end
142
+
143
+ def function_abs(*args)
144
+ if args.count == 1
145
+ value = args.first
146
+ else
147
+ raise Errors::InvalidArityError, "function abs() expects one argument"
148
+ end
149
+ if Numeric === value
150
+ value.abs
151
+ else
152
+ raise Errors::InvalidTypeError, "function abs() expects a number"
153
+ end
154
+ end
155
+
156
+ def function_avg(*args)
157
+ if args.count == 1
158
+ values = args.first
159
+ else
160
+ raise Errors::InvalidArityError, "function avg() expects one argument"
161
+ end
162
+ if Array === values
163
+ values.inject(0) do |total,n|
164
+ if Numeric === n
165
+ total + n
166
+ else
167
+ raise Errors::InvalidTypeError, "function avg() expects numeric values"
168
+ end
169
+ end / values.size.to_f
170
+ else
171
+ raise Errors::InvalidTypeError, "function avg() expects a number"
172
+ end
173
+ end
174
+
175
+ def function_ceil(*args)
176
+ if args.count == 1
177
+ value = args.first
178
+ else
179
+ raise Errors::InvalidArityError, "function ceil() expects one argument"
180
+ end
181
+ if Numeric === value
182
+ value.ceil
183
+ else
184
+ raise Errors::InvalidTypeError, "function ceil() expects a numeric value"
185
+ end
186
+ end
187
+
188
+ def function_contains(*args)
189
+ if args.count == 2
190
+ if String === args[0] || Array === args[0]
191
+ args[0].include?(args[1])
192
+ else
193
+ raise Errors::InvalidTypeError, "contains expects 2nd arg to be a list"
194
+ end
195
+ else
196
+ raise Errors::InvalidArityError, "function contains() expects 2 arguments"
197
+ end
198
+ end
199
+
200
+ def function_floor(*args)
201
+ if args.count == 1
202
+ value = args.first
203
+ else
204
+ raise Errors::InvalidArityError, "function floor() expects one argument"
205
+ end
206
+ if Numeric === value
207
+ value.floor
208
+ else
209
+ raise Errors::InvalidTypeError, "function floor() expects a numeric value"
210
+ end
211
+ end
212
+
213
+ def function_length(*args)
214
+ if args.count == 1
215
+ value = args.first
216
+ else
217
+ raise Errors::InvalidArityError, "function length() expects one argument"
218
+ end
219
+ case value
220
+ when Hash, Array, String then value.size
221
+ else raise Errors::InvalidTypeError, "function length() expects string, array or object"
222
+ end
223
+ end
224
+
225
+ def function_max(*args)
226
+ if args.count == 1
227
+ values = args.first
228
+ else
229
+ raise Errors::InvalidArityError, "function max() expects one argument"
230
+ end
231
+ if Array === values
232
+ values.inject(values.first) do |max, v|
233
+ if Numeric === v
234
+ v > max ? v : max
235
+ else
236
+ raise Errors::InvalidTypeError, "function max() expects numeric values"
237
+ end
238
+ end
239
+ else
240
+ raise Errors::InvalidTypeError, "function max() expects an array"
241
+ end
242
+ end
243
+
244
+ def function_min(*args)
245
+ if args.count == 1
246
+ values = args.first
247
+ else
248
+ raise Errors::InvalidArityError, "function min() expects one argument"
249
+ end
250
+ if Array === values
251
+ values.inject(values.first) do |min, v|
252
+ if Numeric === v
253
+ v < min ? v : min
254
+ else
255
+ raise Errors::InvalidTypeError, "function min() expects numeric values"
256
+ end
257
+ end
258
+ else
259
+ raise Errors::InvalidTypeError, "function min() expects an array"
260
+ end
261
+ end
262
+
263
+ def function_type(*args)
264
+ if args.count == 1
265
+ get_type(args.first)
266
+ else
267
+ raise Errors::InvalidArityError, "function type() expects one argument"
268
+ end
269
+ end
270
+
271
+ def function_keys(*args)
272
+ if args.count == 1
273
+ value = args.first
274
+ if hash_like?(value)
275
+ case value
276
+ when Hash then value.keys.map(&:to_s)
277
+ when Struct then value.members.map(&:to_s)
278
+ else raise NotImplementedError
279
+ end
280
+ else
281
+ raise Errors::InvalidTypeError, "function keys() expects a hash"
282
+ end
283
+ else
284
+ raise Errors::InvalidArityError, "function keys() expects one argument"
285
+ end
286
+ end
287
+
288
+ def function_values(*args)
289
+ if args.count == 1
290
+ value = args.first
291
+ if hash_like?(value)
292
+ value.values
293
+ elsif Array === value
294
+ value
295
+ else
296
+ raise Errors::InvalidTypeError, "function values() expects an array or a hash"
297
+ end
298
+ else
299
+ raise Errors::InvalidArityError, "function values() expects one argument"
300
+ end
301
+ end
302
+
303
+ def function_join(*args)
304
+ if args.count == 2
305
+ glue = args[0]
306
+ values = args[1]
307
+ if !(String === glue)
308
+ raise Errors::InvalidTypeError, "function join() expects the first argument to be a string"
309
+ elsif Array === values && values.all? { |v| String === v }
310
+ values.join(glue)
311
+ else
312
+ raise Errors::InvalidTypeError, "function join() expects values to be an array of strings"
313
+ end
314
+ else
315
+ raise Errors::InvalidArityError, "function join() expects an array of strings"
316
+ end
317
+ end
318
+
319
+ def function_to_string(*args)
320
+ if args.count == 1
321
+ value = args.first
322
+ String === value ? value : MultiJson.dump(value)
323
+ else
324
+ raise Errors::InvalidArityError, "function to_string() expects one argument"
325
+ end
326
+ end
327
+
328
+ def function_to_number(*args)
329
+ if args.count == 1
330
+ begin
331
+ value = Float(args.first)
332
+ Integer(value) === value ? value.to_i : value
333
+ rescue
334
+ nil
335
+ end
336
+ else
337
+ raise Errors::InvalidArityError, "function to_number() expects one argument"
338
+ end
339
+ end
340
+
341
+ def function_sum(*args)
342
+ if args.count == 1 && Array === args.first
343
+ args.first.inject(0) do |sum,n|
344
+ if Numeric === n
345
+ sum + n
346
+ else
347
+ raise Errors::InvalidTypeError, "function sum() expects values to be numeric"
348
+ end
349
+ end
350
+ else
351
+ raise Errors::InvalidArityError, "function sum() expects one argument"
352
+ end
353
+ end
354
+
355
+ def function_not_null(*args)
356
+ if args.count > 0
357
+ args.find { |value| !value.nil? }
358
+ else
359
+ raise Errors::InvalidArityError, "function not_null() expects one or more arguments"
360
+ end
361
+ end
362
+
363
+ def function_sort(*args)
364
+ if args.count == 1
365
+ value = args.first
366
+ if Array === value
367
+ value.sort do |a, b|
368
+ a_type = get_type(a)
369
+ b_type = get_type(b)
370
+ if ['string', 'number'].include?(a_type) && a_type == b_type
371
+ a <=> b
372
+ else
373
+ raise Errors::InvalidTypeError, "function sort() expects values to be an array of numbers or integers"
374
+ end
375
+ end
376
+ else
377
+ raise Errors::InvalidTypeError, "function sort() expects values to be an array of numbers or integers"
378
+ end
379
+ else
380
+ raise Errors::InvalidArityError, "function sort() expects one argument"
381
+ end
382
+ end
383
+
384
+ def function_sort_by(*args)
385
+ if args.count == 2
386
+ if get_type(args[0]) == 'array' && get_type(args[1]) == 'expression'
387
+ values = args[0]
388
+ expression = args[1]
389
+ values.sort do |a,b|
390
+ a_value = expression.interpreter.visit(expression.node, a)
391
+ b_value = expression.interpreter.visit(expression.node, b)
392
+ a_type = get_type(a_value)
393
+ b_type = get_type(b_value)
394
+ if ['string', 'number'].include?(a_type) && a_type == b_type
395
+ a_value <=> b_value
396
+ else
397
+ raise Errors::InvalidTypeError, "function sort() expects values to be an array of numbers or integers"
398
+ end
399
+ end
400
+ else
401
+ raise Errors::InvalidTypeError, "function sort_by() expects an array and an expression"
402
+ end
403
+ else
404
+ raise Errors::InvalidArityError, "function sort_by() expects two arguments"
405
+ end
406
+ end
407
+
408
+ def function_max_by(*args)
409
+ number_compare(:max, *args)
410
+ end
411
+
412
+ def function_min_by(*args)
413
+ number_compare(:min, *args)
414
+ end
415
+
416
+ def number_compare(mode, *args)
417
+ if args.count == 2
418
+ if get_type(args[0]) == 'array' && get_type(args[1]) == 'expression'
419
+ values = args[0]
420
+ expression = args[1]
421
+ args[0].send("#{mode}_by") do |entry|
422
+ value = expression.interpreter.visit(expression.node, entry)
423
+ if get_type(value) == 'number'
424
+ value
425
+ else
426
+ raise Errors::InvalidTypeError, "function #{mode}_by() expects values to be an numbers"
427
+ end
428
+ end
429
+ else
430
+ raise Errors::InvalidTypeError, "function #{mode}_by() expects an array and an expression"
431
+ end
432
+ else
433
+ raise Errors::InvalidArityError, "function #{mode}_by() expects two arguments"
434
+ end
435
+ end
436
+
437
+ def function_slice(values, *args)
438
+ if String === values || Array === values
439
+ _slice(values, *args)
440
+ else
441
+ nil
442
+ end
443
+ end
444
+
445
+ def _slice(values, start, stop, step)
446
+ start, stop, step = _adjust_slice(values.size, start, stop, step)
447
+ result = []
448
+ if step > 0
449
+ i = start
450
+ while i < stop
451
+ result << values[i]
452
+ i += step
453
+ end
454
+ else
455
+ i = start
456
+ while i > stop
457
+ result << values[i]
458
+ i += step
459
+ end
460
+ end
461
+ String === values ? result.join : result
462
+ end
463
+
464
+ def _adjust_slice(length, start, stop, step)
465
+ if step.nil?
466
+ step = 1
467
+ elsif step == 0
468
+ raise Errors::RuntimeError, 'slice step cannot be 0'
469
+ end
470
+
471
+ if start.nil?
472
+ start = step < 0 ? length - 1 : 0
473
+ else
474
+ start = _adjust_endpoint(length, start, step)
475
+ end
476
+
477
+ if stop.nil?
478
+ stop = step < 0 ? -1 : length
479
+ else
480
+ stop = _adjust_endpoint(length, stop, step)
481
+ end
482
+
483
+ [start, stop, step]
484
+ end
485
+
486
+ def _adjust_endpoint(length, endpoint, step)
487
+ if endpoint < 0
488
+ endpoint += length
489
+ endpoint = 0 if endpoint < 0
490
+ endpoint
491
+ elsif endpoint >= length
492
+ step < 0 ? length - 1 : length
493
+ else
494
+ endpoint
495
+ end
496
+ end
497
+
498
+ def compare_values(a, b)
499
+ if a == b
500
+ true
501
+ else
502
+ false
503
+ end
504
+ end
505
+
506
+ def is_int(value)
507
+ Integer === value
508
+ end
509
+
510
+ def get_type(value)
511
+ case
512
+ when ExprNode === value then 'expression'
513
+ when String === value then 'string'
514
+ when hash_like?(value) then 'object'
515
+ when Array === value then 'array'
516
+ when [true, false].include?(value) then 'boolean'
517
+ when value.nil? then 'null'
518
+ when Numeric === value then 'number'
519
+ end
520
+ end
521
+
522
+ end
523
+ end