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
@@ -1,9 +1,8 @@
1
1
  require 'test_helper'
2
- require 'instance_agent/codedeploy_plugin/installer'
3
2
 
4
3
  class CodeDeployPluginInstallerTest < InstanceAgentTestCase
5
4
 
6
- include InstanceAgent::CodeDeployPlugin
5
+ include InstanceAgent::Plugins::CodeDeployPlugin
7
6
 
8
7
  context "The CodeDeploy Plugin's file installer" do
9
8
 
@@ -49,8 +48,9 @@ class CodeDeployPluginInstallerTest < InstanceAgentTestCase
49
48
  InstallInstruction
50
49
  .stubs(:generate_instructions)
51
50
  .yields(@instruction_builder)
52
- .returns([])
51
+ .returns(@instruction_builder)
53
52
 
53
+ @instruction_builder.stubs(:cleanup).returns(nil)
54
54
  File.stubs(:exists?).returns(false)
55
55
  File.stubs(:exists?).with("deploy-instructions-dir/ig1-cleanup").returns(false)
56
56
 
@@ -88,7 +88,7 @@ class CodeDeployPluginInstallerTest < InstanceAgentTestCase
88
88
 
89
89
  InstallInstruction
90
90
  .expects(:generate_instructions)
91
- .returns([])
91
+ .returns(CommandBuilder.new)
92
92
  .in_sequence(call_sequence)
93
93
 
94
94
  @installer.install(@deployment_group_id, @app_spec)
@@ -366,13 +366,13 @@ class CodeDeployPluginInstallerTest < InstanceAgentTestCase
366
366
 
367
367
  context "with permissions" do
368
368
  setup do
