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,52 @@
1
+ require 'set'
2
+
3
+ module AwsSdkCodeGenerator
4
+ module Generators
5
+ class TypesModule < Dsl::Module
6
+
7
+ include Helper
8
+
9
+ # @option options [required, Hash] :api
10
+ def initialize(options)
11
+ @api = options.fetch(:api)
12
+ super('Types')
13
+ input_shapes = compute_input_shapes(@api)
14
+ structures.each do |shape_name, shape|
15
+ add(StructureTypeClass.new(
16
+ name: shape_name,
17
+ api: @api,
18
+ used_as_input: input_shapes.include?(shape_name)
19
+ ))
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def compute_input_shapes(api)
26
+ inputs = Set.new
27
+ (api['operations'] || {}).each do |_, operation|
28
+ visit_inputs(operation['input'], inputs) if operation['input']
29
+ end
30
+ inputs
31
+ end
32
+
33
+ def visit_inputs(shape_ref, inputs)
34
+ return if inputs.include?(shape_ref['shape']) # recursion
35
+ inputs << shape_ref['shape']
36
+ s = shape(shape_ref)
37
+ case s['type']
38
+ when 'structure'
39
+ s['members'].each_pair do |_, member_ref|
40
+ visit_inputs(member_ref, inputs)
41
+ end
42
+ when 'list'
43
+ visit_inputs(s['member'], inputs)
44
+ when 'map'
45
+ visit_inputs(s['key'], inputs)
46
+ visit_inputs(s['value'], inputs)
47
+ end
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,62 @@
1
+ module AwsSdkCodeGenerator
2
+ module Generators
3
+ class WaiterClass < Dsl::Class
4
+
5
+ # @option options [required, String] :waiter_name
6
+ # @option options [required, Hash] :waiter
7
+ def initialize(options)
8
+ waiter = options.fetch(:waiter)
9
+ client_method = underscore(waiter['operation'])
10
+ super(options.fetch(:waiter_name))
11
+ code <<-CODE
12
+ # @param [Hash] options
13
+ # @option options [required, Client] :client
14
+ # @option options [Integer] :max_attempts (#{waiter['maxAttempts']})
15
+ # @option options [Integer] :delay (#{waiter['delay']})
16
+ # @option options [Proc] :before_attempt
17
+ # @option options [Proc] :before_wait
18
+ def initialize(options)
19
+ @client = options.fetch(:client)
20
+ @waiter = Aws::Waiters::Waiter.new({
21
+ max_attempts: #{waiter['maxAttempts']},
22
+ delay: #{waiter['delay']},
23
+ poller: Aws::Waiters::Poller.new(#{poller_args(waiter)} )
24
+ }.merge(options))
25
+ end
26
+
27
+ # @option (see Client##{client_method})
28
+ # @return (see Client##{client_method})
29
+ def wait(params = {})
30
+ @waiter.wait(client: @client, params: params)
31
+ end
32
+
33
+ # @api private
34
+ attr_reader :waiter
35
+ CODE
36
+ end
37
+
38
+ private
39
+
40
+ def poller_args(hash)
41
+ hash = hash.dup
42
+ hash.delete('delay')
43
+ hash.delete('maxAttempts')
44
+ hash[:operation_name] = Underscore.underscore(hash.delete('operation')).to_sym
45
+ hash[:acceptors] = hash.delete('acceptors').map do |acceptor|
46
+ if acceptor['argument']
47
+ acceptor['argument'] = Underscore.underscore_jmespath(acceptor['argument'])
48
+ end
49
+ acceptor
50
+ end
51
+ formatter = HashFormatter.new(
52
+ wrap: false,
53
+ inline: false,
54
+ quote_strings: true
55
+ )
56
+ lines = formatter.format(hash).lines
57
+ lines.map { |line| " #{line}" }.join
58
+ end
59
+
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,20 @@
1
+ module AwsSdkCodeGenerator
2
+ module Generators
3
+ class WaitersModule < Dsl::Module
4
+
5
+ include Helper
6
+
7
+ # @option options [required, Module] :parent
8
+ # @option options [required, Hash, nil] :waiters
9
+ def initialize(options)
10
+ waiters = options.fetch(:waiters)
11
+ super('Waiters', parent: options.fetch(:parent))
12
+ require_statement('aws-sdk-core/waiters')
13
+ waiters['waiters'].each do |waiter_name, waiter|
14
+ add(WaiterClass.new(waiter_name: waiter_name, waiter: waiter))
15
+ end
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,122 @@
1
+ module AwsSdkCodeGenerator
2
+ class HashFormatter
3
+
4
+ # @option options [Boolean] :wrap (true) When `true`, the formatted hash will
5
+ # be wrapped with curly braces.
6
+ #
7
+ # @option options [Boolean] :inline (false) When `true` the formatted hash will
8
+ # contain no newlines.
9
+ #
10
+ # @option options [Boolean] :quote_strings (false) By default, all hash string values
11
+ # must contain their own quotes. If you pass `true`, then all hash string
12
+ # values will be inspected via `#inspect` which will auto-quote them.
13
+ #
14
+ def initialize(options = {})
15
+ @wrap = options.fetch(:wrap, true)
16
+ @inline = options.fetch(:inline, false)
17
+ @quote_strings = options.fetch(:quote_strings, false)
18
+ end
19
+
20
+ def format(obj)
21
+ result = hash(obj, i:'', inline:@inline)
22
+ result = unwrap(result, obj.size) if !@wrap
23
+ result = result.strip if @inline && result.lines.to_a.length == 1
24
+ result
25
+ end
26
+
27
+ private
28
+
29
+ def value(obj, options)
30
+ i = options.fetch(:i)
31
+ inline = options.fetch(:inline)
32
+ case obj
33
+ when Hash then hash(obj, i:i, inline:inline)
34
+ when Array then array(obj, i:i)
35
+ when String then @quote_strings ? obj.inspect : obj
36
+ when Symbol then obj.inspect
37
+ when Fixnum, true, false then obj.inspect
38
+ else raise ArgumentError, "unsupported value `#{obj.class}'"
39
+ end
40
+ end
41
+
42
+ def hash(hash, options)
43
+ i = options.fetch(:i)
44
+ inline = options.fetch(:inline)
45
+ if hash.empty?
46
+ '{}'
47
+ elsif inline_hash?(hash, inline)
48
+ inline_hash(hash)
49
+ else
50
+ multiline_hash(hash, i:i)
51
+ end
52
+ end
53
+
54
+ def inline_hash(hash)
55
+ "{ #{hash_entry(hash.keys[0], hash.values[0], i:'')} }"
56
+ end
57
+
58
+ def multiline_hash(hash, options)
59
+ i = options.fetch(:i)
60
+ str = "{\n"
61
+ hash.each.with_index do |(key, value), n|
62
+ str += "#{i} #{hash_entry(key, value, i:i)}"
63
+ str += "," unless n == hash.keys.length - 1
64
+ str += "\n"
65
+ end
66
+ str + "#{i}}"
67
+ end
68
+
69
+ def hash_entry(key, value, options)
70
+ i = options.fetch(:i)
71
+ value = value(value, i: i + ' ', inline:false)
72
+ if Symbol === key
73
+ "#{key}: #{value}"
74
+ else
75
+ "#{key.inspect} => #{value}"
76
+ end
77
+ end
78
+
79
+ def array(array, options)
80
+ i = options.fetch(:i)
81
+ if array.empty?
82
+ '[]'
83
+ elsif inline_array?(array)
84
+ "[#{value(array[0], i:i, inline:true)}]"
85
+ else
86
+ format_multiline_array(array, i:i)
87
+ end
88
+ end
89
+
90
+ def format_multiline_array(array, options)
91
+ i = options.fetch(:i)
92
+ str = "[\n"
93
+ array.each.with_index do |value, n|
94
+ str += "#{i} #{value(value, i:i + ' ', inline:true)}"
95
+ str += "," unless n == array.length - 1
96
+ str += "\n"
97
+ end
98
+ str + "#{i}]"
99
+ end
100
+
101
+ def inline_hash?(hash, inline)
102
+ hash.length == 1 && String === hash.values[0] && inline
103
+ end
104
+
105
+ def inline_array?(array)
106
+ array.length == 1# && String === array[0]
107
+ end
108
+
109
+ def unwrap(str, size)
110
+ if @inline || size > 1
111
+ str[1..-2]
112
+ else
113
+ lines = str.lines.to_a
114
+ lines.shift
115
+ lines.pop
116
+ lines = lines.map { |line| line[2..-1] }
117
+ lines.join.rstrip
118
+ end
119
+ end
120
+
121
+ end
122
+ end
@@ -0,0 +1,215 @@
1
+ require 'kramdown'
2
+ require 'set'
3
+
4
+ # disable inline attribute lists
5
+ class Kramdown::Converter::Kramdown
6
+ def ial_for_element(*args)
7
+ nil
8
+ end
9
+ end
10
+
11
+ module AwsSdkCodeGenerator
12
+ module Helper
13
+
14
+ def identifier_type(identifier)
15
+ case identifier['type']
16
+ when nil, 'string' then 'String'
17
+ when 'integer' then 'Integer'
18
+ else
19
+ msg = "unsupported identifier type `#{identifier['type']}'"
20
+ raise ArgumentError, msg
21
+ end
22
+ end
23
+
24
+ # @param [Array<Array<String>>]
25
+ def markdown_table(table)
26
+ # compute the width of each column by scanning for longest values
27
+ column_width = lambda do |col|
28
+ table.map { |row| row[col].size }.max
29
+ end
30
+ widths = [
31
+ column_width.call(0),
32
+ column_width.call(1),
33
+ column_width.call(2),
34
+ column_width.call(3),
35
+ ]
36
+
37
+ # insert a dashed line after the header row
38
+ table = [
39
+ table[0],
40
+ ['-' * widths[0], '-' * widths[1], '-' * widths[2], '-' * widths[3]]
41
+ ] + table[1..-1]
42
+
43
+ # build the final table
44
+ line = "| #{widths.map{|n| "%-#{n}s" }.join(' | ')} |"
45
+ table.map { |row| line % row }.join("\n")
46
+ end
47
+ module_function :markdown_table
48
+
49
+ def underscore(str)
50
+ str.split('.').map do |part|
51
+ Underscore.underscore(part)
52
+ end.join('.')
53
+ end
54
+
55
+ def structures
56
+ Enumerator.new do |y|
57
+ (@api['shapes'] || {}).each do |shape_name, shape|
58
+ if shape['type'] == 'structure' && !shape['error'] && !shape['exception']
59
+ y.yield(shape_name, shape)
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ # @option options [Boolean] :nested (false)
66
+ def ruby_input_type(shape_ref, options = {})
67
+ nested = options.fetch(:nested, false)
68
+ shape = @api['shapes'][shape_ref['shape']]
69
+ case shape['type']
70
+ when 'byte' then 'Integer<byte>'
71
+ when 'blob' then 'String, IO'
72
+ when 'boolean' then 'Boolean'
73
+ when 'character' then 'String<character>'
74
+ when 'double' then 'Float'
75
+ when 'float' then 'Float'
76
+ when 'integer' then 'Integer'
77
+ when 'list'
78
+ if nested
79
+ "Array"
80
+ else
81
+ "Array<#{ruby_input_type(shape['member'], nested:true)}>"
82
+ end
83
+ when 'long' then 'Integer'
84
+ when 'map'
85
+ if nested
86
+ "Hash"
87
+ else
88
+ "Hash<String,#{ruby_input_type(shape['value'], nested:true)}>"
89
+ end
90
+ when 'string' then 'String'
91
+ when 'structure' then "Types::#{shape_ref['shape']}"
92
+ when 'timestamp' then 'Time,DateTime,Date,Integer,String'
93
+ else
94
+ raise "unhandled type #{shape.type}.inspect"
95
+ end
96
+ end
97
+
98
+ def ruby_type(shape_ref)
99
+ shape = @api['shapes'][shape_ref['shape']]
100
+ case shape['type']
101
+ when 'blob' then streaming?(shape_ref, shape) ? 'IO' : 'String'
102
+ when 'boolean' then 'Boolean'
103
+ when 'byte' then 'Integer<byte>'
104
+ when 'character' then 'String<character>'
105
+ when 'double' then 'Float'
106
+ when 'float' then 'Float'
107
+ when 'integer' then 'Integer'
108
+ when 'list' then "Array<#{ruby_type(shape['member'])}>"
109
+ when 'long' then 'Integer'
110
+ when 'map' then "Hash<String,#{ruby_type(shape['value'])}>"
111
+ when 'string' then 'String'
112
+ when 'structure' then "Types::#{shape_ref['shape']}"
113
+ when 'timestamp' then 'Time'
114
+ else
115
+ raise "unhandled type #{shape['type'].inspect}"
116
+ end
117
+ end
118
+
119
+ def streaming?(ref, shape)
120
+ ref['streaming'] || shape['streaming']
121
+ end
122
+
123
+ # @option options [Integer] :line_width (70)
124
+ def documentation(ref_or_shape, options = {})
125
+ line_width = options.fetch(:line_width, 70)
126
+ shape = ref_or_shape.key?('type') ? ref_or_shape : shape(ref_or_shape)
127
+ docstring = ref_or_shape['documentation'] || shape['documentation'] || ''
128
+
129
+ # append boilerplate idempotency docs
130
+ if ref_or_shape['idempotencyToken']
131
+ docstring = "#{docstring}<p>**A suitable default value is "
132
+ docstring += "auto-generated.** You should normally "
133
+ docstring += "not need to pass this option.</p>"
134
+ docstring = docstring.strip
135
+ end
136
+
137
+ docstring == '' ? docstring : markdown(docstring, line_width:line_width)
138
+ end
139
+
140
+ def member_shape(shape_name, member_name)
141
+ shape = @api['shapes'][shape_name]
142
+ member_ref = shape['members'][member_name]
143
+ shape(member_ref)
144
+ end
145
+
146
+ def shape(ref)
147
+ if ref.nil?
148
+ nil
149
+ else
150
+ shape = @api['shapes'][ref['shape']]
151
+ raise ArgumentError, "no such shape `#{ref['shape']}'" unless shape
152
+ shape
153
+ end
154
+ end
155
+
156
+ # @option options [Integer] :line_width (70)
157
+ def markdown(html, options = {})
158
+ line_width = options.fetch(:line_width, 70)
159
+ # TODO : this section of code is **very slow** and runs many times
160
+ # while building a service.
161
+ if html
162
+ html = "<p>#{html}</p>" unless html.match(/<\w+>/)
163
+
164
+ # unescaped curly braces cause YARD errors, they are interpreted
165
+ # as code links.
166
+ html = html.gsub('{', "\\{").gsub('}', "\\}")
167
+
168
+ # Kramdown generates invalid markup when there are attributes
169
+ # on the code tag, have to reduce these down to get the proper markdown.
170
+ html = html.gsub(/<code.*?>(.+?)<\/code>/) { "<code>#{$1}</code>" }
171
+
172
+ # Kramdown creates invalid markup with target="_blank" attributes.
173
+ html = html.gsub(' target="_blank"', '')
174
+
175
+ # There are quite a few empty <a> tags. These appear to be code names,
176
+ # such as structure member names, or structure type names. We should
177
+ # investigate if it is possible to inflect these properly and then
178
+ # turn them into YARD links.
179
+ html = html.gsub(/<a>(.+?)<\/a>/) { $1 }
180
+
181
+ # <important> tag doesn't render well
182
+ html = html.gsub(/<important>(.+?)<\/important>/){ "<p>#{$1}</p>" }
183
+
184
+ markdown = Kramdown::Document.new(
185
+ html,
186
+ input: 'html',
187
+ line_width: line_width,
188
+ auto_ids: false
189
+ ).to_kramdown.strip
190
+
191
+ # remove extra escape
192
+ markdown.gsub(/\\(\*|`|'|")/, '\1')
193
+ end
194
+ end
195
+
196
+ def deep_copy(obj)
197
+ case obj
198
+ when nil then nil
199
+ when true then true
200
+ when false then false
201
+ when Hash then obj.inject({}) { |h,(k,v)| h[deep_copy(k)] = deep_copy(v); h }
202
+ when Array then obj.map { |v| deep_copy(v) }
203
+ else
204
+ if obj.respond_to?(:dup)
205
+ obj.dup
206
+ elsif obj.respond_to?(:clone)
207
+ obj.clone
208
+ else
209
+ obj
210
+ end
211
+ end
212
+ end
213
+
214
+ end
215
+ end