openapi-sourcetools 0.4.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/LICENSE.txt +17 -0
- data/bin/openapi-frequencies +105 -0
- data/bin/openapi-generatecode +128 -0
- data/bin/openapi-merge +171 -0
- data/bin/openapi-processpaths +111 -0
- data/lib/common.rb +83 -0
- metadata +54 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a91c5ab72a092af55c0caae79470cc3e7d4b7567547f48be039e0c7c3b957d3e
|
4
|
+
data.tar.gz: 02aa4b9a9dd541b2fbce410fb71ecacee7cea40621327dee907c08253688d830
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b44257770c3112bd7441f7ef70e6e4ff4930a55d99501fb58c77e5f5114489b03bb8c1c092ab44445c73719d3afc5fdd6d7bf389f0d28d0b38d06a631a88a521
|
7
|
+
data.tar.gz: fd596ef20c71daa9d9af1f8b44842369ae11e6910d2a96c9b7e0d795e5f351a4fab1d446233e1b6731b5af13edb9a88ab6702842412c1e1071c92d02a6e84d20
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
Copyright (c) 2019 Ismo Kärkkäinen
|
2
|
+
|
3
|
+
The Universal Permissive License (UPL), Version 1.0
|
4
|
+
|
5
|
+
Subject to the condition set forth below, permission is hereby granted to any person obtaining a copy of this software, associated documentation and/or data (collectively the "Software"), free of charge and under any and all copyright rights in the Software, and any and all patent rights owned or freely licensable by each licensor hereunder covering either (i) the unmodified Software as contributed to or provided by such licensor, or (ii) the Larger Works (as defined below), to deal in both
|
6
|
+
|
7
|
+
(a) the Software, and
|
8
|
+
|
9
|
+
(b) any piece of software and/or hardware listed in the lrgrwrks.txt file if one is included with the Software (each a “Larger Work” to which the Software is contributed by such licensors),
|
10
|
+
|
11
|
+
without restriction, including without limitation the rights to copy, create derivative works of, display, perform, and distribute the Software and make, use, sell, offer for sale, import, export, have made, and have sold the Software and the Larger Work(s), and to sublicense the foregoing rights on either these or other terms.
|
12
|
+
|
13
|
+
This license is subject to the following condition:
|
14
|
+
|
15
|
+
The above copyright notice and either this complete permission notice or at a minimum a reference to the UPL must be included in all copies or substantial portions of the Software.
|
16
|
+
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,105 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Copyright © 2021 Ismo Kärkkäinen
|
5
|
+
# Licensed under Universal Permissive License. See LICENSE.txt.
|
6
|
+
|
7
|
+
require '../lib/common.rb'
|
8
|
+
require 'optparse'
|
9
|
+
require 'yaml'
|
10
|
+
require 'json'
|
11
|
+
|
12
|
+
|
13
|
+
default_env(:in, '')
|
14
|
+
default_env(:out, '')
|
15
|
+
default_env(:format, 'YAML')
|
16
|
+
default_env(:paths, '')
|
17
|
+
|
18
|
+
parser = OptionParser.new do |opts|
|
19
|
+
opts.summary_indent = ' '
|
20
|
+
opts.summary_width = 24
|
21
|
+
opts.banner = 'Usage: openapi-frequencies [options]'
|
22
|
+
opts.separator ''
|
23
|
+
opts.separator 'Options (equivalent environment variable and value in parentheses):'
|
24
|
+
opts.on('-i', '--input FILE', 'Read source from FILE, not stdin (IN=FILE).') do |f|
|
25
|
+
env(:in, f)
|
26
|
+
end
|
27
|
+
opts.on('-o', '--output FILE', 'Output result to FILE, not stdout (OUT=FILE).') do |f|
|
28
|
+
env(:out, f)
|
29
|
+
end
|
30
|
+
opts.on('-p', '--paths FILE', 'Path log file name (PATHS=FILE).') do |f|
|
31
|
+
env(:paths, f)
|
32
|
+
end
|
33
|
+
opts.on('--yaml', 'Output format is YAML (default) (FORMAT=YAML).') do
|
34
|
+
env(:format, 'YAML')
|
35
|
+
end
|
36
|
+
opts.on('--json', 'Output format is JSON. (FORMAT=JSON)') do
|
37
|
+
env(:format, 'JSON')
|
38
|
+
end
|
39
|
+
opts.on('-h', '--help', 'Print this help and exit.') do
|
40
|
+
$stdout.puts %(#{opts}
|
41
|
+
|
42
|
+
Matches given file with paths and adds frequency information.
|
43
|
+
)
|
44
|
+
exit 0
|
45
|
+
end
|
46
|
+
end
|
47
|
+
parser.parse!
|
48
|
+
|
49
|
+
aargh("Format neither JSON nor YAML: #{env(:format)}", 1) unless %w[JSON YAML].include? env(:format)
|
50
|
+
fn = env(:paths)
|
51
|
+
aargh("Path log file name must be given.") if fn.empty?
|
52
|
+
|
53
|
+
def read_source(filename)
|
54
|
+
YAML.safe_load(filename.empty? ? $stdin : File.read(filename))
|
55
|
+
rescue Errno::ENOENT => e
|
56
|
+
aargh("Could not read #{filename.empty? ? 'stdin' : filename}", 2)
|
57
|
+
rescue StandardError => e
|
58
|
+
aargh(e.to_s, 3)
|
59
|
+
end
|
60
|
+
doc = read_source(env(:in))
|
61
|
+
|
62
|
+
begin
|
63
|
+
# Expect one path per line with nothing else. Query is allowed and ignored.
|
64
|
+
f = File.new(fn, 'rt')
|
65
|
+
f.each_line do |line|
|
66
|
+
p = ServerPath.new(split_path(line))
|
67
|
+
doc['paths'].each_value do |info|
|
68
|
+
next unless (p <=> info['parts']) == 0
|
69
|
+
info['freq'] = info.fetch('freq', 0) + 1
|
70
|
+
# Lookalikes are the others that can be matched, since path has no
|
71
|
+
# variables so any change in fixed parts (not a lookalike) will be a
|
72
|
+
# mismatch with path.
|
73
|
+
info['lookalike'].each do |path|
|
74
|
+
cand = doc['paths'][path]
|
75
|
+
next unless (p <=> cand['parts']) == 0
|
76
|
+
cand['freq'] = cand.fetch('freq', 0) + 1
|
77
|
+
end
|
78
|
+
break
|
79
|
+
end
|
80
|
+
end
|
81
|
+
f.close
|
82
|
+
rescue Errno::ENOENT => e
|
83
|
+
aargh("Could not read path log file: #{fn}", 4)
|
84
|
+
rescue NoMethodError
|
85
|
+
aargh('Is input file an output of openapi-processpaths?', 5)
|
86
|
+
rescue StandardError => e
|
87
|
+
aargh(e.to_s, 6)
|
88
|
+
end
|
89
|
+
|
90
|
+
output = env(:out)
|
91
|
+
if output.empty?
|
92
|
+
output = $stdout
|
93
|
+
else
|
94
|
+
begin
|
95
|
+
output = File.open(output, 'w')
|
96
|
+
rescue StandardError
|
97
|
+
aargh("Failed to open for writing: #{output}", 7)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
case env(:format)
|
102
|
+
when 'JSON' then output.puts JSON.generate(doc)
|
103
|
+
when 'YAML' then output.puts YAML.dump(doc)
|
104
|
+
end
|
105
|
+
output.close
|
@@ -0,0 +1,128 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Copyright © 2021 Ismo Kärkkäinen
|
5
|
+
# Licensed under Universal Permissive License. See LICENSE.txt.
|
6
|
+
|
7
|
+
require '../lib/common.rb'
|
8
|
+
require 'optparse'
|
9
|
+
require 'yaml'
|
10
|
+
require 'erb'
|
11
|
+
|
12
|
+
|
13
|
+
default_env(:out, '')
|
14
|
+
default_env(:in, '')
|
15
|
+
default_env(:template, '')
|
16
|
+
|
17
|
+
ENV['POSIXLY_CORRECT'] = '1'
|
18
|
+
parser = OptionParser.new do |opts|
|
19
|
+
opts.summary_indent = ' '
|
20
|
+
opts.summary_width = 26
|
21
|
+
opts.banner = 'Usage: openapi-generatecode [options] [additions...]'
|
22
|
+
opts.separator ''
|
23
|
+
opts.separator 'Options (equivalent environment variable and value in parentheses):'
|
24
|
+
opts.on('-i', '--input FILE', 'Read processed API from FILE, not stdin (IN=FILE).') do |f|
|
25
|
+
env(:in, f)
|
26
|
+
end
|
27
|
+
opts.on('-o', '--output FILE', 'Output result to FILE, not stdout (OUT=FILE).') do |f|
|
28
|
+
env(:out, f)
|
29
|
+
end
|
30
|
+
opts.on('-t', '--template FILE', 'Read template from FILE (TEMPLATE=FILE).') do |f|
|
31
|
+
env(:template, f)
|
32
|
+
end
|
33
|
+
opts.on('-h', '--help', 'Print this help and exit.') do
|
34
|
+
$stdout.puts %(#{opts}
|
35
|
+
|
36
|
+
Loads ERB template and optional additions to a context along with the processed
|
37
|
+
API document and produces the template result.
|
38
|
+
)
|
39
|
+
exit 0
|
40
|
+
end
|
41
|
+
end
|
42
|
+
parser.parse!
|
43
|
+
|
44
|
+
aargh('Template file name must be given.', 1) if env(:template).empty?
|
45
|
+
|
46
|
+
def load_content(name)
|
47
|
+
name.empty? ? $stdin.read : File.read(name)
|
48
|
+
rescue Errno::ENOENT
|
49
|
+
aargh("Could not load #{name || 'stdin'}", 2)
|
50
|
+
rescue StandardError => e
|
51
|
+
aargh("#{e}\nFailed to read #{name || 'stdin'}", 2)
|
52
|
+
end
|
53
|
+
|
54
|
+
class Generator
|
55
|
+
attr_accessor :addition, :order, :full_name_order, :template, :document
|
56
|
+
|
57
|
+
def initialize(document_content, template_content)
|
58
|
+
@addition = Hash.new
|
59
|
+
@order = []
|
60
|
+
@full_name_order = []
|
61
|
+
@document = document_content
|
62
|
+
@template = template_content
|
63
|
+
end
|
64
|
+
|
65
|
+
def get_binding
|
66
|
+
binding
|
67
|
+
end
|
68
|
+
|
69
|
+
def add(name, content, strip_suffix)
|
70
|
+
@full_name_order.push({ filename: name, contents: content })
|
71
|
+
name = File.basename(name)
|
72
|
+
@order.push name
|
73
|
+
if strip_suffix
|
74
|
+
idx = name.rindex('.')
|
75
|
+
name = name.slice(0, idx) unless idx.nil?
|
76
|
+
end
|
77
|
+
@addition[name] = content
|
78
|
+
end
|
79
|
+
end
|
80
|
+
t = load_content(env(:template))
|
81
|
+
d = load_content(env(:in))
|
82
|
+
begin
|
83
|
+
$generator = Generator.new(YAML.safe_load(d), t)
|
84
|
+
rescue StandardError => e
|
85
|
+
aargh('Failed to parse API document.', 3)
|
86
|
+
end
|
87
|
+
|
88
|
+
ARGV.each do |name|
|
89
|
+
aargh('Addition file name is empty.', 1) if name.empty?
|
90
|
+
if name.end_with? '.rb'
|
91
|
+
begin
|
92
|
+
require File.absolute_path(name)
|
93
|
+
rescue SyntaxError => e
|
94
|
+
aargh("Syntax error in addition #{name}: #{e.to_s}", 4)
|
95
|
+
rescue StandardError => e
|
96
|
+
aargh("Failed to require addition #{name}: #{e.to_s}", 5)
|
97
|
+
end
|
98
|
+
else
|
99
|
+
c = load_content(name)
|
100
|
+
begin
|
101
|
+
d = YAML.safe_load(c)
|
102
|
+
rescue StandardError
|
103
|
+
d = c
|
104
|
+
end
|
105
|
+
$generator.add(name, d, name.upcase.end_with?('.YAML') || name.upcase.end_with?('.JSON'))
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
begin
|
110
|
+
out = ERB.new($generator.template).result($generator.get_binding)
|
111
|
+
rescue SyntaxError => e
|
112
|
+
aargh("Template syntax error: #{e.to_s}", 6)
|
113
|
+
rescue StandardError => e
|
114
|
+
aargh("Template error: #{e.to_s}", 7)
|
115
|
+
end
|
116
|
+
|
117
|
+
output = env(:out)
|
118
|
+
if output.empty?
|
119
|
+
output = $stdout
|
120
|
+
else
|
121
|
+
begin
|
122
|
+
output = File.open(output, 'w')
|
123
|
+
rescue StandardError
|
124
|
+
aargh("Failed to open for writing: #{output}", 1)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
output.write(out)
|
128
|
+
output.close
|
data/bin/openapi-merge
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Copyright © 2021 Ismo Kärkkäinen
|
5
|
+
# Licensed under Universal Permissive License. See LICENSE.txt.
|
6
|
+
|
7
|
+
require '../lib/common.rb'
|
8
|
+
require 'optparse'
|
9
|
+
require 'yaml'
|
10
|
+
require 'json'
|
11
|
+
require 'set'
|
12
|
+
|
13
|
+
|
14
|
+
default_env(:out, '')
|
15
|
+
default_env(:format, 'YAML')
|
16
|
+
default_env(:prune, false);
|
17
|
+
|
18
|
+
ENV['POSIXLY_CORRECT'] = '1'
|
19
|
+
parser = OptionParser.new do |opts|
|
20
|
+
opts.summary_indent = ' '
|
21
|
+
opts.summary_width = 26
|
22
|
+
opts.banner = 'Usage: openapi-merge [options] sources...'
|
23
|
+
opts.separator ''
|
24
|
+
opts.separator 'Options (equivalent environment variable and value in parentheses):'
|
25
|
+
opts.on('-o', '--output FILE', 'Output result to FILE, not stdout (OUT=FILE).') do |f|
|
26
|
+
env(:out, f)
|
27
|
+
end
|
28
|
+
opts.on('-p', '--prune', 'Remove all unreferenced objects under components.') do
|
29
|
+
env(:prune, true)
|
30
|
+
end
|
31
|
+
opts.on('--yaml', 'Output format is YAML (default).') do
|
32
|
+
env(:format, 'YAML')
|
33
|
+
end
|
34
|
+
opts.on('--json', 'Output format is JSON.') do
|
35
|
+
env(:format, 'JSON')
|
36
|
+
end
|
37
|
+
opts.on('-h', '--help', 'Print this help and exit.') do
|
38
|
+
$stdout.puts %(#{opts}
|
39
|
+
|
40
|
+
Source files are combined to form one API specification document. Sources are
|
41
|
+
allowed only to append to the merged document, not re-define anything in it.
|
42
|
+
)
|
43
|
+
exit 0
|
44
|
+
end
|
45
|
+
end
|
46
|
+
parser.parse!
|
47
|
+
|
48
|
+
aargh("Format neither JSON nor YAML: #{env(:format)}", 1) unless %w[JSON YAML].include? env(:format)
|
49
|
+
|
50
|
+
def read_source(filename)
|
51
|
+
YAML.safe_load(File.read(filename))
|
52
|
+
rescue Errno::ENOENT => e
|
53
|
+
aargh("Could not read #{filename}", 2)
|
54
|
+
rescue StandardError => e
|
55
|
+
aargh(e.to_s, 3)
|
56
|
+
end
|
57
|
+
|
58
|
+
def raise_se(message)
|
59
|
+
raise StandardError, message
|
60
|
+
end
|
61
|
+
|
62
|
+
def path_combo(path, prefix = true)
|
63
|
+
if prefix
|
64
|
+
path.empty? ? 'at root' : "under #{path.join('.')}"
|
65
|
+
else
|
66
|
+
path.empty? ? 'root' : path.join('.')
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def too_deep(path, max_depths)
|
71
|
+
return false if path.empty?
|
72
|
+
max_depths.fetch(path.first) <= path.size
|
73
|
+
end
|
74
|
+
|
75
|
+
def add_undefined(merged, incoming, filename, path, max_depths)
|
76
|
+
incoming.each_pair do |key, value|
|
77
|
+
unless merged.has_key? key
|
78
|
+
merged[key] = value
|
79
|
+
next
|
80
|
+
end
|
81
|
+
m = merged[key]
|
82
|
+
raise_se "Path #{path_combo(path, false)} merged type #{m.class} differs from type #{value.class} in #{filename}" unless m.class == value.class
|
83
|
+
raise_se("Re-definition of #{key} #{path_combo(path)} in #{filename}") if too_deep(path, max_depths)
|
84
|
+
if m.is_a? Hash # paths or similar
|
85
|
+
path.push key
|
86
|
+
add_undefined(m, value, filename, path, max_depths)
|
87
|
+
path.pop
|
88
|
+
elsif m.is_a? Array
|
89
|
+
value.each do |v|
|
90
|
+
next if m.include? v
|
91
|
+
m.push v
|
92
|
+
end
|
93
|
+
else
|
94
|
+
raise_se "Re-definition of #{key} #{path_combo(path)} in #{filename}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
rescue StandardError => e
|
98
|
+
aargh(e.to_s, 3)
|
99
|
+
end
|
100
|
+
|
101
|
+
max_depths = Hash.new(0)
|
102
|
+
max_depths['openapi'] = 1
|
103
|
+
max_depths['info'] = 1
|
104
|
+
max_depths['servers'] = 1
|
105
|
+
max_depths['paths'] = 2 # Allows get, post, options, etc. from different files.
|
106
|
+
max_depths['webhooks'] = 2
|
107
|
+
max_depths['components'] = 2
|
108
|
+
max_depths['security'] = 1
|
109
|
+
max_depths['tags'] = 1
|
110
|
+
merged = {}
|
111
|
+
ARGV.each do |filename|
|
112
|
+
add_undefined(merged, read_source(filename), filename, [], max_depths)
|
113
|
+
end
|
114
|
+
|
115
|
+
def gather_refs(doc, found)
|
116
|
+
doc.each_pair do |key, value|
|
117
|
+
if key == '$ref' && value.is_a?(String) && value.start_with?('#/components/')
|
118
|
+
found.add(value)
|
119
|
+
elsif value.is_a? Hash
|
120
|
+
gather_refs(value, found)
|
121
|
+
elsif value.is_a? Array
|
122
|
+
value.each do |v|
|
123
|
+
gather_refs(v, found) if v.is_a? Hash
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
if env(:prune)
|
130
|
+
refs = Set.new
|
131
|
+
gather_refs(merged, refs)
|
132
|
+
used = {}
|
133
|
+
all = merged.fetch('components', {})
|
134
|
+
refs.each do |ref|
|
135
|
+
p = ref.split('/')
|
136
|
+
p.shift(2)
|
137
|
+
item = all
|
138
|
+
p.each do |key|
|
139
|
+
item = item.fetch(key, nil)
|
140
|
+
break if item.nil?
|
141
|
+
end
|
142
|
+
next if item.nil?
|
143
|
+
sub = used
|
144
|
+
p.each_index do |k|
|
145
|
+
if k + 1 < p.size
|
146
|
+
sub[p[k]] = {} unless sub.has_key? p[k]
|
147
|
+
sub = sub[p[k]]
|
148
|
+
else
|
149
|
+
sub[p[k]] = item
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
merged['components'] = used
|
154
|
+
end
|
155
|
+
|
156
|
+
output = env(:out)
|
157
|
+
if output.empty?
|
158
|
+
output = $stdout
|
159
|
+
else
|
160
|
+
begin
|
161
|
+
output = File.open(output, 'w')
|
162
|
+
rescue StandardError
|
163
|
+
aargh("Failed to open for writing: #{output}", 1)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
case env(:format)
|
168
|
+
when 'JSON' then output.puts JSON.generate(merged)
|
169
|
+
when 'YAML' then output.puts YAML.dump(merged)
|
170
|
+
end
|
171
|
+
output.close
|
@@ -0,0 +1,111 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Copyright © 2021 Ismo Kärkkäinen
|
5
|
+
# Licensed under Universal Permissive License. See LICENSE.txt.
|
6
|
+
|
7
|
+
require '../lib/common.rb'
|
8
|
+
require 'optparse'
|
9
|
+
require 'yaml'
|
10
|
+
require 'json'
|
11
|
+
|
12
|
+
|
13
|
+
default_env(:in, '')
|
14
|
+
default_env(:out, '')
|
15
|
+
default_env(:format, 'YAML')
|
16
|
+
default_env(:error, true)
|
17
|
+
|
18
|
+
parser = OptionParser.new do |opts|
|
19
|
+
opts.summary_indent = ' '
|
20
|
+
opts.summary_width = 24
|
21
|
+
opts.banner = 'Usage: openapi-processpaths [options]'
|
22
|
+
opts.separator ''
|
23
|
+
opts.separator 'Options (equivalent environment variable and value in parentheses):'
|
24
|
+
opts.on('-i', '--input FILE', 'Read source from FILE, not stdin (IN=FILE).') do |f|
|
25
|
+
env(:in, f)
|
26
|
+
end
|
27
|
+
opts.on('-o', '--output FILE', 'Output result to FILE, not stdout (OUT=FILE).') do |f|
|
28
|
+
env(:out, f)
|
29
|
+
end
|
30
|
+
opts.on('--warn', 'Only warn of paths with same fixed parts (ERROR=0).') do
|
31
|
+
env(:error, false)
|
32
|
+
end
|
33
|
+
opts.on('--yaml', 'Output format is YAML (default) (FORMAT=YAML).') do
|
34
|
+
env(:format, 'YAML')
|
35
|
+
end
|
36
|
+
opts.on('--json', 'Output format is JSON. (FORMAT=JSON)') do
|
37
|
+
env(:format, 'JSON')
|
38
|
+
end
|
39
|
+
opts.on('-h', '--help', 'Print this help and exit.') do
|
40
|
+
$stdout.puts %(#{opts}
|
41
|
+
|
42
|
+
Processes API specification document path objects into form that is expected by
|
43
|
+
later stage tools. Checks for paths that may be ambiguous.
|
44
|
+
)
|
45
|
+
exit 0
|
46
|
+
end
|
47
|
+
end
|
48
|
+
parser.parse!
|
49
|
+
|
50
|
+
aargh("Format neither JSON nor YAML: #{env(:format)}", 1) unless %w[JSON YAML].include? env(:format)
|
51
|
+
|
52
|
+
def read_source(filename)
|
53
|
+
YAML.safe_load(filename.empty? ? $stdin : File.read(filename))
|
54
|
+
rescue Errno::ENOENT => e
|
55
|
+
aargh("Could not read #{filename.empty? ? 'stdin' : filename}", 2)
|
56
|
+
rescue StandardError => e
|
57
|
+
aargh(e.to_s, 3)
|
58
|
+
end
|
59
|
+
doc = read_source(env(:in))
|
60
|
+
|
61
|
+
processed = {}
|
62
|
+
doc.fetch('paths', {}).each_pair do |path, value|
|
63
|
+
parts = split_path(path, true)
|
64
|
+
processed[path] = {
|
65
|
+
'parts' => parts,
|
66
|
+
'orig' => value,
|
67
|
+
'lookalike' => [],
|
68
|
+
path: ServerPath.new(parts)
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
# Find lookalike sets.
|
73
|
+
lookalikes = false
|
74
|
+
paths = processed.keys.sort
|
75
|
+
paths.each_index do |k|
|
76
|
+
pk = paths[k]
|
77
|
+
a = processed[pk]
|
78
|
+
(0...k).each do |n|
|
79
|
+
pn = paths[n]
|
80
|
+
b = processed[pn]
|
81
|
+
next unless (a[:path].compare(b[:path])) == 0
|
82
|
+
a['lookalike'].push pn
|
83
|
+
b['lookalike'].push pk
|
84
|
+
$stderr.puts("Similar: #{pn} #{pk}")
|
85
|
+
lookalikes = true
|
86
|
+
end
|
87
|
+
end
|
88
|
+
aargh('Similar paths found.', 7) if lookalikes && env(:error)
|
89
|
+
|
90
|
+
# Clean-up temporary fields.
|
91
|
+
processed.each_value do |v|
|
92
|
+
v.keys.each { |k| v.delete(k) if k.is_a? Symbol }
|
93
|
+
end
|
94
|
+
doc['paths'] = processed
|
95
|
+
|
96
|
+
output = env(:out)
|
97
|
+
if output.empty?
|
98
|
+
output = $stdout
|
99
|
+
else
|
100
|
+
begin
|
101
|
+
output = File.open(output, 'w')
|
102
|
+
rescue StandardError
|
103
|
+
aargh("Failed to open for writing: #{output}", 6)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
case env(:format)
|
108
|
+
when 'JSON' then output.puts JSON.generate(doc)
|
109
|
+
when 'YAML' then output.puts YAML.dump(doc)
|
110
|
+
end
|
111
|
+
output.close
|
data/lib/common.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
def aargh(message, exit_code = nil)
|
4
|
+
$stderr.puts message
|
5
|
+
exit exit_code unless exit_code.nil?
|
6
|
+
end
|
7
|
+
|
8
|
+
def env(var, value = nil)
|
9
|
+
k = var.to_s.upcase
|
10
|
+
ENV[k] = { false => '0', true => '1' }.fetch(value, value) unless value.nil?
|
11
|
+
v = ENV.fetch(k, nil)
|
12
|
+
case v
|
13
|
+
when '0' then false
|
14
|
+
when '1' then true
|
15
|
+
else
|
16
|
+
v
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def default_env(var, value)
|
21
|
+
v = env(var)
|
22
|
+
env(var, value) if v.nil?
|
23
|
+
end
|
24
|
+
|
25
|
+
def split_path(p, spec = false)
|
26
|
+
parts = []
|
27
|
+
p = p.strip
|
28
|
+
unless spec
|
29
|
+
q = p.index('?')
|
30
|
+
p.slice!(0...q) unless q.nil?
|
31
|
+
end
|
32
|
+
p.split('/').each do |s|
|
33
|
+
next if s.empty?
|
34
|
+
s = { var: s } if spec && s.include?('{')
|
35
|
+
parts.push(s)
|
36
|
+
end
|
37
|
+
parts
|
38
|
+
end
|
39
|
+
|
40
|
+
ServerPath = Struct.new(:parts) do
|
41
|
+
def <=>(p) # Variables are after fixed strings.
|
42
|
+
pp = p.is_a?(Array) ? p : p.parts
|
43
|
+
parts.each_index do |k|
|
44
|
+
return 1 if pp.size <= k # Longer comes after shorter.
|
45
|
+
pk = parts[k]
|
46
|
+
ppk = pp[k]
|
47
|
+
if pk.is_a? String
|
48
|
+
if ppk.is_a? String
|
49
|
+
c = pk <=> ppk
|
50
|
+
else
|
51
|
+
return -1
|
52
|
+
end
|
53
|
+
else
|
54
|
+
if ppk.is_a? String
|
55
|
+
return 1
|
56
|
+
else
|
57
|
+
c = pk.fetch('var', '') <=> ppk.fetch('var', '')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
return c unless c.zero?
|
61
|
+
end
|
62
|
+
(parts.size < pp.size) ? -1 : 0
|
63
|
+
end
|
64
|
+
|
65
|
+
def compare(p, range = nil) # Not fit for sorting. Variable equals anything.
|
66
|
+
pp = p.is_a?(Array) ? p : p.parts
|
67
|
+
if range.nil?
|
68
|
+
range = 0...parts.size
|
69
|
+
elsif range.is_a? Number
|
70
|
+
range = range...(range + 1)
|
71
|
+
end
|
72
|
+
range.each do |k|
|
73
|
+
return 1 if pp.size <= k # Longer comes after shorter.
|
74
|
+
ppk = pp[k]
|
75
|
+
next unless ppk.is_a? String
|
76
|
+
pk = parts[k]
|
77
|
+
next unless pk.is_a? String
|
78
|
+
c = pk <=> ppk
|
79
|
+
return c unless c.zero?
|
80
|
+
end
|
81
|
+
(parts.size < pp.size) ? -1 : 0
|
82
|
+
end
|
83
|
+
end
|
metadata
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: openapi-sourcetools
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ismo Kärkkäinen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-10-05 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: |2
|
14
|
+
|
15
|
+
Tools for generating source code from API specification in OpenAPI format.
|
16
|
+
email: ismokarkkainen@icloud.com
|
17
|
+
executables:
|
18
|
+
- openapi-generatecode
|
19
|
+
- openapi-frequencies
|
20
|
+
- openapi-merge
|
21
|
+
- openapi-processpaths
|
22
|
+
extensions: []
|
23
|
+
extra_rdoc_files: []
|
24
|
+
files:
|
25
|
+
- LICENSE.txt
|
26
|
+
- bin/openapi-frequencies
|
27
|
+
- bin/openapi-generatecode
|
28
|
+
- bin/openapi-merge
|
29
|
+
- bin/openapi-processpaths
|
30
|
+
- lib/common.rb
|
31
|
+
homepage: https://xn--ismo-krkkinen-gfbd.fi/openapi-sourcetools/index.html
|
32
|
+
licenses:
|
33
|
+
- UPL-1.0
|
34
|
+
metadata: {}
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options: []
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
requirements: []
|
50
|
+
rubygems_version: 3.1.2
|
51
|
+
signing_key:
|
52
|
+
specification_version: 4
|
53
|
+
summary: Tools for creating source code from API specification.
|
54
|
+
test_files: []
|