google-apis-generator 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.yardopts +13 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.md +202 -0
- data/OVERVIEW.md +22 -0
- data/bin/generate-api +209 -0
- data/lib/google/apis/generator.rb +99 -0
- data/lib/google/apis/generator/annotator.rb +339 -0
- data/lib/google/apis/generator/helpers.rb +78 -0
- data/lib/google/apis/generator/model.rb +195 -0
- data/lib/google/apis/generator/template.rb +124 -0
- data/lib/google/apis/generator/updater.rb +139 -0
- data/lib/google/apis/generator/version.rb +21 -0
- metadata +129 -0
@@ -0,0 +1,195 @@
|
|
1
|
+
# Copyright 2015 Google Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'active_support/inflector'
|
16
|
+
require 'google/apis/discovery_v1'
|
17
|
+
|
18
|
+
# Extend the discovery API classes with additional data needed to make
|
19
|
+
# code generation produce better results
|
20
|
+
module Google
|
21
|
+
module Apis
|
22
|
+
module DiscoveryV1
|
23
|
+
TYPE_MAP = {
|
24
|
+
'string' => 'String',
|
25
|
+
'boolean' => 'Boolean',
|
26
|
+
'number' => 'Float',
|
27
|
+
'integer' => 'Fixnum',
|
28
|
+
'any' => 'Object'
|
29
|
+
}
|
30
|
+
|
31
|
+
class JsonSchema
|
32
|
+
attr_accessor :name
|
33
|
+
attr_accessor :generated_name
|
34
|
+
attr_accessor :generated_class_name
|
35
|
+
attr_accessor :base_ref
|
36
|
+
attr_accessor :parent
|
37
|
+
attr_accessor :discriminant
|
38
|
+
attr_accessor :discriminant_value
|
39
|
+
attr_accessor :path
|
40
|
+
|
41
|
+
def properties
|
42
|
+
Hash[(@properties || {}).sort]
|
43
|
+
end
|
44
|
+
|
45
|
+
def qualified_name
|
46
|
+
parent.qualified_name + '::' + generated_class_name
|
47
|
+
end
|
48
|
+
|
49
|
+
def generated_type
|
50
|
+
case type
|
51
|
+
when 'string', 'boolean', 'number', 'integer', 'any'
|
52
|
+
return 'DateTime' if format == 'date-time'
|
53
|
+
return 'Date' if format == 'date'
|
54
|
+
return 'Fixnum' if format == 'int64'
|
55
|
+
return 'Fixnum' if format == 'uint64'
|
56
|
+
return TYPE_MAP[type]
|
57
|
+
when 'array'
|
58
|
+
if items == self
|
59
|
+
return sprintf('Array<%s>', qualified_name)
|
60
|
+
end
|
61
|
+
return sprintf('Array<%s>', items.generated_type)
|
62
|
+
when 'hash'
|
63
|
+
if additional_properties == self
|
64
|
+
return sprintf('Hash<String,%s>', qualified_name)
|
65
|
+
end
|
66
|
+
return sprintf('Hash<String,%s>', additional_properties.generated_type)
|
67
|
+
when 'object'
|
68
|
+
return qualified_name
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class RestMethod
|
74
|
+
attr_accessor :generated_name
|
75
|
+
attr_accessor :parent
|
76
|
+
|
77
|
+
def parameters
|
78
|
+
Hash[(@parameters || {}).sort]
|
79
|
+
end
|
80
|
+
|
81
|
+
def path_parameters
|
82
|
+
return [] if parameter_order.nil? || parameters.nil?
|
83
|
+
parameter_order.map { |name| parameters[name] }.select { |param| param.location == 'path' }
|
84
|
+
end
|
85
|
+
|
86
|
+
def query_parameters
|
87
|
+
return [] if parameters.nil?
|
88
|
+
parameters.values.select { |param| param.location == 'query' }
|
89
|
+
end
|
90
|
+
|
91
|
+
def required_parameters
|
92
|
+
return [] if parameter_order.nil? || parameters.nil?
|
93
|
+
parameter_order.map { |name| parameters[name] }.select { |param| param.location == 'path' || param.required }
|
94
|
+
end
|
95
|
+
|
96
|
+
def optional_query_parameters
|
97
|
+
query_parameters.select { |param| param.required != true }
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
class RestResource
|
103
|
+
attr_accessor :parent
|
104
|
+
|
105
|
+
def api_methods
|
106
|
+
Hash[(@api_methods || {}).sort]
|
107
|
+
end
|
108
|
+
|
109
|
+
def resources
|
110
|
+
Hash[(@resources || {}).sort]
|
111
|
+
end
|
112
|
+
|
113
|
+
def all_methods
|
114
|
+
m = []
|
115
|
+
m << api_methods.values unless api_methods.nil?
|
116
|
+
m << resources.map { |_k, r| r.all_methods } unless resources.nil?
|
117
|
+
m.flatten
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class RestDescription
|
122
|
+
attr_accessor :force_alt_json
|
123
|
+
alias_method :force_alt_json?, :force_alt_json
|
124
|
+
|
125
|
+
# Don't expose these in the API directly.
|
126
|
+
PARAMETER_BLACKLIST = %w(alt access_token bearer_token oauth_token pp prettyPrint
|
127
|
+
$.xgafv callback upload_protocol uploadType)
|
128
|
+
|
129
|
+
def version
|
130
|
+
ActiveSupport::Inflector.camelize(@version.gsub(/\W/, '-')).gsub(/-/, '_')
|
131
|
+
end
|
132
|
+
|
133
|
+
def name
|
134
|
+
ActiveSupport::Inflector.camelize(@name)
|
135
|
+
end
|
136
|
+
|
137
|
+
def module_name
|
138
|
+
name + version
|
139
|
+
end
|
140
|
+
|
141
|
+
def qualified_name
|
142
|
+
sprintf('Google::Apis::%s', module_name)
|
143
|
+
end
|
144
|
+
|
145
|
+
def base_path
|
146
|
+
ActiveSupport::Inflector.underscore(qualified_name)
|
147
|
+
end
|
148
|
+
|
149
|
+
def gem_name
|
150
|
+
base_path.tr("/", "-")
|
151
|
+
end
|
152
|
+
|
153
|
+
def service_name
|
154
|
+
class_name = (canonical_name || name).gsub(/\W/, '')
|
155
|
+
ActiveSupport::Inflector.camelize(sprintf('%sService', class_name))
|
156
|
+
end
|
157
|
+
|
158
|
+
def api_methods
|
159
|
+
Hash[(@api_methods || {}).sort]
|
160
|
+
end
|
161
|
+
|
162
|
+
def resources
|
163
|
+
Hash[(@resources || {}).sort]
|
164
|
+
end
|
165
|
+
|
166
|
+
def all_methods
|
167
|
+
m = []
|
168
|
+
m << api_methods.values unless api_methods.nil?
|
169
|
+
m << resources.map { |_k, r| r.all_methods } unless resources.nil?
|
170
|
+
m.flatten
|
171
|
+
end
|
172
|
+
|
173
|
+
def parameters
|
174
|
+
Hash[(@parameters || {}).sort].delete_if { |k, _v| PARAMETER_BLACKLIST.include?(k) }
|
175
|
+
end
|
176
|
+
|
177
|
+
def schemas
|
178
|
+
Hash[(@schemas || {}).sort]
|
179
|
+
end
|
180
|
+
|
181
|
+
class Auth
|
182
|
+
class Oauth2
|
183
|
+
class Scope
|
184
|
+
attr_accessor :constant
|
185
|
+
end
|
186
|
+
|
187
|
+
def scopes
|
188
|
+
Hash[(@scopes || {}).sort]
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# Copyright 2015 Google Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'active_support/inflector'
|
16
|
+
require 'erb'
|
17
|
+
require 'ostruct'
|
18
|
+
|
19
|
+
module Google
|
20
|
+
module Apis
|
21
|
+
# @private
|
22
|
+
class Generator
|
23
|
+
# Directory containing ERB templates
|
24
|
+
TEMPLATE_DIR = File.expand_path('../templates', __FILE__)
|
25
|
+
|
26
|
+
# Helpers used in ERB templates
|
27
|
+
module TemplateHelpers
|
28
|
+
# Get the include path for a ruby module/class
|
29
|
+
#
|
30
|
+
# @param [String] module_name
|
31
|
+
# Fully qualified module/class name
|
32
|
+
# @return [String]
|
33
|
+
# Path to file
|
34
|
+
def to_path(module_name)
|
35
|
+
ActiveSupport::Inflector.underscore(module_name)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Render a block comment
|
39
|
+
#
|
40
|
+
# @param [String] str
|
41
|
+
# Comment string
|
42
|
+
# @param [Fixnum] spaces_before
|
43
|
+
# Number of spaces to indent the comment hash
|
44
|
+
# @param [Fixnum] spaces_after
|
45
|
+
# Number of spaces to indent after the comment hash for subsequent lines
|
46
|
+
# @return [String] formatted comment
|
47
|
+
def block_comment(str, spaces_before = 0, spaces_after = 0)
|
48
|
+
return '' if str.nil?
|
49
|
+
pre = ' ' * spaces_before
|
50
|
+
post = ' ' * spaces_after
|
51
|
+
lines = str.gsub(/([{}])/, '`').scan(/.{1,78}(?:\W|$)/).map(&:strip)
|
52
|
+
lines.join("\n" + pre + '#' + post)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Indent a block of text
|
56
|
+
#
|
57
|
+
# @param [String] str
|
58
|
+
# Content to indent
|
59
|
+
# @param [Fixnum] spaces
|
60
|
+
# Number of spaces to indent
|
61
|
+
# @return [String] formatted content
|
62
|
+
def indent(str, spaces)
|
63
|
+
pre = ' ' * spaces
|
64
|
+
str = pre + str.split(/\n/).join("\n" + pre) + "\n"
|
65
|
+
return str unless str.strip.empty?
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
|
69
|
+
# Include a partial inside a template.
|
70
|
+
#
|
71
|
+
# @private
|
72
|
+
# @param [String] partial
|
73
|
+
# Name of the template
|
74
|
+
# @param [Hash] context
|
75
|
+
# Context used to render
|
76
|
+
# @return [String] rendered content
|
77
|
+
def include(partial, context)
|
78
|
+
template = Template.new(sprintf('_%s.tmpl', partial))
|
79
|
+
template.render(context)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Holds local vars/helpers for template rendering
|
84
|
+
class Context < OpenStruct
|
85
|
+
include TemplateHelpers
|
86
|
+
|
87
|
+
# Get the context for ERB evaluation
|
88
|
+
# @return [Binding]
|
89
|
+
def to_binding
|
90
|
+
binding
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# ERB template for the code generator
|
95
|
+
class Template
|
96
|
+
# Loads a template from the template dir. Automatically
|
97
|
+
# appends the .tmpl suffix
|
98
|
+
#
|
99
|
+
# @param [String] template_name
|
100
|
+
# Name of the template file
|
101
|
+
def self.load(template_name)
|
102
|
+
Template.new(sprintf('%s.tmpl', template_name))
|
103
|
+
end
|
104
|
+
|
105
|
+
# @param [String] template_name
|
106
|
+
# Name of the template file
|
107
|
+
def initialize(template_name)
|
108
|
+
file = File.join(TEMPLATE_DIR, template_name)
|
109
|
+
@erb = ERB.new(File.read(file), nil, '-')
|
110
|
+
end
|
111
|
+
|
112
|
+
# Render the template
|
113
|
+
#
|
114
|
+
# @param [Hash] context
|
115
|
+
# Variables to set when rendering the template
|
116
|
+
# @return [String] rendered template
|
117
|
+
def render(context)
|
118
|
+
ctx = Context.new(context)
|
119
|
+
@erb.result(ctx.to_binding)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# Copyright 2020 Google LLC
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require "gems"
|
16
|
+
|
17
|
+
module Google
|
18
|
+
module Apis
|
19
|
+
class Generator
|
20
|
+
# Matches generated files against current files and decides what to update
|
21
|
+
# @private
|
22
|
+
class Updater
|
23
|
+
def initialize
|
24
|
+
@gems_client = nil
|
25
|
+
@current_rubygems_versions = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
def analyze(base_dir, generator_result)
|
29
|
+
modified_files = {}
|
30
|
+
version_content = changelog_content = nil
|
31
|
+
generator_result.files.each do |path, content|
|
32
|
+
full_path = File.join(base_dir, path)
|
33
|
+
old_content = File.binread(full_path) if File.readable?(full_path)
|
34
|
+
if path == generator_result.version_path
|
35
|
+
version_content = old_content || content
|
36
|
+
elsif path == generator_result.changelog_path
|
37
|
+
changelog_content = old_content || content
|
38
|
+
else
|
39
|
+
modified_files[path] = content unless content == old_content
|
40
|
+
end
|
41
|
+
end
|
42
|
+
unless modified_files.empty?
|
43
|
+
desired_gem_version = next_rubygems_version(generator_result.gem_name)
|
44
|
+
version_content, generator_version_change, revision_change =
|
45
|
+
update_version_content(version_content, desired_gem_version, generator_result.revision)
|
46
|
+
changelog_content = update_changelog_content(changelog_content, desired_gem_version, generator_version_change, revision_change)
|
47
|
+
modified_files[generator_result.version_path] = version_content
|
48
|
+
modified_files[generator_result.changelog_path] = changelog_content
|
49
|
+
end
|
50
|
+
modified_files
|
51
|
+
end
|
52
|
+
|
53
|
+
# @private
|
54
|
+
def gems_client
|
55
|
+
@gems_client ||= Gems::Client.new
|
56
|
+
end
|
57
|
+
|
58
|
+
# @private
|
59
|
+
def current_rubygems_version(gem_name)
|
60
|
+
@current_rubygems_versions[gem_name] ||= begin
|
61
|
+
gems_client.info(gem_name)["version"]
|
62
|
+
rescue Gems::NotFound
|
63
|
+
"0.0.0"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# @private
|
68
|
+
def next_rubygems_version(gem_name)
|
69
|
+
major, minor = current_rubygems_version(gem_name).split(".")
|
70
|
+
"#{major.to_i}.#{minor.to_i + 1}.0"
|
71
|
+
end
|
72
|
+
|
73
|
+
# @private
|
74
|
+
def update_version_content(content, desired_gem_version, new_revision)
|
75
|
+
generator_version_change = revision_change = nil
|
76
|
+
modified_content = content.dup
|
77
|
+
modified_content.sub!(/GEM_VERSION = "([\w\.]*)"/) do |*|
|
78
|
+
"GEM_VERSION = \"#{desired_gem_version}\""
|
79
|
+
end or raise "gem_version.rb is missing GEM_VERSION"
|
80
|
+
modified_content.sub!(/GENERATOR_VERSION = "([\w\.]*)"/) do |*|
|
81
|
+
generator_version_change = Generator::VERSION unless Regexp.last_match[1] == Generator::VERSION
|
82
|
+
"GENERATOR_VERSION = \"#{Generator::VERSION}\""
|
83
|
+
end or raise "gem_version.rb is missing GENERATOR_VERSION"
|
84
|
+
modified_content.sub!(/REVISION = "([\w\.]*)"/) do |*|
|
85
|
+
revision_change = new_revision unless Regexp.last_match[1] == new_revision
|
86
|
+
"REVISION = \"#{new_revision}\""
|
87
|
+
end or raise "gem_version.rb is missing REVISION"
|
88
|
+
[modified_content, generator_version_change, revision_change]
|
89
|
+
end
|
90
|
+
|
91
|
+
# @private
|
92
|
+
def update_changelog_content(content, desired_gem_version, generator_version_change, revision_change)
|
93
|
+
lines = parse_existing_changelog_entry(content, desired_gem_version)
|
94
|
+
modify_changelog_lines(lines, generator_version_change, revision_change)
|
95
|
+
entry = assemble_changelog_entry(lines, desired_gem_version)
|
96
|
+
replace_changelog_entry(content, desired_gem_version, entry)
|
97
|
+
end
|
98
|
+
|
99
|
+
# @private
|
100
|
+
def parse_existing_changelog_entry(content, desired_gem_version)
|
101
|
+
quoted_gem_version = Regexp.quote(desired_gem_version)
|
102
|
+
match = /\n+### v#{quoted_gem_version} \([\d-]+\)\n+((?:[^#][^\n]*\n+)*)(?=#|$)/.match content
|
103
|
+
return [] unless match
|
104
|
+
match[1].split("\n")
|
105
|
+
end
|
106
|
+
|
107
|
+
# @private
|
108
|
+
def modify_changelog_lines(lines, generator_version_change, revision_change)
|
109
|
+
if generator_version_change
|
110
|
+
lines.reject! { |line| line =~ /^\* Regenerated using generator version \d[\w\.]+/ }
|
111
|
+
lines.unshift("* Regenerated using generator version #{generator_version_change}")
|
112
|
+
end
|
113
|
+
if revision_change
|
114
|
+
lines.reject! { |line| line =~ /^\* Regenerated from discovery document revision \d+/ }
|
115
|
+
lines.unshift("* Regenerated from discovery document revision #{revision_change}")
|
116
|
+
end
|
117
|
+
lines << "* Unspecified changes" if lines.empty?
|
118
|
+
end
|
119
|
+
|
120
|
+
# @private
|
121
|
+
def assemble_changelog_entry(lines, desired_gem_version)
|
122
|
+
entry_lines = lines.join("\n")
|
123
|
+
date = Time.now.strftime("%Y-%m-%d")
|
124
|
+
"\n\n### v#{desired_gem_version} (#{date})\n\n#{entry_lines}\n\n"
|
125
|
+
end
|
126
|
+
|
127
|
+
# @private
|
128
|
+
def replace_changelog_entry(content, desired_gem_version, entry)
|
129
|
+
quoted_gem_version = Regexp.quote(desired_gem_version)
|
130
|
+
modified_content = content.dup
|
131
|
+
modified_content.sub!(/\n+### v#{quoted_gem_version} \([\d-]+\)\n+(?:[^#][^\n]*\n+)*(?=#|$)/, entry) or
|
132
|
+
modified_content.sub!(/^(\n*# [^\n]+)\n+(?=#|$)/, "\\1#{entry}") or
|
133
|
+
raise "CHANGELOG doesn't seem to have the expected header"
|
134
|
+
modified_content
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|