369
- @permissions = [InstanceAgent::CodeDeployPlugin::ApplicationSpecification::LinuxPermissionInfo.new("dst1/src1",
369
+ @permissions = [InstanceAgent::Plugins::CodeDeployPlugin::ApplicationSpecification::LinuxPermissionInfo.new("dst1/src1",
370
370
  {:type => ["file"]}),
371
- InstanceAgent::CodeDeployPlugin::ApplicationSpecification::LinuxPermissionInfo.new("/",
371
+ InstanceAgent::Plugins::CodeDeployPlugin::ApplicationSpecification::LinuxPermissionInfo.new("/",
372
372
  {:type => ["directory"],
373
373
  :pattern => "dst*",
374
374
  :except => []}),
375
- InstanceAgent::CodeDeployPlugin::ApplicationSpecification::LinuxPermissionInfo.new("dst3/",
375
+ InstanceAgent::Plugins::CodeDeployPlugin::ApplicationSpecification::LinuxPermissionInfo.new("dst3/",
376
376
  {:type => ["file","directory"],
377
377
  :pattern => "file*",
378
378
  :except => [*2]})]
@@ -472,8 +472,11 @@ class CodeDeployPluginInstallerTest < InstanceAgentTestCase
472
472
  setup do
473
473
  @command_list = [stub("a command", :execute => nil),
474
474
  stub("a second command", :execute => nil)]
475
- InstallInstruction.stubs(:generate_instructions).returns(@command_list)
476
- @command_list.stubs(:to_json).returns("INSTALL!")
475
+ commands = mock("command builder")
476
+ commands.stubs(:command_array).returns(@command_list)
477
+ commands.stubs(:cleanup)
478
+ InstallInstruction.stubs(:generate_instructions).returns(commands)
479
+ commands.stubs(:to_json).returns("INSTALL!")
477
480
  end
478
481
 
479
482
  should "write them to disk" do
@@ -0,0 +1,72 @@
1
+ class OnPremiseConfigTest < InstanceAgentTestCase
2
+
3
+ include InstanceAgent::Plugins::CodeDeployPlugin
4
+
5
+ linux_path = '/etc/codedeploy-agent/conf/codedeploy.onpremises.yml'
6
+
7
+ context "Config file doesn't exist" do
8
+ setup do
9
+ File.stubs(:exists?).with(linux_path).returns(false)
10
+ end
11
+
12
+ should "do nothing" do
13
+ OnPremisesConfig.configure
14
+ end
15
+ end
16
+
17
+ context "Linux config file exists" do
18
+ setup do
19
+ File.stubs(:exists?).with(linux_path).returns(true)
20
+ end
21
+
22
+ context "Linux file is not readable" do
23
+ setup do
24
+ File.stubs(:readable?).with(linux_path).returns(false)
25
+ end
26
+
27
+ should "do nothing" do
28
+ OnPremisesConfig.configure
29
+ end
30
+ end
31
+
32
+ context "Linux file is valid" do
33
+
34
+ linux_file = <<-END
35
+ region: us-east-test
36
+ aws_access_key_id: linuxkey
37
+ aws_secret_access_key: linuxsecretkey
38
+ iam_user_arn: test:arn
39
+ END
40
+
41
+ setup do
42
+ File.stubs(:readable?).with(linux_path).returns(true)
43
+ File.stubs(:read).with(linux_path).returns(linux_file)
44
+ end
45
+
46
+ should "set the ENV variables correctly" do
47
+ OnPremisesConfig.configure
48
+ assert_equal 'us-east-test', ENV['AWS_REGION']
49
+ assert_equal 'linuxkey', ENV['AWS_ACCESS_KEY']
50
+ assert_equal 'linuxsecretkey', ENV['AWS_SECRET_KEY']
51
+ assert_equal 'test:arn', ENV['AWS_HOST_IDENTIFIER']
52
+ end
53
+ end
54
+
55
+ context "config file with invalid yaml" do
56
+ linux_file = <<-END
57
+ invalid yaml content
58
+ END
59
+
60
+ setup do
61
+ File.stubs(:readable?).with(linux_path).returns(true)
62
+ File.stubs(:read).with(linux_path).returns(linux_file)
63
+ end
64
+
65
+ should "raise an exception" do
66
+ assert_raise do
67
+ OnPremisesConfig.configure
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -5,7 +5,7 @@ class RunnerChildTest < InstanceAgentTestCase
5
5
  setup do
6
6
  @dir = '/tmp'
7
7
  @agent = mock()
8
- InstanceAgent::CodeDeployPlugin::CommandPoller.stubs(:new).returns(@agent)
8
+ InstanceAgent::Plugins::CodeDeployPlugin::CommandPoller.stubs(:new).returns(@agent)
9
9
  @agent.stubs(:description).returns("CommandPoller")
10
10
  InstanceAgent::Runner::Child.any_instance.stubs(:trap_signals)
11
11
  @child = InstanceAgent::Runner::Child.new(0, 777)
@@ -1 +1 @@
1
- Tue, 04 Nov 2014 07:48:38 +0000
1
+ Tue, 30 Jun 2015 13:02:54 -0700
@@ -11,6 +11,10 @@ module Aws
11
11
  url = "https://codedeploy-commands.us-east-1.amazonaws.com"
12
12
  when "us-west-2"
13
13
  url = "https://codedeploy-commands.us-west-2.amazonaws.com"
14
+ when "eu-west-1"
15
+ url = "https://codedeploy-commands.eu-west-1.amazonaws.com"
16
+ when "ap-southeast-2"
17
+ url = "https://codedeploy-commands.ap-southeast-2.amazonaws.com"
14
18
  else
15
19
  raise "Not able to find an endpoint. Unknown region."
16
20
  end
@@ -0,0 +1,41 @@
1
+ require 'multi_json'
2
+ require 'pathname'
3
+
4
+ module JMESPath
5
+
6
+ autoload :CachingParser, 'jmespath/caching_parser'
7
+ autoload :Errors, 'jmespath/errors'
8
+ autoload :ExprNode, 'jmespath/expr_node'
9
+ autoload :Lexer, 'jmespath/lexer'
10
+ autoload :Parser, 'jmespath/parser'
11
+ autoload :Runtime, 'jmespath/runtime'
12
+ autoload :Token, 'jmespath/token'
13
+ autoload :TokenStream, 'jmespath/token_stream'
14
+ autoload :TreeInterpreter, 'jmespath/tree_interpreter'
15
+ autoload :VERSION, 'jmespath/version'
16
+
17
+ class << self
18
+
19
+
20
+ # @param [String] expression A valid
21
+ # [JMESPath](https://github.com/boto/jmespath) expression.
22
+ # @param [Hash] data
23
+ # @return [Mixed,nil] Returns the matched values. Returns `nil` if the
24
+ # expression does not resolve inside `data`.
25
+ def search(expression, data)
26
+ data = case data
27
+ when Hash, Struct then data # check for most common case first
28
+ when Pathname then load_json(data)
29
+ when IO, StringIO then MultiJson.load(data.read)
30
+ else data
31
+ end
32
+ Runtime.new.search(expression, data)
33
+ end
34
+
35
+ # @api private
36
+ def load_json(path)
37
+ MultiJson.load(File.open(path, 'r', encoding: 'UTF-8') { |f| f.read })
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,30 @@
1
+ require 'thread'
2
+
3
+ module JMESPath
4
+ class CachingParser
5
+
6
+ def initialize(options = {})
7
+ @parser = options[:parser] || Parser.new(options)
8
+ @mutex = Mutex.new
9
+ @cache = {}
10
+ end
11
+
12
+ def parse(expression)
13
+ if cached = @cache[expression]
14
+ cached
15
+ else
16
+ cache_expression(expression)
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def cache_expression(expression)
23
+ @mutex.synchronize do
24
+ @cache.clear if @cache.size > 1000
25
+ @cache[expression] = @parser.parse(expression)
26
+ end
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,17 @@
1
+ module JMESPath
2
+ module Errors
3
+
4
+ class Error < StandardError; end
5
+
6
+ class RuntimeError < Error; end
7
+
8
+ class SyntaxError < Error; end
9
+
10
+ class InvalidTypeError < Error; end
11
+
12
+ class InvalidArityError < Error; end
13
+
14
+ class UnknownFunctionError < Error; end
15
+
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ module JMESPath
2
+ # @api private
3
+ class ExprNode
4
+
5
+ def initialize(interpreter, node)
6
+ @interpreter = interpreter
7
+ @node = node
8
+ end
9
+
10
+ attr_reader :interpreter
11
+
12
+ attr_reader :node
13
+
14
+ end
15
+ end
@@ -0,0 +1,116 @@
1
+ module JMESPath
2
+ # @api private
3
+ class Lexer
4
+
5
+ # @api private
6
+ TOKEN_PATTERNS = {}
7
+
8
+ # @api private
9
+ TOKEN_TYPES = {}
10
+
11
+ {
12
+ '[a-zA-Z_][a-zA-Z_0-9]*' => :identifier,
13
+ '\.' => :dot,
14
+ '\*' => :star,
15
+ '\[\]' => :flatten,
16
+ '-?\d+' => :number,
17
+ '\|\|' => :or,
18
+ '\|' => :pipe,
19
+ '\[\?' => :filter,
20
+ '\[' => :lbracket,
21
+ '\]' => :rbracket,
22
+ '"(?:\\\\\\\\|\\\\"|[^"])*"' => :quoted_identifier,
23
+ '`(?:\\\\\\\\|\\\\`|[^`])*`' => :literal,
24
+ ',' => :comma,
25
+ ':' => :colon,
26
+ '@' => :current,
27
+ '&' => :expref,
28
+ '\(' => :lparen,
29
+ '\)' => :rparen,
30
+ '\{' => :lbrace,
31
+ '\}' => :rbrace,
32
+ '!=' => :comparator,
33
+ '==' => :comparator,
34
+ '<=' => :comparator,
35
+ '>=' => :comparator,
36
+ '<' => :comparator,
37
+ '>' => :comparator,
38
+ '[ \t]' => :skip,
39
+ }.each.with_index do |(pattern, type), n|
40
+ TOKEN_PATTERNS[n] = pattern
41
+ TOKEN_TYPES[n] = type
42
+ end
43
+
44
+ # @api private
45
+ TOKEN_REGEX = /(#{TOKEN_PATTERNS.values.join(')|(')})/
46
+
47
+ # @api private
48
+ JSON_VALUE = /^[\["{]/
49
+
50
+ # @api private
51
+ JSON_NUMBER = /^\-?[0-9]*(\.[0-9]+)?([e|E][+|\-][0-9]+)?$/
52
+
53
+ # @param [String<JMESPath>] expression
54
+ # @return [Array<Hash>]
55
+ def tokenize(expression)
56
+ offset = 0
57
+ tokens = []
58
+ expression.scan(TOKEN_REGEX).each do |match|
59
+ match_index = match.find_index { |token| !token.nil? }
60
+ match_value = match[match_index]
61
+ type = TOKEN_TYPES[match_index]
62
+ token = Token.new(type, match_value, offset)
63
+ if token.type != :skip
64
+ case token.type
65
+ when :number then token_number(token, expression, offset)
66
+ when :literal then token_literal(token, expression, offset)
67
+ when :quoted_identifier
68
+ token_quoted_identifier(token, expression, offset)
69
+ end
70
+ tokens << token
71
+ end
72
+ offset += match_value.size
73
+ end
74
+ tokens << Token.new(:eof, nil, offset)
75
+ unless expression.size == offset
76
+ syntax_error('invalid expression', expression, offset)
77
+ end
78
+ tokens
79
+ end
80
+
81
+ private
82
+
83
+ def token_number(token, expression, offset)
84
+ token[:value] = token[:value].to_i
85
+ end
86
+
87
+ def token_literal(token, expression, offset)
88
+ token[:value] = token[:value][1..-2].lstrip.gsub('\`', '`')
89
+ token[:value] =
90
+ case token[:value]
91
+ when 'true', 'false' then token[:value] == 'true'
92
+ when 'null' then nil
93
+ when '' then syntax_error("empty json literal", expression, offset)
94
+ when JSON_VALUE then decode_json(token[:value], expression, offset)
95
+ when JSON_NUMBER then decode_json(token[:value], expression, offset)
96
+ else decode_json('"' + token[:value] + '"', expression, offset)
97
+ end
98
+ end
99
+
100
+ def token_quoted_identifier(token, expression, offset)
101
+ token[:value] = decode_json(token[:value], expression, offset)
102
+ end
103
+
104
+ def decode_json(json, expression, offset)
105
+ MultiJson.load(json)
106
+ rescue MultiJson::ParseError => e
107
+ syntax_error(e.message, expression, offset)
108
+ end
109
+
110
+ def syntax_error(message, expression, offset)
111
+ msg = message + "in #{expression.inspect} at #{offset}"
112
+ raise Errors::SyntaxError.new(msg)
113
+ end
114
+
115
+ end
116
+ end
@@ -0,0 +1,347 @@
1
+ require 'set'
2
+
3
+ module JMESPath
4
+ # @api private
5
+ class Parser
6
+
7
+ # @api private
8
+ AFTER_DOT = Set.new([
9
+ :identifier, # foo.bar
10
+ :quoted_identifier, # foo."bar"
11
+ :star, # foo.*
12
+ :lbrace, # foo[1]
13
+ :lbracket, # foo{a: 0}
14
+ :function, # foo.*.to_string(@)
15
+ :filter, # foo.[?bar==10]
16
+ ])
17
+
18
+ CURRENT_NODE = { type: :current }
19
+
20
+ # @option options [Lexer] :lexer
21
+ def initialize(options = {})
22
+ @lexer = options[:lexer] || Lexer.new()
23
+ end
24
+
25
+ # @param [String<JMESPath>] expression
26
+ def parse(expression)
27
+ stream = TokenStream.new(expression, @lexer.tokenize(expression))
28
+ result = expr(stream)
29
+ if stream.token.type != :eof
30
+ raise Errors::SyntaxError, "expected :eof got #{stream.token.type}"
31
+ else
32
+ result
33
+ end
34
+ end
35
+
36
+ # @api private
37
+ def method_missing(method_name, *args)
38
+ if matches = method_name.match(/^(nud_|led_)(.*)/)
39
+ raise Errors::SyntaxError, "unexpected token #{matches[2]}"
40
+ else
41
+ super
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ # @param [TokenStream] stream
48
+ # @param [Integer] rbp Right binding power
49
+ def expr(stream, rbp = 0)
50
+ left = send("nud_#{stream.token.type}", stream)
51
+ while rbp < stream.token.binding_power
52
+ left = send("led_#{stream.token.type}", stream, left)
53
+ end
54
+ left
55
+ end
56
+
57
+ def nud_current(stream)
58
+ stream.next
59
+ CURRENT_NODE
60
+ end
61
+
62
+ def nud_expref(stream)
63
+ stream.next
64
+ {
65
+ type: :expression,
66
+ children: [expr(stream, 2)]
67
+ }
68
+ end
69
+
70
+ def nud_filter(stream)
71
+ led_filter(stream, CURRENT_NODE)
72
+ end
73
+
74
+ def nud_flatten(stream)
75
+ led_flatten(stream, CURRENT_NODE)
76
+ end
77
+
78
+ def nud_identifier(stream)
79
+ token = stream.token
80
+ stream.next
81
+ { type: :field, key: token.value }
82
+ end
83
+
84
+ def nud_lbrace(stream)
85
+ valid_keys = Set.new([:quoted_identifier, :identifier])
86
+ stream.next(match:valid_keys)
87
+ pairs = []
88
+ begin
89
+ pairs << parse_key_value_pair(stream)
90
+ if stream.token.type == :comma
91
+ stream.next(match:valid_keys)
92
+ end
93
+ end while stream.token.type != :rbrace
94
+ stream.next
95
+ {
96
+ type: :multi_select_hash,
97
+ children: pairs
98
+ }
99
+ end
100
+
101
+ def nud_lbracket(stream)
102
+ stream.next
103
+ type = stream.token.type
104
+ if type == :number || type == :colon
105
+ parse_array_index_expression(stream)
106
+ elsif type == :star && stream.lookahead(1).type == :rbracket
107
+ parse_wildcard_array(stream)
108
+ else
109
+ parse_multi_select_list(stream)
110
+ end
111
+ end
112
+
113
+ def nud_literal(stream)
114
+ value = stream.token.value
115
+ stream.next
116
+ {
117
+ type: :literal,
118
+ value: value
119
+ }
120
+ end
121
+
122
+ def nud_quoted_identifier(stream)
123
+ token = stream.token
124
+ next_token = stream.next
125
+ if next_token.type == :lparen
126
+ msg = 'quoted identifiers are not allowed for function names'
127
+ raise Errors::SyntaxError, msg
128
+ else
129
+ { type: :field, key: token[:value] }
130
+ end
131
+ end
132
+
133
+ def nud_star(stream)
134
+ parse_wildcard_object(stream, CURRENT_NODE)
135
+ end
136
+
137
+ def led_comparator(stream, left)
138
+ token = stream.token
139
+ stream.next
140
+ {
141
+ type: :comparator,
142
+ relation: token.value,
143
+ children: [
144
+ left,
145
+ expr(stream),
146
+ ]
147
+ }
148
+ end
149
+
150
+ def led_dot(stream, left)
151
+ stream.next(match:AFTER_DOT)
152
+ if stream.token.type == :star
153
+ parse_wildcard_object(stream, left)
154
+ else
155
+ {
156
+ type: :subexpression,
157
+ children: [
158
+ left,
159
+ parse_dot(stream, Token::BINDING_POWER[:dot])
160
+ ]
161
+ }
162
+ end
163
+ end
164
+
165
+ def led_filter(stream, left)
166
+ stream.next
167
+ expression = expr(stream)
168
+ if stream.token.type != :rbracket
169
+ raise Errors::SyntaxError, 'expected a closing rbracket for the filter'
170
+ end
171
+ stream.next
172
+ rhs = parse_projection(stream, Token::BINDING_POWER[:filter])
173
+ {
174
+ type: :projection,
175
+ from: :array,
176
+ children: [
177
+ left ? left : CURRENT_NODE,
178
+ {
179
+ type: :condition,
180
+ children: [expression, rhs],
181
+ }
182
+ ]
183
+ }
184
+ end
185
+
186
+ def led_flatten(stream, left)
187
+ stream.next
188
+ {
189
+ type: :projection,
190
+ from: :array,
191
+ children: [
192
+ { type: :flatten, children: [left] },
193
+ parse_projection(stream, Token::BINDING_POWER[:flatten])
194
+ ]
195
+ }
196
+ end
197
+
198
+ def led_lbracket(stream, left)
199
+ stream.next(match: Set.new([:number, :colon, :star]))
200
+ type = stream.token.type
201
+ if type == :number || type == :colon
202
+ {
203
+ type: :subexpression,
204
+ children: [
205
+ left,
206
+ parse_array_index_expression(stream)
207
+ ]
208
+ }
209
+ else
210
+ parse_wildcard_array(stream, left)
211
+ end
212
+ end
213
+
214
+ def led_lparen(stream, left)
215
+ args = []
216
+ name = left[:key]
217
+ stream.next
218
+ while stream.token.type != :rparen
219
+ args << expr(stream, 0)
220
+ if stream.token.type == :comma
221
+ stream.next
222
+ end
223
+ end
224
+ stream.next
225
+ {
226
+ type: :function,
227
+ fn: name,
228
+ children: args,
229
+ }
230
+ end
231
+
232
+ def led_or(stream, left)
233
+ stream.next
234
+ {
235
+ type: :or,
236
+ children: [left, expr(stream, Token::BINDING_POWER[:or])]
237
+ }
238
+ end
239
+
240
+ def led_pipe(stream, left)
241
+ stream.next
242
+ {
243
+ type: :pipe,
244
+ children: [left, expr(stream, Token::BINDING_POWER[:pipe])],
245
+ }
246
+ end
247
+
248
+ def parse_array_index_expression(stream)
249
+ pos = 0
250
+ parts = [nil, nil, nil]
251
+ begin
252
+ if stream.token.type == :colon
253
+ pos += 1
254
+ else
255
+ parts[pos] = stream.token.value
256
+ end
257
+ stream.next(match:Set.new([:number, :colon, :rbracket]))
258
+ end while stream.token.type != :rbracket
259
+ stream.next
260
+ if pos == 0
261
+ { type: :index, index: parts[0] }
262
+ elsif pos > 2
263
+ raise Errors::SyntaxError, 'invalid array slice syntax: too many colons'
264
+ else
265
+ { type: :slice, args: parts }
266
+ end
267
+ end
268
+
269
+ def parse_dot(stream, binding_power)
270
+ if stream.token.type == :lbracket
271
+ stream.next
272
+ parse_multi_select_list(stream)
273
+ else
274
+ expr(stream, binding_power)
275
+ end
276
+ end
277
+
278
+ def parse_key_value_pair(stream)
279
+ key = stream.token.value
280
+ stream.next(match:Set.new([:colon]))
281
+ stream.next
282
+ {
283
+ type: :key_value_pair,
284
+ key: key,
285
+ children: [expr(stream)]
286
+ }
287
+ end
288
+
289
+ def parse_multi_select_list(stream)
290
+ nodes = []
291
+ begin
292
+ nodes << expr(stream)
293
+ if stream.token.type == :comma
294
+ stream.next
295
+ if stream.token.type == :rbracket
296
+ raise Errors::SyntaxError, 'expression epxected, found rbracket'
297
+ end
298
+ end
299
+ end while stream.token.type != :rbracket
300
+ stream.next
301
+ {
302
+ type: :multi_select_list,
303
+ children: nodes
304
+ }
305
+ end
306
+
307
+ def parse_projection(stream, binding_power)
308
+ type = stream.token.type
309
+ if stream.token.binding_power < 10
310
+ CURRENT_NODE
311
+ elsif type == :dot
312
+ stream.next(match:AFTER_DOT)
313
+ parse_dot(stream, binding_power)
314
+ elsif type == :lbracket || type == :filter
315
+ expr(stream, binding_power)
316
+ else
317
+ raise Errors::SyntaxError, 'syntax error after projection'
318
+ end
319
+ end
320
+
321
+ def parse_wildcard_array(stream, left = nil)
322
+ stream.next(match:Set.new([:rbracket]))
323
+ stream.next
324
+ {
325
+ type: :projection,
326
+ from: :array,
327
+ children: [
328
+ left ? left : CURRENT_NODE,
329
+ parse_projection(stream, Token::BINDING_POWER[:star])
330
+ ]
331
+ }
332
+ end
333
+
334
+ def parse_wildcard_object(stream, left = nil)
335
+ stream.next
336
+ {
337
+ type: :projection,
338
+ from: :object,
339
+ children: [
340
+ left ? left : CURRENT_NODE,
341
+ parse_projection(stream, Token::BINDING_POWER[:star])
342
+ ]
343
+ }
344
+ end
345
+
346
+ end
347
+ end