google-ads-common 0.2.0

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.
@@ -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