aws-sdk-code-generator 0.1.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/lib/aws-sdk-code-generator.rb +91 -0
  3. data/lib/aws-sdk-code-generator/apply_docs.rb +37 -0
  4. data/lib/aws-sdk-code-generator/code_builder.rb +201 -0
  5. data/lib/aws-sdk-code-generator/dsl/access_control_statement.rb +23 -0
  6. data/lib/aws-sdk-code-generator/dsl/attribute_accessor.rb +43 -0
  7. data/lib/aws-sdk-code-generator/dsl/attribute_reader.rb +11 -0
  8. data/lib/aws-sdk-code-generator/dsl/attribute_writer.rb +11 -0
  9. data/lib/aws-sdk-code-generator/dsl/autoload_statement.rb +15 -0
  10. data/lib/aws-sdk-code-generator/dsl/block_param.rb +11 -0
  11. data/lib/aws-sdk-code-generator/dsl/class.rb +27 -0
  12. data/lib/aws-sdk-code-generator/dsl/code_literal.rb +66 -0
  13. data/lib/aws-sdk-code-generator/dsl/code_object.rb +33 -0
  14. data/lib/aws-sdk-code-generator/dsl/docstring.rb +36 -0
  15. data/lib/aws-sdk-code-generator/dsl/eigenclass.rb +15 -0
  16. data/lib/aws-sdk-code-generator/dsl/extend_statement.rb +12 -0
  17. data/lib/aws-sdk-code-generator/dsl/formatter.rb +25 -0
  18. data/lib/aws-sdk-code-generator/dsl/include_statement.rb +17 -0
  19. data/lib/aws-sdk-code-generator/dsl/main.rb +105 -0
  20. data/lib/aws-sdk-code-generator/dsl/method.rb +108 -0
  21. data/lib/aws-sdk-code-generator/dsl/module.rb +167 -0
  22. data/lib/aws-sdk-code-generator/dsl/option_tag.rb +36 -0
  23. data/lib/aws-sdk-code-generator/dsl/param.rb +43 -0
  24. data/lib/aws-sdk-code-generator/dsl/param_list.rb +38 -0
  25. data/lib/aws-sdk-code-generator/dsl/return_tag.rb +19 -0
  26. data/lib/aws-sdk-code-generator/dsl/tag_default.rb +20 -0
  27. data/lib/aws-sdk-code-generator/dsl/tag_docstring.rb +27 -0
  28. data/lib/aws-sdk-code-generator/dsl/tag_type.rb +18 -0
  29. data/lib/aws-sdk-code-generator/errors.rb +30 -0
  30. data/lib/aws-sdk-code-generator/gem_builder.rb +71 -0
  31. data/lib/aws-sdk-code-generator/generators/client_api_module.rb +334 -0
  32. data/lib/aws-sdk-code-generator/generators/client_class.rb +389 -0
  33. data/lib/aws-sdk-code-generator/generators/client_operation_documentation.rb +166 -0
  34. data/lib/aws-sdk-code-generator/generators/errors_module.rb +25 -0
  35. data/lib/aws-sdk-code-generator/generators/resource/action.rb +88 -0
  36. data/lib/aws-sdk-code-generator/generators/resource/batch_builder.rb +211 -0
  37. data/lib/aws-sdk-code-generator/generators/resource/builder.rb +50 -0
  38. data/lib/aws-sdk-code-generator/generators/resource/client_getter.rb +15 -0
  39. data/lib/aws-sdk-code-generator/generators/resource/client_request.rb +49 -0
  40. data/lib/aws-sdk-code-generator/generators/resource/client_request_docs.rb +97 -0
  41. data/lib/aws-sdk-code-generator/generators/resource/client_request_params.rb +88 -0
  42. data/lib/aws-sdk-code-generator/generators/resource/collection_class.rb +180 -0
  43. data/lib/aws-sdk-code-generator/generators/resource/data_attribute_getter.rb +24 -0
  44. data/lib/aws-sdk-code-generator/generators/resource/data_loaded_method.rb +18 -0
  45. data/lib/aws-sdk-code-generator/generators/resource/data_method.rb +49 -0
  46. data/lib/aws-sdk-code-generator/generators/resource/exists_method.rb +29 -0
  47. data/lib/aws-sdk-code-generator/generators/resource/extract_identifier_method.rb +32 -0
  48. data/lib/aws-sdk-code-generator/generators/resource/has_association.rb +101 -0
  49. data/lib/aws-sdk-code-generator/generators/resource/has_many_association.rb +108 -0
  50. data/lib/aws-sdk-code-generator/generators/resource/identifier_getter.rb +26 -0
  51. data/lib/aws-sdk-code-generator/generators/resource/identifiers_method.rb +28 -0
  52. data/lib/aws-sdk-code-generator/generators/resource/initialize_method.rb +67 -0
  53. data/lib/aws-sdk-code-generator/generators/resource/load_method.rb +65 -0
  54. data/lib/aws-sdk-code-generator/generators/resource/value_source.rb +68 -0
  55. data/lib/aws-sdk-code-generator/generators/resource/waiter_method.rb +61 -0
  56. data/lib/aws-sdk-code-generator/generators/resource_class.rb +325 -0
  57. data/lib/aws-sdk-code-generator/generators/response_structure_example.rb +83 -0
  58. data/lib/aws-sdk-code-generator/generators/root_resource_class.rb +42 -0
  59. data/lib/aws-sdk-code-generator/generators/service_documentation.rb +64 -0
  60. data/lib/aws-sdk-code-generator/generators/shared_example.rb +132 -0
  61. data/lib/aws-sdk-code-generator/generators/structure_type_class.rb +95 -0
  62. data/lib/aws-sdk-code-generator/generators/syntax_example.rb +169 -0
  63. data/lib/aws-sdk-code-generator/generators/types_module.rb +52 -0
  64. data/lib/aws-sdk-code-generator/generators/waiter_class.rb +62 -0
  65. data/lib/aws-sdk-code-generator/generators/waiters_module.rb +20 -0
  66. data/lib/aws-sdk-code-generator/hash_formatter.rb +122 -0
  67. data/lib/aws-sdk-code-generator/helper.rb +215 -0
  68. data/lib/aws-sdk-code-generator/service.rb +126 -0
  69. data/lib/aws-sdk-code-generator/underscore.rb +45 -0
  70. data/lib/aws-sdk-code-generator/view.rb +23 -0
  71. data/lib/aws-sdk-code-generator/views.rb +3 -0
  72. data/lib/aws-sdk-code-generator/views/features/env.rb +24 -0
  73. data/lib/aws-sdk-code-generator/views/features/step_definitions.rb +20 -0
  74. data/lib/aws-sdk-code-generator/views/gemspec.rb +41 -0
  75. data/lib/aws-sdk-code-generator/views/service_module.rb +85 -0
  76. data/lib/aws-sdk-code-generator/views/spec/spec_helper.rb +24 -0
  77. data/lib/aws-sdk-code-generator/views/version.rb +16 -0
  78. metadata +120 -0
