aws-codedeploy-agent 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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