google-ads-common 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # Author:: api.dklimkin@gmail.com (Danial Klimkin)
4
+ #
5
+ # Copyright:: Copyright 2011, Google Inc. All Rights Reserved.
6
+ #
7
+ # License:: Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
16
+ # implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #
20
+ # Code template generator base class
21
+
22
+ require 'savon'
23
+ require 'erb'
24
+
25
+ module AdsCommon
26
+ module Build
27
+ class SavonAbstractGenerator
28
+ def initialize(args)
29
+ if self.class() == AdsCommon::Build::SavonAbstractGenerator
30
+ raise NoMethodError, "Tried to instantiate an abstract class"
31
+ end
32
+ @require_path = args[:require_path]
33
+ @service_name = args[:service_name]
34
+ @module_name = args[:module_name]
35
+ @namespace = args[:namespace]
36
+ @generator_stamp = "Code generated by AdsCommon library %s on %s." %
37
+ [AdsCommon::ApiConfig::ADS_COMMON_VERSION,
38
+ Time.now.strftime("%Y-%m-%d %H:%M:%S")]
39
+ prepare_modules_string()
40
+ end
41
+
42
+ def generate_code()
43
+ code = ERB.new(get_code_template(), 0, '%<>')
44
+ code.result(binding)
45
+ end
46
+
47
+ def get_code_template()
48
+ raise NotImplementedError
49
+ end
50
+
51
+ def prepare_modules_string()
52
+ modules_count = @module_name.scan(/::/).length
53
+ @modules_open_string = 'module ' + @module_name.gsub(/::/, '; module ')
54
+ @modules_close_string = 'end; ' * modules_count
55
+ @modules_close_string += 'end'
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # Author:: api.dklimkin@gmail.com (Danial Klimkin)
4
+ #
5
+ # Copyright:: Copyright 2011, Google Inc. All Rights Reserved.
6
+ #
7
+ # License:: Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
16
+ # implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #
20
+ # Generates the wrappers for API services. Only used during the
21
+ # 'rake generate' step of library setup.
22
+
23
+ gem 'savon', '~>0.8.5'
24
+ require 'savon'
25
+
26
+ require 'ads_common/build/savon_service_generator'
27
+ require 'ads_common/build/savon_registry'
28
+ require 'ads_common/build/savon_registry_generator'
29
+ require 'ads_common/errors'
30
+
31
+ module AdsCommon
32
+ module Build
33
+
34
+ # Contains the methods that handle wrapper code generation.
35
+ class SavonGenerator
36
+
37
+ # Create a new generator for given WSDL
38
+ #
39
+ # Args:
40
+ # wsdl_url local or remote URL to pull WSDL data
41
+ # code_path local path to store generated files
42
+ # service_name a service name to generate stubs from
43
+ # module_name a fully-qualified module name
44
+ def initialize(wsdl_url, code_path, service_name, module_name)
45
+ @wsdl_url = wsdl_url
46
+ @code_path = code_path
47
+ @service_name = service_name
48
+ @generator_args = {
49
+ :service_name => service_name,
50
+ :module_name => module_name,
51
+ :require_path => @code_path.sub(/^lib\//, '')
52
+ }
53
+ end
54
+
55
+ #
56
+ # Pull, parse and generate wrapper for WSDL on given URL
57
+ #
58
+ # Args:
59
+ # none, instance variables are used.
60
+ #
61
+ # Returns:
62
+ # none
63
+ def process_wsdl()
64
+ client = Savon::Client.new {|wsdl| wsdl.document = @wsdl_url }
65
+ begin
66
+ @generator_args[:namespace] = client.wsdl.namespace
67
+ do_process_wsdl_client(client)
68
+ rescue AdsCommon::Errors::Error => e
69
+ error_msg = "An unrecoverable error occured during code generation"
70
+ error_msg += " for service [%s]: %s" % [@wsdl_url, e]
71
+ # TODO log properly
72
+ raise AdsCommon::Errors::BuildError, error_msg
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ # Generate code for given Savon client
79
+ def do_process_wsdl_client(client)
80
+ service_file_name = @service_name.to_s.snakecase
81
+
82
+ wsdl = client.wsdl
83
+ check_service(wsdl)
84
+
85
+ wrapper_file_name = "%s/%s.rb" % [@code_path, service_file_name]
86
+ write_wrapper(wsdl, wrapper_file_name)
87
+
88
+ registry_file_name = "%s/%s_registry.rb" %
89
+ [@code_path, service_file_name]
90
+ write_registry(wsdl, registry_file_name)
91
+ end
92
+
93
+ def check_service(wsdl)
94
+ if wsdl.endpoint.nil? || wsdl.namespace.nil?
95
+ raise AdsCommon::Errors::BuildError,
96
+ 'WSDL could not be retrieved or parsed.'
97
+ end
98
+ end
99
+
100
+ # Generates wrapper file
101
+ def write_wrapper(wsdl, file_name)
102
+ wrapper_file = create_new_file(file_name)
103
+ generator = SavonServiceGenerator.new(@generator_args)
104
+ generator.add_actions(wsdl.soap_actions.dup)
105
+ wrapper_file.write(generator.generate_code())
106
+ wrapper_file.close
107
+ end
108
+
109
+ # Generates registry file
110
+ def write_registry(wsdl, file_name)
111
+ registry_file = create_new_file(file_name)
112
+ generator = SavonRegistryGenerator.new(@generator_args)
113
+ registry = SavonRegistry.new(wsdl)
114
+ generator.add_exceptions(registry.soap_exceptions.dup)
115
+ generator.add_methods(registry.soap_methods.dup)
116
+ generator.add_types(registry.soap_types.dup)
117
+ registry_file.write(generator.generate_code())
118
+ registry_file.close
119
+ end
120
+
121
+ # Creates a new file on specified path, overwriting existing one if it
122
+ # exists
123
+ def create_new_file(file_name)
124
+ puts "Creating %s..." % [file_name]
125
+ make_dir_for_path(file_name)
126
+ new_file = File.new(file_name, File::WRONLY|File::TRUNC|File::CREAT)
127
+ end
128
+
129
+ # Creates a directory for the file path specified if not exists
130
+ def make_dir_for_path(path)
131
+ dir_name = File.dirname(path)
132
+ Dir.mkdir(dir_name) if !File.directory?(dir_name)
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,236 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # Author:: api.dklimkin@gmail.com (Danial Klimkin)
4
+ #
5
+ # Copyright:: Copyright 2011, Google Inc. All Rights Reserved.
6
+ #
7
+ # License:: Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
16
+ # implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #
20
+ # Registry object for Savon backend. Used on generation step as parsed
21
+ # representation of WSDL for API stubs generation.
22
+
23
+ require 'savon'
24
+ require 'rexml/document'
25
+
26
+ module AdsCommon
27
+ module Build
28
+ STANDARD_TYPES = ['long', 'boolean', 'int', 'string', 'double']
29
+
30
+ # Contains the methods that extracts WSDL data.
31
+ class SavonRegistry
32
+ attr_reader :soap_exceptions
33
+ attr_reader :soap_methods
34
+ attr_reader :soap_types
35
+
36
+ # Initializes the instance.
37
+ # Args:
38
+ # wsdl - string containing wsdl to parse.
39
+ def initialize(wsdl)
40
+ do_process_wsdl(wsdl)
41
+ end
42
+
43
+ private
44
+
45
+ # Generate code for given Savon client.
46
+ def do_process_wsdl(wsdl)
47
+ doc = REXML::Document.new(wsdl.to_xml)
48
+ @raw_types = []
49
+ @soap_exceptions = extract_exceptions(doc)
50
+ sort_exceptions()
51
+ @soap_methods = extract_methods(doc)
52
+ @soap_types = extract_types(doc)
53
+ end
54
+
55
+ # Extracts exceptions list with basic properties from ComplexTypes list.
56
+ def extract_exceptions(doc)
57
+ exceptions = []
58
+ ctypes = get_complex_types(doc)
59
+ raw_exceptions = exceptions_from_ctypes(ctypes)
60
+ raw_exceptions.each do |raw_exception|
61
+ exceptions << {:name => raw_exception.attributes['name'],
62
+ :doc => get_element_doc(raw_exception),
63
+ :base => get_element_base(raw_exception),
64
+ :fields => get_element_fields(raw_exception)}
65
+ end
66
+ return exceptions
67
+ end
68
+
69
+ # Extracts ComplexTypes from XML into an array.
70
+ def get_complex_types(doc)
71
+ complex_types = []
72
+ REXML::XPath.each(doc, '//schema/complexType') do |ctype|
73
+ complex_types << ctype
74
+ end
75
+ return complex_types
76
+ end
77
+
78
+ # Helper function to find Exceptions (by name) from all types.
79
+ def exceptions_from_ctypes(ctypes)
80
+ exceptions = []
81
+ ctypes.each do |ctype|
82
+ if ctype.attributes['name'].match('.+Exception$')
83
+ exceptions << ctype
84
+ end
85
+ end
86
+ return exceptions
87
+ end
88
+
89
+ # Extracts methods and parameters from XML.
90
+ def extract_methods(doc)
91
+ methods = []
92
+ iface = REXML::XPath.first(doc, 'descendant::wsdl:portType')
93
+ REXML::XPath.each(iface, 'descendant::wsdl:operation') do |operation|
94
+ methods << {:name => operation.attributes['name'].to_s.snakecase,
95
+ :input => extract_input_parameters(operation, doc),
96
+ :output => extract_output_parameters(operation, doc)}
97
+ # This could be used to include documentation from wsdl.
98
+ # :doc => get_element_doc(operation, 'wsdl')}
99
+ end
100
+ return methods
101
+ end
102
+
103
+ # Extracts input parameters of given method as an array.
104
+ def extract_input_parameters(op_node, doc)
105
+ result = []
106
+ op_name = op_node.attributes['name'].to_s
107
+ doc.each_element_with_attribute('name', op_name, 0,
108
+ '//schema/element') do |method_node|
109
+ seq_node = REXML::XPath.first(method_node, 'complexType/sequence')
110
+ result = get_element_fields(seq_node)
111
+ process_method_field_types(result)
112
+ end
113
+ return result
114
+ end
115
+
116
+ # Extracts output parameter name and fields.
117
+ def extract_output_parameters(op_node, doc)
118
+ output_element = REXML::XPath.first(op_node, 'descendant::wsdl:output')
119
+ output_name = (output_element.nil?) ? nil :
120
+ output_element.attribute('name').to_s
121
+ output_fields = []
122
+ doc.each_element_with_attribute('name', output_name, 0,
123
+ '//schema/element') do |response_node|
124
+ seq_node = REXML::XPath.first(response_node, 'complexType/sequence')
125
+ output_fields = get_element_fields(seq_node)
126
+ process_method_field_types(output_fields)
127
+ end
128
+ result = {:name => output_name.snakecase, :fields => output_fields}
129
+ return result
130
+ end
131
+
132
+ # Checks all fields are of standard type or included in raw_types.
133
+ def process_method_field_types(fields)
134
+ fields.each do |field|
135
+ field_type = field[:type]
136
+ next if STANDARD_TYPES.include?(field_type)
137
+ @raw_types << field_type unless @raw_types.include?(field_type)
138
+ end
139
+ end
140
+
141
+ # Gets element base defined as an attribute in sibling.
142
+ def get_element_base(root)
143
+ base_element = REXML::XPath.first(root, 'complexContent/extension')
144
+ base = (base_element.nil?) ? nil :
145
+ base_element.attribute('base').to_s.gsub(/^.+:/, '')
146
+ return base
147
+ end
148
+
149
+ # Gets element documentation text.
150
+ def get_element_doc(root, namespace = nil)
151
+ key = 'documentation'
152
+ key = "%s:%s" % [namespace, key] if namespace
153
+ doc_element = REXML::XPath.first(root, "descendant::%s" % key)
154
+ doc = (doc_element.nil?) ? '' :
155
+ REXML::Text.unnormalize(doc_element.get_text.to_s)
156
+ return doc
157
+ end
158
+
159
+ # Gets subfields defined as elements under given root.
160
+ def get_element_fields(root)
161
+ fields = []
162
+ REXML::XPath.each(root, 'descendant::element') do |element|
163
+ fields << {:name => element.attribute('name').to_s.snakecase,
164
+ :type => element.attribute('type').to_s.gsub(/^.+:/, ''),
165
+ :min_occurs => attribute_to_int(element.attribute('minOccurs')),
166
+ :max_occurs => attribute_to_int(element.attribute('maxOccurs'))}
167
+ end
168
+ return fields
169
+ end
170
+
171
+ # Extracts definition of all types. If a non standard undefined type is
172
+ # found it process it recursively.
173
+ # Special case for extensions - types with a base class.
174
+ def extract_types(doc)
175
+ types = []
176
+ @raw_types.each do |raw_type|
177
+ doc.each_element_with_attribute('name', raw_type, 0,
178
+ '//schema/complexType') do |type_node|
179
+ type = {:name => raw_type, :fields => []}
180
+ ext_node = REXML::XPath.first(type_node, 'complexContent/extension')
181
+ if ext_node
182
+ base_type = ext_node.attribute('base').to_s.gsub(/^.+:/, '')
183
+ type[:base] = base_type
184
+ @raw_types << base_type unless @raw_types.include?(base_type)
185
+ seq_node = REXML::XPath.first(ext_node, 'sequence')
186
+ fields = get_element_fields(seq_node)
187
+ process_method_field_types(fields)
188
+ type[:fields] += fields
189
+ end
190
+ seq_node = REXML::XPath.first(type_node, 'sequence')
191
+ if seq_node
192
+ fields = get_element_fields(seq_node)
193
+ process_method_field_types(fields)
194
+ type[:fields] += fields
195
+ end
196
+ types << type
197
+ end
198
+ end
199
+ return types
200
+ end
201
+
202
+ # Simple converter for int values.
203
+ def attribute_to_int(attribute)
204
+ return attribute.value.eql?('unbounded') ? nil : attribute.value.to_i
205
+ end
206
+
207
+ # Reorders exceptions so that base ones always come before derived.
208
+ def sort_exceptions()
209
+ @ordered_exceptions = []
210
+ @soap_exceptions.each {|exception| do_include_exception(exception)}
211
+ @soap_exceptions = @ordered_exceptions
212
+ end
213
+
214
+ # Includes exception into ordered_exceptions if not already there and
215
+ # makes sure its base is already included.
216
+ def do_include_exception(exception)
217
+ return if find_exception(exception, @ordered_exceptions)
218
+ if exception[:base].nil?
219
+ @ordered_exceptions.push(exception)
220
+ else
221
+ base = find_exception(exception[:base], @soap_exceptions)
222
+ do_include_exception(base)
223
+ @ordered_exceptions.push(exception)
224
+ end
225
+ end
226
+
227
+ # Finds object (exception) by name attribute in a list.
228
+ def find_exception(exception, list)
229
+ list.each do |e|
230
+ return e if (e.eql?(exception) || e[:name].eql?(exception))
231
+ end
232
+ return nil
233
+ end
234
+ end
235
+ end
236
+ end
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # Author:: api.dklimkin@gmail.com (Danial Klimkin)
4
+ #
5
+ # Copyright:: Copyright 2011, Google Inc. All Rights Reserved.
6
+ #
7
+ # License:: Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
16
+ # implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #
20
+ # Code template for registry generation for Savon backend
21
+
22
+ require 'savon'
23
+ require 'ads_common/build/savon_abstract_generator'
24
+ require 'pp'
25
+
26
+ module AdsCommon
27
+ module Build
28
+ class SavonRegistryGenerator < SavonAbstractGenerator
29
+ REGISTRY_TEMPLATE = %q{<% %>
30
+ #!/usr/bin/ruby
31
+ # This is auto-generated code, changes will be overwritten.
32
+ # Copyright:: Copyright 2011, Google Inc. All Rights Reserved.
33
+ # License:: Licensed under the Apache License,Version 2.0 (the "License").
34
+ #
35
+ # <%= @generator_stamp %>
36
+
37
+ require 'savon'
38
+
39
+ <%= @modules_open_string %>
40
+
41
+ class <%= @service_name %>Registry
42
+ <%= @service_name.upcase %>_METHODS = <%= format_signature(@methods) %>
43
+ <%= @service_name.upcase %>_TYPES = <%= format_signature(@types) %>
44
+
45
+ def self.get_method_signature(method_name)
46
+ return <%= @service_name.upcase %>_METHODS[method_name.to_sym]
47
+ end
48
+
49
+ def self.get_type_signature(type_name)
50
+ return <%= @service_name.upcase %>_TYPES[type_name.to_sym]
51
+ end
52
+ end
53
+ <% @exceptions.each do |exception| %>
54
+ <% array_fields = [] %>
55
+
56
+ <% doc_lines = format_doc(exception[:doc]) %>
57
+ <% doc_lines.each do |doc_line| %>
58
+ # <%= doc_line %>
59
+ <% end %>
60
+ <% base_text = (exception[:base].nil?) ? @default_exception_base : exception[:base] %>
61
+ class <%= exception[:name] %> < <%= base_text %>
62
+ <% exception[:fields].each do |field| %>
63
+ attr_reader :<%= field[:name] %> # <%= field[:type] %>
64
+ <% array_fields << field[:name] if field[:max_occurs].nil? || (field[:max_occurs] > 1) %>
65
+ <% end %>
66
+ <% if !(array_fields.empty?) %>
67
+ def initialize(exception_fault)
68
+ @array_fields = [] if !defined?(@array_fields)
69
+ <% array_fields.each do |field| %>
70
+ @array_fields << '<%= field.to_s %>'
71
+ <% end %>
72
+ super(exception_fault)
73
+ end
74
+ <% end %>
75
+ end
76
+ <% end %>
77
+ <%= @modules_close_string %>
78
+
79
+ }.gsub(/^ /, '')
80
+
81
+ def initialize(args)
82
+ super(args)
83
+ @exceptions = []
84
+ @methods = []
85
+ @types = []
86
+ @default_exception_base = "%s::Errors::ApiException" %
87
+ @module_name.split('::').first
88
+ end
89
+
90
+ def get_code_template()
91
+ REGISTRY_TEMPLATE
92
+ end
93
+
94
+ def add_exceptions(exceptions)
95
+ @exceptions += exceptions
96
+ end
97
+
98
+ def add_methods(methods)
99
+ @methods += methods
100
+ end
101
+
102
+ def add_types(types)
103
+ @types += types
104
+ end
105
+
106
+ private
107
+
108
+ # Multi-line documentation formatter. Used to format text extracted from
109
+ # XML into stripped multi-line text.
110
+ def format_doc(doc)
111
+ res = []
112
+ doc.split(/\n/).each do |line|
113
+ line = line.strip();
114
+ res << line if !(line.empty?)
115
+ end
116
+ return res
117
+ end
118
+
119
+ # Prepares a hash string based on array of hashes passed.
120
+ def format_signature(objects_array)
121
+ objects_hash = get_hash_for_names_array(objects_array)
122
+ return PP.singleline_pp(objects_hash, '')
123
+ end
124
+
125
+ # Converts an array of hashes to a hash based on ":name" fields:
126
+ # [{:name => 'foo', :data => 'bar'}] => {:foo => {:data => 'bar'}}
127
+ def get_hash_for_names_array(input)
128
+ output = {}
129
+ input.each do |e|
130
+ key = e[:name].to_sym
131
+ value = e.dup
132
+ value.delete(:name)
133
+ output[key] = value
134
+ end
135
+ return output
136
+ end
137
+ end
138
+ end
139
+ end