@@ -0,0 +1,166 @@
1
+ module AwsSdkCodeGenerator
2
+ module Generators
3
+ class ClientOperationDocumentation
4
+
5
+ include Helper
6
+
7
+ def self.apply(options)
8
+ new(options).apply(options)
9
+ end
10
+
11
+ # @option options [required, Hash] :api
12
+ # @option options [required, String] :service_identifier
13
+ # @option options [required, String] :operation_name
14
+ # @option options [required, Hash] :operation
15
+ # @option options [Hash] :examples
16
+ def initialize(options)
17
+ @api = options.fetch(:api)
18
+ @service_id = options.fetch(:service_identifier)
19
+ @operation_name = options.fetch(:operation_name)
20
+ @method_name = underscore(@operation_name)
21
+ @operation = options.fetch(:operation)
22
+ @examples = options.fetch(:examples, nil) || { 'examples' => {} }
23
+ end
24
+
25
+ def apply(options)
26
+ method = options.fetch(:method)
27
+ method.docstring do |docstring|
28
+ apply_operation_docs(docstring)
29
+ apply_option_tags(docstring)
30
+ apply_return_tags(docstring)
31
+ apply_shared_examples(docstring)
32
+ apply_examples_from_disk(docstring)
33
+ apply_request_syntax_example(docstring)
34
+ apply_response_struture_example(docstring)
35
+ apply_overload_tag(docstring)
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def apply_operation_docs(docstring)
42
+ docstring.append(markdown(@operation['documentation']))
43
+ end
44
+
45
+ def apply_option_tags(docstring)
46
+ # document the `:response_target` option if the response is streaming
47
+ if output = shape(@operation['output'])
48
+ if output['payload'] && output['members'][output['payload']]['streaming']
49
+ docstring.lines.concat(Dsl::OptionTag.new(
50
+ name: 'response_target',
51
+ type: 'String, IO',
52
+ param: 'params',
53
+ required: false,
54
+ docstring: 'Where to write response data, file path, or IO object.'
55
+ ).lines)
56
+ end
57
+ end
58
+
59
+ if input_shape = shape(@operation['input'])
60
+ required = input_shape['required'] || []
61
+ input_shape['members'].each_pair do |member_name, member_ref|
62
+ docstring.lines.concat(Dsl::OptionTag.new(
63
+ name: underscore(member_name),
64
+ type: ruby_input_type(member_ref),
65
+ param: 'params',
66
+ required: required.include?(member_name),
67
+ docstring: documentation(member_ref),
68
+ ).lines)
69
+ end
70
+ end
71
+ end
72
+
73
+ def apply_return_tags(docstring)
74
+ output_shape = shape(@operation['output'])
75
+ resp = '{Seahorse::Client::Response response}'
76
+ if output_shape && output_shape['members'].size > 0
77
+ type = ruby_type(@operation['output'])
78
+ returns = "@return [#{type}] Returns a #{resp} object which responds to "
79
+ returns << "the following methods:\n\n"
80
+ output_shape['members'].each_pair do |mname, mref|
81
+ mtype = ruby_type(mref).gsub(/</, '&lt;').gsub(/>/, '&gt;')
82
+ returns << " * {#{type}##{underscore(mname)} ##{mname}} => #{mtype}\n"
83
+ end
84
+ docstring.append(returns)
85
+ else
86
+ docstring.append("@return [Struct] Returns an empty #{resp}.")
87
+ end
88
+ end
89
+
90
+ def apply_shared_examples(docstring)
91
+ (@examples[@operation_name] || []).size.times do |n|
92
+ begin
93
+ # TODO : known issue with an ec2 shared example that
94
+ # attempts to document a member that is not
95
+ # present in the model any longer (intentionally
96
+ # removed in customizations) - raises runtime
97
+ # error. This should be cleaned up.
98
+ docstring.append(
99
+ SharedExample.new(
100
+ operation_name: @operation_name,
101
+ api: @api,
102
+ examples: @examples,
103
+ example: n
104
+ ).to_s
105
+ )
106
+ rescue
107
+ end
108
+ end
109
+ end
110
+
111
+ def apply_shared_example(docstring, example)
112
+ return
113
+
114
+ input_comments = json_ex['comments']['input']
115
+ input = SharedExample.new(json_ex['input'], method_name, operation, input_comments).to_str_input
116
+ parts = []
117
+ parts << "@example Example: #{json_ex['title']}\n\n"
118
+ parts << " # #{json_ex['description']}\n\n"
119
+ parts += input.lines.map { |line| " " + line }
120
+ if json_ex['output']
121
+ output_comments = json_ex['comments']['output']
122
+ output = SharedExample.new(json_ex['output'], method_name, operation, output_comments).to_str_output
123
+ parts << "\n\n # resp.to_h outputs the following:\n"
124
+ parts += output.lines.map { |line| " " + line }
125
+ end
126
+ tag(parts.join)
127
+ end
128
+
129
+ def apply_examples_from_disk(docstring)
130
+ examples = File.expand_path('../../../../../../doc-src/examples', __FILE__)
131
+ glob = "#{examples}/#{@service_id}/client/#{@method_name}/*.rb"
132
+ Dir.glob(glob).map do |path|
133
+ title = File.basename(path).split(/\./).first
134
+ title = title.sub(/^\d+_/, '').gsub(/_/, ' ')
135
+ title = title[0].upcase + title[1..-1]
136
+ docstring.append("\n@example #{title}")
137
+ docstring.append(" " + File.read(path).lines.join(' '))
138
+ end
139
+ end
140
+
141
+ def apply_request_syntax_example(docstring)
142
+ if @operation['input']
143
+ syntax = SyntaxExample.new(
144
+ struct_shape: shape(@operation['input']),
145
+ api: @api,
146
+ indent: ' '
147
+ ).format.strip
148
+ docstring.append("\n@example Request syntax with placeholder values")
149
+ docstring.append(" resp = client.#{@method_name}(#{syntax})")
150
+ end
151
+ end
152
+
153
+ def apply_response_struture_example(docstring)
154
+ output = @operation['output']
155
+ if output && shape(output)['members'].size > 0
156
+ docstring.append(ResponseStructureExample.new(shape_ref:output, api:@api).to_s)
157
+ end
158
+ end
159
+
160
+ def apply_overload_tag(docstring)
161
+ docstring.append("@overload #{@method_name}(params = {})")
162
+ end
163
+
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,25 @@
1
+ module AwsSdkCodeGenerator
2
+ module Generators
3
+ class ErrorsModule < Dsl::Module
4
+
5
+ include Helper
6
+
7
+ def initialize(options)
8
+ @api = options.fetch(:api)
9
+ super('Errors')
10
+ self.extend('Aws::Errors::DynamicErrors')
11
+ self.class('ResourceNotLoadable', extends: 'RuntimeError') do |c|
12
+ c.docstring(<<-DOCSTRING)
13
+ Raised when calling #load or #data on a resource class that can not be
14
+ loaded. This can happen when:
15
+
16
+ * A resource class has identifiers, but no data attributes.
17
+ * Resource data is only available when making an API call that
18
+ enumerates all resources of that type.
19
+ DOCSTRING
20
+ end
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,88 @@
1
+ module AwsSdkCodeGenerator
2
+ module Generators
3
+ module Resource
4
+ class Action < Dsl::Method
5
+
6
+ include Helper
7
+
8
+ # @option options [required, String] :name
9
+ # @option options [required, Hash] :action
10
+ # @option options [required, Hash] :api
11
+ # @option options [String] :var_name ('')
12
+ def initialize(options = {})
13
+ @api = options.fetch(:api)
14
+ @request = options.fetch(:action).fetch('request')
15
+ @resource = options.fetch(:action).fetch('resource', nil)
16
+ @var_name = options.fetch(:var_name, '')
17
+ super(underscore(options.fetch(:name)))
18
+ param('options', type:Hash, default:{})
19
+ apply_client_request_docs
20
+ apply_response
21
+ apply_return_tag
22
+ end
23
+
24
+ private
25
+
26
+ def apply_client_request_docs
27
+ ClientRequestDocs.new(
28
+ request: @request,
29
+ api: @api,
30
+ var_name: @var_name,
31
+ returns: @resource ? @resource['type'].downcase : nil
32
+ ).apply(self)
33
+ end
34
+
35
+ def apply_response
36
+ if @resource && batch?(@resource)
37
+ code('batch = []')
38
+ add(client_request)
39
+ code(BatchBuilder.new(resource: @resource))
40
+ code("#{resource_type}::Collection.new([batch], size: batch.size)")
41
+ elsif @resource
42
+ add(client_request)
43
+ code(Builder.new(resource: @resource, request_made: true))
44
+ else
45
+ add(client_request)
46
+ code('resp.data')
47
+ end
48
+ end
49
+
50
+ def client_request
51
+ ClientRequest.new(
52
+ request: @request,
53
+ resp: true
54
+ )
55
+ end
56
+
57
+ def apply_return_tag
58
+ if @resource && batch?(@resource)
59
+ returns("#{resource_type}::Collection")
60
+ elsif @resource
61
+ returns(resource_type)
62
+ else
63
+ returns(request_return_type)
64
+ end
65
+ end
66
+
67
+ def resource_type
68
+ @resource['type']
69
+ end
70
+
71
+ def request_return_type
72
+ operation = @api['operations'][@request['operation']]
73
+ if operation['output']
74
+ "Types::#{operation['output']['shape']}"
75
+ else
76
+ 'EmptyStructure'
77
+ end
78
+ end
79
+
80
+ def batch?(resource)
81
+ paths = (@resource['identifiers'] || []).map {|i| i['path'] }
82
+ paths << @resource['path']
83
+ paths.compact.any? { |path| path.match(/\[/) }
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,211 @@
1
+ require 'set'
2
+
3
+ module AwsSdkCodeGenerator
4
+ module Generators
5
+ module Resource
6
+ class BatchBuilder < Dsl::CodeLiteral
7
+
8
+ include Helper
9
+
10
+ # @option options [required, Hash] :resource
11
+ # @option options [String] :resp_var_name ('resp')
12
+ def initialize(options)
13
+ @resource = options.fetch(:resource)
14
+ @resp_var_name = options.fetch(:resp_var_name, 'resp')
15
+ verify_resource!
16
+ @context = loop_context
17
+ super()
18
+
19
+ # create nested blocks from each loop expression
20
+ stack = [self]
21
+ loops.each do |loop_expression|
22
+ stack.last.append(loop_expression)
23
+ stack << stack.last.indent
24
+ end
25
+
26
+ # add resource to the batch
27
+ stack.last.append("batch << #{resource_class}.new(#{constructor_args})")
28
+
29
+ # add closing end tags
30
+ stack.pop
31
+ stack.each { |c| c.append('end') }
32
+ end
33
+
34
+ private
35
+
36
+ def resource_class
37
+ @resource['type']
38
+ end
39
+
40
+ def constructor_args
41
+ hash = {}
42
+ hash.update(identifiers)
43
+ hash[:data] = data_path if @resource['path']
44
+ hash[:client] = "@client"
45
+ HashFormatter.new(wrap:false, inline:true).format(hash)
46
+ end
47
+
48
+ def identifiers
49
+ (@resource['identifiers'] || []).inject({}) do |hash, identifier|
50
+ value = relative_identifier_path(identifier)
51
+ hash[underscore(identifier['target']).to_sym] = value
52
+ hash
53
+ end
54
+ end
55
+
56
+ def data_path
57
+ relative_identifier_path({
58
+ 'source' => 'data',
59
+ 'path' => @resource['path'],
60
+ })
61
+ end
62
+
63
+ def relative_identifier_path(identifier)
64
+ path = identifier['path']
65
+ if path && path.include?('[]')
66
+ prefix = loops.last.match(/\|(.+)\|/)[1]
67
+ suffix = underscore(path[common_prefix.length..-1])
68
+ if @context == :options
69
+ suffix = suffix.gsub(/\.\w+/) { |word| "[:#{word[1..-1]}]" }
70
+ end
71
+ suffix.length == 0 ? prefix : prefix + suffix
72
+ else
73
+ ValueSource.new(identifier).to_s
74
+ end
75
+ end
76
+
77
+ def common_prefix
78
+ paths = plural_paths
79
+ if paths.empty?
80
+ ''
81
+ elsif paths.size == 1
82
+ # grab everything upto and including the final []
83
+ paths.first.match(/(.+\[\]).*?$/)[1]
84
+ else
85
+ prefix = find_prefix(paths)
86
+ prefix = prefix.sub(/\[\].+?$/, '[]')
87
+ if prefix[-2..-1] != '[]'
88
+ msg = 'response paths must have a common prefix ending in [], got :'
89
+ msg << paths.inspect
90
+ raise ArgumentError, msg
91
+ else
92
+ prefix
93
+ end
94
+ end
95
+ end
96
+
97
+ def find_prefix(paths)
98
+ prefix = ''
99
+ loop.with_index do |_, n|
100
+ return prefix if paths.empty?
101
+ letter = paths[0][n]
102
+ paths.each do |path|
103
+ return prefix if path[n].nil?
104
+ return prefix if path[n] != letter
105
+ end
106
+ prefix += letter
107
+ end
108
+ end
109
+
110
+ def loops
111
+ loop_var =
112
+ case @context
113
+ when :data then 'data.'
114
+ when :options then 'options'
115
+ when :response then "#{@resp_var_name}.data."
116
+ end
117
+
118
+ used_vars = Set.new
119
+ used_vars << loop_var
120
+
121
+ parts = common_prefix.split('[]')
122
+ parts = parts.map.with_index do |part,n|
123
+ part = underscore(part)
124
+ if @context == :options
125
+ part = part.gsub(/\w+/) { |word| "[:#{word}]" }
126
+ part = part.gsub(/\./, '')
127
+ end
128
+ part = "#{loop_var}#{part}"
129
+ loop_var = loop_letter(part, used_vars)
130
+ part = part + ".each do |#{loop_var}|"
131
+ part
132
+ end
133
+ parts
134
+ end
135
+
136
+ def loop_letter(str, used_vars)
137
+ letter = if @context == :options
138
+ str.scan(/:\w/).last[1]
139
+ else
140
+ str.split('.').last[0]
141
+ end
142
+ n = 1
143
+ var = letter
144
+ while used_vars.include?(var)
145
+ n += 1
146
+ var = "#{letter}#{n}"
147
+ end
148
+ used_vars << var
149
+ var
150
+ end
151
+
152
+ def all_plural_paths
153
+ paths = {}
154
+ @resource['identifiers'].each do |i|
155
+ if i['path'] && i['path'].include?('[]')
156
+ paths[i['source']] ||= []
157
+ paths[i['source']] << i['path']
158
+ end
159
+ end
160
+ #if @resource['path'] && @resource['path'].include?('[]')
161
+ # type = @context == :data ? 'data' : 'response'
162
+ # paths[type] ||= []
163
+ # paths[type] << @resource['path']
164
+ #end
165
+ paths
166
+ end
167
+
168
+ def plural_paths
169
+ all_plural_paths.values.first
170
+ end
171
+
172
+ def verify_resource!
173
+ verify_plural_paths!
174
+ end
175
+
176
+ def verify_plural_paths!
177
+ paths = all_plural_paths
178
+ case paths.size
179
+ when 0
180
+ msg = 'expected at least one plural identifier path, got none'
181
+ raise ArgumentError, msg
182
+ when 1
183
+ case paths.keys.first
184
+ when 'requestParameter'
185
+ when 'response'
186
+ when 'data'
187
+ else
188
+ msg = "unsupported identifier source #{paths.keys.first.inspect}"
189
+ raise ArgumentError, msg
190
+ end
191
+ else
192
+ msg = 'mixing plural source types is not supported'
193
+ raise ArgumentError, msg
194
+ end
195
+ end
196
+
197
+ def loop_context
198
+ case all_plural_paths.keys
199
+ when ['response'] then :response
200
+ when ['data'] then :data
201
+ when ['requestParameter'] then :options
202
+ else
203
+ msg = "unable to determine loop context: #{@resource.inspect}"
204
+ raise msg
205
+ end
206
+ end
207
+
208
+ end
209
+ end
210
+ end
211
+ end