openapi-sourcetools 0.7.0 → 0.8.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 +4 -4
- data/bin/openapi-addheaders +11 -11
- data/bin/openapi-addparameters +52 -12
- data/bin/openapi-addresponses +11 -12
- data/bin/openapi-addschemas +41 -17
- data/bin/openapi-checkschemas +21 -21
- data/bin/openapi-frequencies +24 -26
- data/bin/openapi-generate +15 -12
- data/bin/openapi-merge +21 -15
- data/bin/openapi-modifypaths +17 -16
- data/bin/openapi-processpaths +16 -27
- data/lib/openapi/sourcetools/apiobjects.rb +191 -0
- data/lib/openapi/sourcetools/common.rb +82 -0
- data/lib/openapi/sourcetools/config.rb +158 -0
- data/lib/openapi/sourcetools/docs.rb +41 -0
- data/lib/{gen.rb → openapi/sourcetools/gen.rb} +38 -13
- data/lib/openapi/sourcetools/generate.rb +96 -0
- data/lib/openapi/sourcetools/helper.rb +93 -0
- data/lib/openapi/sourcetools/loaders.rb +164 -0
- data/lib/openapi/sourcetools/output.rb +83 -0
- data/lib/openapi/sourcetools/securityschemes.rb +268 -0
- data/lib/openapi/sourcetools/task.rb +137 -0
- data/lib/openapi/sourcetools/version.rb +13 -0
- data/lib/openapi/sourcetools.rb +15 -0
- metadata +42 -18
- data/lib/apiobjects.rb +0 -333
- data/lib/common.rb +0 -110
- data/lib/docs.rb +0 -33
- data/lib/generate.rb +0 -90
- data/lib/helper.rb +0 -94
- data/lib/loaders.rb +0 -96
- data/lib/output.rb +0 -58
- data/lib/task.rb +0 -101
@@ -0,0 +1,268 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright © 2024-2025 Ismo Kärkkäinen
|
4
|
+
# Licensed under Universal Permissive License. See LICENSE.txt.
|
5
|
+
|
6
|
+
require_relative 'task'
|
7
|
+
require 'yaml'
|
8
|
+
|
9
|
+
|
10
|
+
module OpenAPISourceTools
|
11
|
+
# Class that contains security scheme objects and what headers
|
12
|
+
# and parameters are added to the request when the scheme is used.
|
13
|
+
class SecuritySchemeInfo
|
14
|
+
include Comparable
|
15
|
+
|
16
|
+
attr_reader :headers, :parameters, :query_parameters, :cookies
|
17
|
+
|
18
|
+
def initialize(security_scheme = {}, scheme_templates = [])
|
19
|
+
@headers = {}
|
20
|
+
@parameters = {}
|
21
|
+
@query_parameters = {}
|
22
|
+
@cookies = {}
|
23
|
+
scheme_templates.each do |template|
|
24
|
+
s = template['scheme']
|
25
|
+
match = true
|
26
|
+
s.each do |k, v|
|
27
|
+
unless v == security_scheme[k]
|
28
|
+
match = false
|
29
|
+
break
|
30
|
+
end
|
31
|
+
end
|
32
|
+
next unless match
|
33
|
+
o = template['output']
|
34
|
+
fill_in(@headers, o['headers'] || {}, security_scheme)
|
35
|
+
fill_in(@parameters, o['parameters'] || {}, security_scheme)
|
36
|
+
fill_in(@query_parameters, o['query_parameters'] || {}, security_scheme)
|
37
|
+
fill_in(@cookies, o['cookies'] || {}, security_scheme)
|
38
|
+
break
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def fill_in(output, source, scheme)
|
43
|
+
source.each do |k, v|
|
44
|
+
if k.start_with?('<') && k.end_with?('>')
|
45
|
+
scheme_key = k[1..-2]
|
46
|
+
scheme_value = scheme[scheme_key]
|
47
|
+
raise "Missing security scheme value for #{scheme_key}" if scheme_value.nil?
|
48
|
+
output[scheme_value] = v
|
49
|
+
else
|
50
|
+
output[k] = v
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def merge!(other)
|
56
|
+
@headers.merge!(other.headers)
|
57
|
+
@parameters.merge!(other.parameters)
|
58
|
+
@query_parameters.merge!(other.query_parameters)
|
59
|
+
@cookies.merge!(other.cookies)
|
60
|
+
end
|
61
|
+
|
62
|
+
def merge(other)
|
63
|
+
out = SecuritySchemeInfo.new({}, [])
|
64
|
+
out.merge!(self)
|
65
|
+
out.merge!(other)
|
66
|
+
out
|
67
|
+
end
|
68
|
+
|
69
|
+
def <=>(other)
|
70
|
+
# Only really interested in equality.
|
71
|
+
@headers <=> other.headers || @parameters <=> other.parameters || @query_parameters <=> other.query_parameters || @cookies <=> other.cookies
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class ScopedSecuritySchemeInfo
|
76
|
+
include Comparable
|
77
|
+
|
78
|
+
attr_reader :ssi, :scopes
|
79
|
+
|
80
|
+
def initialize(ssi, scopes)
|
81
|
+
@ssi = ssi
|
82
|
+
@scopes = scopes
|
83
|
+
end
|
84
|
+
|
85
|
+
def merge(other)
|
86
|
+
@ssi = @ssi.merge(other.ssi)
|
87
|
+
@scopes = @scopes.concat(other.scopes).uniq
|
88
|
+
end
|
89
|
+
|
90
|
+
def <=>(other)
|
91
|
+
# Only really interested in equality.
|
92
|
+
@ssi <=> other.ssi || @scopes <=> other.scopes
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Helper class for dealing with securitySchemes.
|
97
|
+
# Adds :security key to each operation object and to root.
|
98
|
+
# They contain the used securitySchemes in use and the security schemes
|
99
|
+
# in effect for that operation.
|
100
|
+
class SecuritySchemesTask
|
101
|
+
include TaskInterface
|
102
|
+
|
103
|
+
def default_configuration
|
104
|
+
# type apiKey, name, in.
|
105
|
+
# type http, scheme, bearerFormat.
|
106
|
+
# type mutualTLS, beyond the scope of code templates.
|
107
|
+
# type oauth2, flows object.
|
108
|
+
# type openIdConnect, openIdConnectUrl. More for login?
|
109
|
+
YAML.safe_load(%q(---
|
110
|
+
security_schemes:
|
111
|
+
- scheme:
|
112
|
+
type: apiKey
|
113
|
+
in: header
|
114
|
+
output:
|
115
|
+
headers:
|
116
|
+
'<name>': string
|
117
|
+
- scheme:
|
118
|
+
type: apiKey
|
119
|
+
in: query
|
120
|
+
output:
|
121
|
+
parameters:
|
122
|
+
'<name>': string
|
123
|
+
- scheme:
|
124
|
+
type: apiKey
|
125
|
+
in: query
|
126
|
+
output:
|
127
|
+
query_parameters:
|
128
|
+
'<name>': string
|
129
|
+
- scheme:
|
130
|
+
type: apiKey
|
131
|
+
in: cookie
|
132
|
+
output:
|
133
|
+
cookies:
|
134
|
+
'<name>': true
|
135
|
+
- scheme:
|
136
|
+
type: http
|
137
|
+
scheme: basic
|
138
|
+
output:
|
139
|
+
headers:
|
140
|
+
Authorization: string
|
141
|
+
- scheme:
|
142
|
+
type: http
|
143
|
+
scheme: bearer
|
144
|
+
output:
|
145
|
+
headers:
|
146
|
+
Authorization: string
|
147
|
+
- scheme:
|
148
|
+
type: mutualTLS
|
149
|
+
output: {}
|
150
|
+
- scheme:
|
151
|
+
type: oauth2
|
152
|
+
output:
|
153
|
+
headers:
|
154
|
+
Authorization: string
|
155
|
+
- scheme:
|
156
|
+
type: openIdConnect
|
157
|
+
output:
|
158
|
+
headers:
|
159
|
+
Authorization: string
|
160
|
+
))
|
161
|
+
end
|
162
|
+
|
163
|
+
def convert_security_schemes(doc)
|
164
|
+
ss = doc.dig('components', 'securitySchemes')
|
165
|
+
return nil if ss.nil?
|
166
|
+
# Should create unique objects. Different name may lead to same object.
|
167
|
+
out = {}
|
168
|
+
ss.each do |name, security_scheme|
|
169
|
+
out[name] = SecuritySchemeInfo.new(security_scheme)
|
170
|
+
end
|
171
|
+
out
|
172
|
+
end
|
173
|
+
|
174
|
+
def operation_object_security(doc, schemes)
|
175
|
+
# Find all operation objects and security in effect.
|
176
|
+
all_ops = []
|
177
|
+
seen_secs = Set.new
|
178
|
+
root_sec = doc['security'] || []
|
179
|
+
not_method = %w[parameters servers summary description]
|
180
|
+
doc['paths'].each_value do |path_item|
|
181
|
+
path_sec = path_item['security'] || root_sec
|
182
|
+
path_item.each do |method, op|
|
183
|
+
next if not_method.include?(method)
|
184
|
+
op_sec = op['security'] || path_sec
|
185
|
+
all_ops.push([ op, op_sec ])
|
186
|
+
op_sec.each do |security_requirement|
|
187
|
+
names = security_requirement.keys
|
188
|
+
seen_secs.merge(names)
|
189
|
+
if names.empty? && !schemes.key?('')
|
190
|
+
schemes[''] = SecuritySchemeInfo.new
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
# Check that all seen secs names have a scheme. Report all in one place.
|
196
|
+
missing = false
|
197
|
+
seen_secs.to_a.sort!.each do |name|
|
198
|
+
unless schemes.key?(name)
|
199
|
+
missing = true
|
200
|
+
warn("#/components/securitySchemes is missing: #{name}")
|
201
|
+
end
|
202
|
+
end
|
203
|
+
return 1 if missing
|
204
|
+
# Now we know all individual parts are available.
|
205
|
+
# Map security arrays of objects to arrays of arrays.
|
206
|
+
all_scopeds = [] # For having just one instance for unique data combination.
|
207
|
+
all_ops.each do |pair|
|
208
|
+
sec = pair.second.map do |sec_req|
|
209
|
+
keys = sec_req.keys.sort!
|
210
|
+
values = keys.map { |name| sec_req[name].sort! }
|
211
|
+
if keys.empty?
|
212
|
+
keys = [ '' ]
|
213
|
+
values = [ [] ]
|
214
|
+
end
|
215
|
+
s3is = []
|
216
|
+
keys.size.times do |idx|
|
217
|
+
name = keys[idx]
|
218
|
+
scopes = values[idx]
|
219
|
+
s3i = ScopedSecuritySchemeInfo.new(schemes[name], scopes)
|
220
|
+
idx = all_scopeds.index(s3i)
|
221
|
+
if idx.nil?
|
222
|
+
all_scopeds.push(s3i)
|
223
|
+
else
|
224
|
+
s3i = all_scopeds(idx) # Use the same instance everywhere.
|
225
|
+
end
|
226
|
+
s3is.push(s3i)
|
227
|
+
end
|
228
|
+
s3is
|
229
|
+
end
|
230
|
+
pair.first[:security] = sec # Arrays of ScopedSecuritySchemeInfo.
|
231
|
+
# When individual objects are not needed, provide merged together items.
|
232
|
+
all_merged = []
|
233
|
+
pair.first[:security_merged] = sec.map do |s3is| # ScopedSecuritySchemeInfos.
|
234
|
+
m = s3is.first
|
235
|
+
s3is[1..].each do |s3i|
|
236
|
+
m = m.merge(s3i)
|
237
|
+
end
|
238
|
+
idx = all_merged.index(m)
|
239
|
+
if idx.nil?
|
240
|
+
all_merged.push(m)
|
241
|
+
else
|
242
|
+
m = all_merged[idx] # Use the same instance everywhere.
|
243
|
+
end
|
244
|
+
m
|
245
|
+
end
|
246
|
+
end
|
247
|
+
all_merged.map(&:ssi).uniq.sort!
|
248
|
+
end
|
249
|
+
|
250
|
+
def generate(_context_binding)
|
251
|
+
# Get security_schemes from config, append defaults to it.
|
252
|
+
scheme_templates = gen.configuration['security_schemes'] || []
|
253
|
+
scheme_templates.concat(default_configuration['security_schemes'])
|
254
|
+
simple_schemes = convert_security_schemes(Gen.doc)
|
255
|
+
# For all operation objects, add :security array with all used schemes.
|
256
|
+
merged_schemes = operation_object_security(Gen.doc, simple_schemes || {})
|
257
|
+
return merged_schemes if merged_schemes.is_a?(Integer)
|
258
|
+
Gen.doc[:securitySchemes] = simple_schemes unless simple_schemes.nil?
|
259
|
+
Gen.doc[:securitySchemes_merged] = merged_schemes unless merged_schemes.empty?
|
260
|
+
end
|
261
|
+
|
262
|
+
def discard
|
263
|
+
true
|
264
|
+
end
|
265
|
+
|
266
|
+
COMPONENTS = '#/components/'
|
267
|
+
end
|
268
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright © 2024-2025 Ismo Kärkkäinen
|
4
|
+
# Licensed under Universal Permissive License. See LICENSE.txt.
|
5
|
+
|
6
|
+
require_relative 'common'
|
7
|
+
require 'erb'
|
8
|
+
|
9
|
+
|
10
|
+
module OpenAPISourceTools
|
11
|
+
|
12
|
+
# Required interface for tasks, with default implementation for some methods.
|
13
|
+
module TaskInterface
|
14
|
+
def generate(context_binding)
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
|
18
|
+
def output_name
|
19
|
+
raise NotImplementedError
|
20
|
+
end
|
21
|
+
|
22
|
+
def discard
|
23
|
+
false
|
24
|
+
end
|
25
|
+
|
26
|
+
def executable
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
def system
|
31
|
+
false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Loads template or takes it as argument. Renders template using ERB.
|
36
|
+
class Task
|
37
|
+
include TaskInterface
|
38
|
+
|
39
|
+
attr_reader :src, :template, :template_name
|
40
|
+
attr_accessor :name, :executable, :discard, :x
|
41
|
+
|
42
|
+
def initialize(src, template, template_name)
|
43
|
+
@src = src
|
44
|
+
@template = template
|
45
|
+
@template_name = template_name
|
46
|
+
if @template.nil?
|
47
|
+
raise ArgumentError, 'template_name or template must be given' if @template_name.nil?
|
48
|
+
begin
|
49
|
+
@template = File.read(@template_name)
|
50
|
+
rescue Errno::ENOENT
|
51
|
+
raise StandardError, "Could not load #{@template_name}"
|
52
|
+
rescue StandardError => e
|
53
|
+
raise StandardError, "#{e}\nFailed to read #{@template_name}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
@name = nil
|
57
|
+
@executable = false
|
58
|
+
@discard = false
|
59
|
+
@x = nil
|
60
|
+
end
|
61
|
+
|
62
|
+
# If this is overridden to perform some processing but not to produce output,
|
63
|
+
# set @discard = true and return value will be ignored. No other methods are
|
64
|
+
# called in that case.
|
65
|
+
def internal_generate(context_binding)
|
66
|
+
ERB.new(@template).result(context_binding)
|
67
|
+
end
|
68
|
+
|
69
|
+
# You can override this instead of internal_generate if you do not need the
|
70
|
+
# exception handling.
|
71
|
+
def generate(context_binding)
|
72
|
+
n = @template_name.nil? ? '' : "#{@template_name} "
|
73
|
+
internal_generate(context_binding)
|
74
|
+
rescue SyntaxError => e
|
75
|
+
OpenAPISourceTools::Common.aargh("Template #{n}syntax error: #{e.full_message}", 5)
|
76
|
+
rescue Exception => e # Some unexpected error.
|
77
|
+
OpenAPISourceTools::Common.aargh("Template #{n}error: #{e.full_message}", 6)
|
78
|
+
end
|
79
|
+
|
80
|
+
# This is only called when generate produced output that is not discarded.
|
81
|
+
def output_name
|
82
|
+
return @name unless @name.nil?
|
83
|
+
# Using template name may show where name assignment is missing.
|
84
|
+
# Name assignment may also be missing in the task creation stage.
|
85
|
+
return File.basename(@template_name) unless @template_name.nil?
|
86
|
+
nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# A task that provides contents to be written to a named file.
|
91
|
+
# Optionaly the file may be made executable.
|
92
|
+
class WriteTask
|
93
|
+
include TaskInterface
|
94
|
+
|
95
|
+
attr_reader :name, :contents, :executable
|
96
|
+
|
97
|
+
def initialize(name, contents, executable = false)
|
98
|
+
raise ArgumentError, 'name and contents must be given' if name.nil? || contents.nil?
|
99
|
+
@name = name
|
100
|
+
@contents = contents
|
101
|
+
@executable = executable
|
102
|
+
end
|
103
|
+
|
104
|
+
def generate(_context_binding)
|
105
|
+
@contents
|
106
|
+
end
|
107
|
+
|
108
|
+
def output_name
|
109
|
+
@name
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Sets Gen.x to empty hash. Inserted after gem tasks.
|
114
|
+
class RestoreProcessorStorage
|
115
|
+
include TaskInterface
|
116
|
+
|
117
|
+
attr_accessor :x # Allows setting the current value when setup code finishes.
|
118
|
+
|
119
|
+
# Sets initial default value.
|
120
|
+
def initialize(x = {})
|
121
|
+
@x = x
|
122
|
+
Gen.x = @x
|
123
|
+
end
|
124
|
+
|
125
|
+
def generate(_context_binding)
|
126
|
+
Gen.x = @x # Restore whatever was current when setup code finished.
|
127
|
+
end
|
128
|
+
|
129
|
+
def discard
|
130
|
+
true
|
131
|
+
end
|
132
|
+
|
133
|
+
def system
|
134
|
+
true
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright © 2025 Ismo Kärkkäinen
|
4
|
+
# Licensed under Universal Permissive License. See LICENSE.txt.
|
5
|
+
|
6
|
+
module OpenAPISourceTools
|
7
|
+
NAME = 'openapi-sourcetools'
|
8
|
+
VERSION = '0.8.0'
|
9
|
+
|
10
|
+
def self.info(separator = ': ')
|
11
|
+
"#{NAME}#{separator}#{VERSION}"
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright © 2024-2025 Ismo Kärkkäinen
|
4
|
+
# Licensed under Universal Permissive License. See LICENSE.txt.
|
5
|
+
|
6
|
+
require_relative 'sourcetools/task'
|
7
|
+
require_relative 'sourcetools/config'
|
8
|
+
require_relative 'sourcetools/version'
|
9
|
+
# Other modules or classes are exposed via Gen attributes as class instances as needed.
|
10
|
+
# Docs is only needed for run-time storage of whatever loaders can handle.
|
11
|
+
# Loaders array is exposed and can be added to at run-time.
|
12
|
+
# Helper instance is accessible via Gen.h.
|
13
|
+
# Output is exposed via Gen.o and Gen.output. Note that if you assign to one,
|
14
|
+
# the other will not change.
|
15
|
+
# The rest are for internal implementation.
|
metadata
CHANGED
@@ -1,20 +1,40 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: openapi-sourcetools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ismo Kärkkäinen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
12
|
-
dependencies:
|
13
|
-
|
11
|
+
date: 2025-01-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: deep_merge
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.2'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.2.2
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.2'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.2.2
|
33
|
+
description: |-
|
34
|
+
Tools for handling API specification in OpenAPI format.
|
35
|
+
Programs to replace of duplicate definitions with references. Other checks.
|
14
36
|
|
15
|
-
|
16
|
-
duplicate definitions with references. Other checks. Does not validate
|
17
|
-
the document against OpenAPI format specification.
|
37
|
+
Does not validate the document against OpenAPI format specification.
|
18
38
|
email: ismokarkkainen@icloud.com
|
19
39
|
executables:
|
20
40
|
- openapi-addheaders
|
@@ -41,15 +61,19 @@ files:
|
|
41
61
|
- bin/openapi-merge
|
42
62
|
- bin/openapi-modifypaths
|
43
63
|
- bin/openapi-processpaths
|
44
|
-
- lib/
|
45
|
-
- lib/
|
46
|
-
- lib/
|
47
|
-
- lib/
|
48
|
-
- lib/
|
49
|
-
- lib/
|
50
|
-
- lib/
|
51
|
-
- lib/
|
52
|
-
- lib/
|
64
|
+
- lib/openapi/sourcetools.rb
|
65
|
+
- lib/openapi/sourcetools/apiobjects.rb
|
66
|
+
- lib/openapi/sourcetools/common.rb
|
67
|
+
- lib/openapi/sourcetools/config.rb
|
68
|
+
- lib/openapi/sourcetools/docs.rb
|
69
|
+
- lib/openapi/sourcetools/gen.rb
|
70
|
+
- lib/openapi/sourcetools/generate.rb
|
71
|
+
- lib/openapi/sourcetools/helper.rb
|
72
|
+
- lib/openapi/sourcetools/loaders.rb
|
73
|
+
- lib/openapi/sourcetools/output.rb
|
74
|
+
- lib/openapi/sourcetools/securityschemes.rb
|
75
|
+
- lib/openapi/sourcetools/task.rb
|
76
|
+
- lib/openapi/sourcetools/version.rb
|
53
77
|
homepage: https://xn--ismo-krkkinen-gfbd.fi/openapi-sourcetools/index.html
|
54
78
|
licenses:
|
55
79
|
- UPL-1.0
|
@@ -63,14 +87,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
63
87
|
requirements:
|
64
88
|
- - ">="
|
65
89
|
- !ruby/object:Gem::Version
|
66
|
-
version: 3.
|
90
|
+
version: 3.2.5
|
67
91
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
92
|
requirements:
|
69
93
|
- - ">="
|
70
94
|
- !ruby/object:Gem::Version
|
71
95
|
version: '0'
|
72
96
|
requirements: []
|
73
|
-
rubygems_version: 3.
|
97
|
+
rubygems_version: 3.4.19
|
74
98
|
signing_key:
|
75
99
|
specification_version: 4
|
76
100
|
summary: Tools for creating source code from API specification.
|