google-apis-generator 0.1.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.
- 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
|