openapi-sourcetools 0.7.1 → 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 +8 -7
- data/bin/openapi-addparameters +50 -9
- data/bin/openapi-addresponses +8 -8
- data/bin/openapi-addschemas +10 -9
- data/bin/openapi-checkschemas +16 -15
- data/bin/openapi-frequencies +23 -25
- data/bin/openapi-generate +15 -12
- data/bin/openapi-merge +6 -6
- data/bin/openapi-modifypaths +16 -15
- data/bin/openapi-processpaths +15 -26
- 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 -306
- data/lib/common.rb +0 -114
- 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
data/bin/openapi-processpaths
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
# Copyright © 2021-
|
4
|
+
# Copyright © 2021-2025 Ismo Kärkkäinen
|
5
5
|
# Licensed under Universal Permissive License. See LICENSE.txt.
|
6
6
|
|
7
|
-
require_relative '../lib/
|
7
|
+
require_relative '../lib/openapi/sourcetools/apiobjects'
|
8
|
+
require_relative '../lib/openapi/sourcetools/common'
|
8
9
|
require 'optparse'
|
9
|
-
|
10
|
+
include OpenAPISourceTools
|
10
11
|
|
11
12
|
|
12
13
|
def main
|
@@ -32,26 +33,22 @@ def main
|
|
32
33
|
opts.on('-h', '--help', 'Print this help and exit.') do
|
33
34
|
$stdout.puts %(#{opts}
|
34
35
|
|
35
|
-
|
36
|
-
|
36
|
+
Adds split path parts into API document path items under x-openapi-sourcetools
|
37
|
+
key. Checks for paths that may be ambiguous.
|
37
38
|
)
|
38
39
|
exit 0
|
39
40
|
end
|
40
41
|
end
|
41
42
|
parser.order!
|
42
43
|
|
43
|
-
doc = load_source(input_name)
|
44
|
+
doc = Common.load_source(input_name)
|
44
45
|
return 2 if doc.nil?
|
45
46
|
|
46
47
|
processed = {}
|
47
|
-
doc.fetch('paths', {}).
|
48
|
-
parts = split_path(path, true)
|
49
|
-
|
50
|
-
|
51
|
-
'orig' => value,
|
52
|
-
'lookalike' => [],
|
53
|
-
path: ServerPath.new(parts)
|
54
|
-
}
|
48
|
+
doc.fetch('paths', {}).each do |path, item|
|
49
|
+
parts = Common.split_path(path, true)
|
50
|
+
item['x-openapi-sourcetools-parts'] = parts # Added to original path item.
|
51
|
+
processed[path] = ApiObjects::ServerPath.new(parts)
|
55
52
|
end
|
56
53
|
|
57
54
|
# Find lookalike sets.
|
@@ -63,22 +60,14 @@ later stage tools. Checks for paths that may be ambiguous.
|
|
63
60
|
k.times do |n|
|
64
61
|
pn = paths[n]
|
65
62
|
b = processed[pn]
|
66
|
-
next unless a
|
67
|
-
a['lookalike'].push pn
|
68
|
-
b['lookalike'].push pk
|
63
|
+
next unless a.compare(b).zero?
|
69
64
|
$stderr.puts("Similar: #{pn} #{pk}")
|
70
65
|
lookalikes = true
|
71
66
|
end
|
72
67
|
end
|
73
|
-
return aargh('Similar paths found.', 4) if lookalikes && error
|
68
|
+
return Common.aargh('Similar paths found.', 4) if lookalikes && error
|
74
69
|
|
75
|
-
|
76
|
-
processed.each_value do |v|
|
77
|
-
v.keys.each { |k| v.delete(k) if k.is_a? Symbol }
|
78
|
-
end
|
79
|
-
doc['paths'] = processed
|
80
|
-
|
81
|
-
dump_result(output_name, YAML.dump(doc), 3)
|
70
|
+
Common.dump_result(output_name, doc, 3)
|
82
71
|
end
|
83
72
|
|
84
|
-
exit(main)
|
73
|
+
exit(main) unless defined?($unit_test)
|
@@ -0,0 +1,191 @@
|
|
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
|
+
module OpenAPISourceTools
|
7
|
+
# Various classes for handling objects in the API specification.
|
8
|
+
# Used in various programs.
|
9
|
+
module ApiObjects
|
10
|
+
|
11
|
+
def self.same(a, b, ignored_keys = Set.new(%w[summary description]))
|
12
|
+
return a == b unless a.is_a?(Hash) && b.is_a?(Hash)
|
13
|
+
keys = Set.new(a.keys + b.keys) - ignored_keys
|
14
|
+
keys.to_a.each do |k|
|
15
|
+
return false unless a.key?(k) && b.key?(k)
|
16
|
+
return false unless same(a[k], b[k], ignored_keys)
|
17
|
+
end
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.ref_string(name, schema_path)
|
22
|
+
"#{schema_path}/#{name}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.reference(obj, schemas, schema_path, ignored_keys = Set.new(%w[summary description]), prefix = 'Schema')
|
26
|
+
# Check if identical schema has been added and if so, return the $ref string.
|
27
|
+
schemas.keys.sort.each do |k|
|
28
|
+
return ref_string(k, schema_path) if same(obj, schemas[k], ignored_keys)
|
29
|
+
end
|
30
|
+
# One of the numbers will not match existing keys. More number than keys.
|
31
|
+
(schemas.size + 1).times do |n|
|
32
|
+
# 'x' is to simplify find and replace (Schema1x vs Schema1 and Schema10)
|
33
|
+
k = "#{prefix}#{n}x"
|
34
|
+
next if schemas.key?(k)
|
35
|
+
schemas[k] = obj.merge
|
36
|
+
return ref_string(k, schema_path)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# A component in the API specification for reference and anchor handling.
|
41
|
+
class Components
|
42
|
+
attr_reader :path, :prefix, :anchor2ref, :schema_names
|
43
|
+
attr_accessor :items, :ignored_keys
|
44
|
+
|
45
|
+
def initialize(path, prefix, ignored_keys = %w[summary description examples example $anchor])
|
46
|
+
path = "#/#{path.join('/')}/" if path.is_a?(Array)
|
47
|
+
path = "#{path}/" unless path.end_with?('/')
|
48
|
+
@path = path
|
49
|
+
@prefix = prefix
|
50
|
+
@anchor2ref = {}
|
51
|
+
@schema_names = Set.new
|
52
|
+
@items = {}
|
53
|
+
@ignored_keys = Set.new(ignored_keys)
|
54
|
+
end
|
55
|
+
|
56
|
+
def add_options(opts)
|
57
|
+
opts.on('--use FIELD', 'Use FIELD in comparisons.') do |f|
|
58
|
+
@ignored_keys.delete(f)
|
59
|
+
end
|
60
|
+
opts.on('--ignore FIELD', 'Ignore FIELD in comparisons.') do |f|
|
61
|
+
@ignored_keys.add(f)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def help
|
66
|
+
%(All fields are used in object equality comparisons except:\n#{@ignored_keys.to_a.sort!.join("\n")})
|
67
|
+
end
|
68
|
+
|
69
|
+
def add_schema_name(name)
|
70
|
+
@schema_names.add(name)
|
71
|
+
end
|
72
|
+
|
73
|
+
def ref_string(name)
|
74
|
+
return nil if name.nil?
|
75
|
+
"#{@path}#{name}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def reference(obj)
|
79
|
+
# Check if identical schema has been added. If so, return the $ref string.
|
80
|
+
@items.each do |k, v|
|
81
|
+
return ref_string(k) if ApiObjects.same(obj, v, @ignored_keys)
|
82
|
+
end
|
83
|
+
# One of the numbers will not match existing keys. More number than keys.
|
84
|
+
(@items.size + 1).times do |n|
|
85
|
+
# 'x' is to simplify find and replace (Schema1x vs Schema1 and Schema10)
|
86
|
+
cand = "#{@prefix}#{n}x"
|
87
|
+
next if @items.key?(cand)
|
88
|
+
@items[cand] = obj.merge
|
89
|
+
@schema_names.add(cand)
|
90
|
+
return ref_string(cand)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def store_anchor(obj, ref = nil)
|
95
|
+
anchor_name = obj['$anchor']
|
96
|
+
return if anchor_name.nil?
|
97
|
+
ref = obj['$ref'] if ref.nil?
|
98
|
+
raise StandardError, 'ref is nil and no $ref or it is nil' if ref.nil?
|
99
|
+
@anchor2ref[anchor_name] = ref
|
100
|
+
end
|
101
|
+
|
102
|
+
def alter_anchors
|
103
|
+
replacements = {}
|
104
|
+
@anchor2ref.each_key do |a|
|
105
|
+
next if @schema_names.member?(a)
|
106
|
+
replacements[a] = ref_string(a)
|
107
|
+
@schema_names.add(a)
|
108
|
+
end
|
109
|
+
replacements.each do |a, r|
|
110
|
+
@anchor2ref[a] = r
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def anchor_ref_replacement(ref)
|
115
|
+
@anchor2ref[ref[1...ref.size]] || ref
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Represents path with fixed parts and variables.
|
120
|
+
class ServerPath
|
121
|
+
# Probably moves to a separate file once processpaths and frequencies receive
|
122
|
+
# some attention.
|
123
|
+
include Comparable
|
124
|
+
|
125
|
+
attr_accessor :parts
|
126
|
+
|
127
|
+
def initialize(parts)
|
128
|
+
@parts = parts
|
129
|
+
end
|
130
|
+
|
131
|
+
# Parameters are after fixed strings.
|
132
|
+
def <=>(other)
|
133
|
+
pp = other.is_a?(Array) ? other : other.parts
|
134
|
+
@parts.size.times do |k|
|
135
|
+
return 1 if pp.size <= k # Longer comes after shorter.
|
136
|
+
pk = @parts[k]
|
137
|
+
ppk = pp[k]
|
138
|
+
if pk.key?('fixed')
|
139
|
+
if ppk.key?('fixed')
|
140
|
+
c = pk['fixed'] <=> ppk['fixed']
|
141
|
+
else
|
142
|
+
return -1
|
143
|
+
end
|
144
|
+
else
|
145
|
+
if ppk.key?('fixed')
|
146
|
+
return 1
|
147
|
+
else
|
148
|
+
c = pk.fetch('parameter', '') <=> ppk.fetch('parameter', '')
|
149
|
+
end
|
150
|
+
end
|
151
|
+
return c unless c.zero?
|
152
|
+
end
|
153
|
+
(@parts.size < pp.size) ? -1 : 0
|
154
|
+
end
|
155
|
+
|
156
|
+
# Not fit for sorting. Variable equals anything.
|
157
|
+
def compare(other, range = nil)
|
158
|
+
pp = other.is_a?(Array) ? other : other.parts
|
159
|
+
if range.nil?
|
160
|
+
range = 0...@parts.size
|
161
|
+
elsif range.is_a? Number
|
162
|
+
range = range...(range + 1)
|
163
|
+
end
|
164
|
+
range.each do |k|
|
165
|
+
return 1 if pp.size <= k # Longer comes after shorter.
|
166
|
+
ppk = pp[k]
|
167
|
+
next unless ppk.key?('fixed')
|
168
|
+
pk = parts[k]
|
169
|
+
next unless pk.key?('fixed')
|
170
|
+
c = pk['fixed'] <=> ppk['fixed']
|
171
|
+
return c unless c.zero?
|
172
|
+
end
|
173
|
+
(@parts.size < pp.size) ? -1 : 0
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def self.operation_objects(path_item)
|
178
|
+
keys = %w[operationId requestBody responses callbacks]
|
179
|
+
out = {}
|
180
|
+
path_item.each do |method, op|
|
181
|
+
next unless op.is_a?(Hash)
|
182
|
+
keys.each do |key|
|
183
|
+
next unless op.key?(key)
|
184
|
+
out[method] = op
|
185
|
+
break
|
186
|
+
end
|
187
|
+
end
|
188
|
+
out
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright © 2021-2025 Ismo Kärkkäinen
|
4
|
+
# Licensed under Universal Permissive License. See LICENSE.txt.
|
5
|
+
|
6
|
+
require 'pathname'
|
7
|
+
require 'yaml'
|
8
|
+
|
9
|
+
module OpenAPISourceTools
|
10
|
+
# Common methods used in programs and elsewhere gathered into one place.
|
11
|
+
module Common
|
12
|
+
def self.aargh(message, return_value = nil)
|
13
|
+
message = message.map(&:to_s).join("\n") if message.is_a? Array
|
14
|
+
$stderr.puts message
|
15
|
+
return_value
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.yesno(boolean)
|
19
|
+
boolean ? 'yes' : 'no'
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.bury(doc, path, value)
|
23
|
+
(path.size - 1).times do |k|
|
24
|
+
p = path[k]
|
25
|
+
doc[p] = {} unless doc.key?(p)
|
26
|
+
doc = doc[p]
|
27
|
+
end
|
28
|
+
doc[path.last] = value
|
29
|
+
end
|
30
|
+
|
31
|
+
module Out
|
32
|
+
attr_reader :count
|
33
|
+
module_function :count
|
34
|
+
attr_accessor :quiet
|
35
|
+
module_function :quiet
|
36
|
+
module_function :quiet=
|
37
|
+
|
38
|
+
def self.put(message)
|
39
|
+
Common.aargh(message) unless @quiet
|
40
|
+
@count = @count.nil? ? 1 : @count + 1
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.split_path(p, spec = false)
|
45
|
+
parts = []
|
46
|
+
p = p.strip
|
47
|
+
unless spec
|
48
|
+
q = p.index('?')
|
49
|
+
p.slice!(0...q) unless q.nil?
|
50
|
+
end
|
51
|
+
p.split('/').each do |s|
|
52
|
+
next if s.empty?
|
53
|
+
s = { (spec && s.include?('{') ? 'parameter' : 'fixed') => s }
|
54
|
+
parts.push(s)
|
55
|
+
end
|
56
|
+
parts
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.load_source(input)
|
60
|
+
YAML.safe_load(input.nil? ? $stdin : File.read(input))
|
61
|
+
rescue Errno::ENOENT
|
62
|
+
aargh "Could not load #{input || 'stdin'}"
|
63
|
+
rescue StandardError => e
|
64
|
+
aargh "#{e}\nFailed to read #{input || 'stdin'}"
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.dump_result(output, doc, error_return)
|
68
|
+
doc = YAML.dump(doc, line_width: 1_000_000) unless doc.is_a?(String)
|
69
|
+
if output.nil?
|
70
|
+
$stdout.puts doc
|
71
|
+
else
|
72
|
+
fp = Pathname.new output
|
73
|
+
fp.open('w') do |f|
|
74
|
+
f.puts doc
|
75
|
+
end
|
76
|
+
end
|
77
|
+
0
|
78
|
+
rescue StandardError => e
|
79
|
+
aargh([ e, "Failed to write output: #{output || 'stdout'}" ], error_return)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,158 @@
|
|
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_relative 'gen'
|
8
|
+
require 'find'
|
9
|
+
require 'yaml'
|
10
|
+
|
11
|
+
module OpenAPISourceTools
|
12
|
+
# Configuration file find and load convenience functions.
|
13
|
+
# See the first 3 methods. The rest are intended to be internal helpers.
|
14
|
+
module ConfigLoader
|
15
|
+
|
16
|
+
# A function to find all files with a given prefix.
|
17
|
+
# Prefix is taken from Gen.config if nil.
|
18
|
+
# Returns an array of ConfigFileInfo objects.
|
19
|
+
def self.find_files(name_prefix:, extensions: [ '.*' ])
|
20
|
+
name_prefix = Gen.config if name_prefix.nil?
|
21
|
+
raise ArgumentError, 'name_prefix or config must be set' if name_prefix.nil?
|
22
|
+
root, name_prefix = prepare_prefix(name_prefix, Gen.wd)
|
23
|
+
file_paths = find_filenames(root, name_prefix)
|
24
|
+
splitter = path_splitter(Gen.separator)
|
25
|
+
out = file_paths.map { |fp| convert_path_end(fp, splitter, root.size + 1, extensions) }
|
26
|
+
out.sort!
|
27
|
+
end
|
28
|
+
|
29
|
+
# A function to read all YAML files in an array of ConfigFileInfo objects.
|
30
|
+
# Returns the same as contents_array.
|
31
|
+
def self.read_contents(config_file_infos)
|
32
|
+
config_file_infos.each do |cfi|
|
33
|
+
c = YAML.safe_load_file(cfi.path)
|
34
|
+
# Check allows e.g. copyright and license files be named with config prefix
|
35
|
+
# for clarity, but ignored during config loading.
|
36
|
+
next if cfi.keys.empty? && !c.is_a?(Hash)
|
37
|
+
cfi.bury_content(c)
|
38
|
+
rescue Psych::SyntaxError
|
39
|
+
next # Was not YAML. Other files can be named using config prefix.
|
40
|
+
end
|
41
|
+
contents_array(config_file_infos)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Maps an array of ConfigFileInfo objects to an array of their contents.
|
45
|
+
def self.contents_array(config_file_infos)
|
46
|
+
config_file_infos.map(&:content).reject(&:nil?)
|
47
|
+
end
|
48
|
+
|
49
|
+
class ConfigFileInfo
|
50
|
+
include Comparable
|
51
|
+
|
52
|
+
attr_reader :root, :keys, :path, :content
|
53
|
+
|
54
|
+
def initialize(pieces, path)
|
55
|
+
@keys = []
|
56
|
+
@root = nil
|
57
|
+
pieces.each do |p|
|
58
|
+
if p.is_a?(String)
|
59
|
+
if @root.nil?
|
60
|
+
@root = p
|
61
|
+
else
|
62
|
+
@keys.push(p)
|
63
|
+
end
|
64
|
+
else
|
65
|
+
break if p == :extension
|
66
|
+
end
|
67
|
+
end
|
68
|
+
@path = path
|
69
|
+
@content = nil
|
70
|
+
end
|
71
|
+
|
72
|
+
def bury_content(content)
|
73
|
+
# Turns chain of keys into nested Hashes.
|
74
|
+
@keys.reverse.each do |key|
|
75
|
+
c = { key => content }
|
76
|
+
content = c
|
77
|
+
end
|
78
|
+
@content = content
|
79
|
+
end
|
80
|
+
|
81
|
+
def <=>(other)
|
82
|
+
d = @root <=> other.root
|
83
|
+
return d unless d.zero?
|
84
|
+
d = @keys.size <=> other.keys.size
|
85
|
+
return d unless d.zero?
|
86
|
+
d = @keys <=> other.keys
|
87
|
+
return d unless d.zero?
|
88
|
+
@path <=> other.path
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.prepare_prefix(name_prefix, root)
|
93
|
+
name_prefix_dir = File.dirname(name_prefix)
|
94
|
+
root = File.realpath(name_prefix_dir, root) unless name_prefix_dir == '.'
|
95
|
+
name_prefix = File.basename(name_prefix)
|
96
|
+
if name_prefix == '.' # Just being nice. Daft argument.
|
97
|
+
name_prefix = File.basename(root)
|
98
|
+
root = File.dirname(root)
|
99
|
+
end
|
100
|
+
[root, name_prefix]
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.find_filenames(root, name_prefix)
|
104
|
+
full_prefix = File.join(root, name_prefix)
|
105
|
+
file_paths = []
|
106
|
+
Find.find(root) do |path|
|
107
|
+
next if path.size < full_prefix.size
|
108
|
+
is_dir = File.directory?(path)
|
109
|
+
if path.start_with?(full_prefix)
|
110
|
+
file_paths.push(path) unless is_dir
|
111
|
+
elsif is_dir
|
112
|
+
Find.prune
|
113
|
+
end
|
114
|
+
end
|
115
|
+
file_paths
|
116
|
+
end
|
117
|
+
|
118
|
+
def self.path_splitter(separator)
|
119
|
+
parts = [ Regexp.quote('/') ]
|
120
|
+
parts.push(Regexp.quote(separator)) if separator.is_a?(String) && !separator.empty?
|
121
|
+
Regexp.new("(#{parts.join('|')})")
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.remove_extension(file_path, extensions)
|
125
|
+
extensions.each do |e|
|
126
|
+
if e == '.*'
|
127
|
+
idx = file_path.rindex('.')
|
128
|
+
next if idx.nil? # No . anywhere.
|
129
|
+
ext = file_path[idx..]
|
130
|
+
next unless ext.index('/').nil? # Last . is before file name.
|
131
|
+
return [ file_path[0...idx], ext ]
|
132
|
+
elsif file_path.end_with?(e)
|
133
|
+
return [ file_path[0..-(1 + e.size)], e ]
|
134
|
+
end
|
135
|
+
end
|
136
|
+
[ file_path, nil ]
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.convert_path_end(path, splitter, prefix_size, extensions)
|
140
|
+
relevant, ext = remove_extension(path[prefix_size..], extensions)
|
141
|
+
pieces = relevant.split(splitter).map do |piece|
|
142
|
+
case piece
|
143
|
+
when '' then nil
|
144
|
+
when '/' then :dir
|
145
|
+
when Gen.separator then :separator
|
146
|
+
else
|
147
|
+
piece
|
148
|
+
end
|
149
|
+
end
|
150
|
+
unless ext.nil?
|
151
|
+
pieces.push(:extension)
|
152
|
+
pieces.push(ext)
|
153
|
+
end
|
154
|
+
pieces.compact!
|
155
|
+
ConfigFileInfo.new(pieces, path)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,41 @@
|
|
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
|
+
module OpenAPISourceTools
|
7
|
+
# To hold documents loaded via command-line.
|
8
|
+
# Provides attribute accessor methods for each document.
|
9
|
+
# Exposed via Gen.d to tasks.
|
10
|
+
class Docs
|
11
|
+
attr_reader :docs
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@docs = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing(method_name, *args)
|
18
|
+
name = method_name.to_s
|
19
|
+
if name.end_with?('=')
|
20
|
+
name = name[0...(name.size - 1)]
|
21
|
+
super unless @docs.key?(name)
|
22
|
+
@docs[name] = args.first
|
23
|
+
return args.first
|
24
|
+
end
|
25
|
+
super unless @docs.key?(name)
|
26
|
+
@docs[name]
|
27
|
+
end
|
28
|
+
|
29
|
+
def respond_to_missing?(method_name, *args)
|
30
|
+
name = method_name.to_s
|
31
|
+
name = name[0...(name.size - 1)] if name.end_with?('=')
|
32
|
+
@docs.key?(name) || super
|
33
|
+
end
|
34
|
+
|
35
|
+
def add(name, content)
|
36
|
+
return false if docs.key?(name)
|
37
|
+
@docs[name] = content
|
38
|
+
true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -1,37 +1,44 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright © 2024 Ismo Kärkkäinen
|
3
|
+
# Copyright © 2024-2025 Ismo Kärkkäinen
|
4
4
|
# Licensed under Universal Permissive License. See LICENSE.txt.
|
5
5
|
|
6
6
|
require_relative 'task'
|
7
7
|
require_relative 'helper'
|
8
8
|
require_relative 'docs'
|
9
9
|
require_relative 'output'
|
10
|
+
require_relative 'config'
|
11
|
+
require 'deep_merge'
|
10
12
|
|
11
13
|
|
14
|
+
# The generation module that contains things visible to tasks.
|
12
15
|
module Gen
|
13
16
|
def self.add_doc(symbol, docstr)
|
14
17
|
return if docstr.nil?
|
15
18
|
@docsrc = [] unless instance_variable_defined?('@docsrc')
|
16
|
-
@docsrc.push("- #{symbol
|
19
|
+
@docsrc.push("- #{symbol} : #{docstr}")
|
17
20
|
end
|
21
|
+
private_class_method :add_doc
|
18
22
|
|
19
23
|
def self.read_attr(symbol, default)
|
20
24
|
return if symbol.nil?
|
21
25
|
attr_reader(symbol)
|
22
26
|
module_function(symbol)
|
23
|
-
instance_variable_set("@#{symbol
|
27
|
+
instance_variable_set("@#{symbol}", default)
|
24
28
|
end
|
29
|
+
private_class_method :read_attr
|
25
30
|
|
26
31
|
def self.mod_attr2_reader(symbol, symbol2, docstr = nil, default = nil)
|
27
32
|
read_attr(symbol, default)
|
28
33
|
read_attr(symbol2, default)
|
29
34
|
add_doc(symbol, docstr)
|
30
35
|
end
|
36
|
+
private_class_method :mod_attr2_reader
|
31
37
|
|
32
38
|
def self.mod_attr_reader(symbol, docstr = nil, default = nil)
|
33
39
|
mod_attr2_reader(symbol, nil, docstr, default)
|
34
40
|
end
|
41
|
+
private_class_method :mod_attr_reader
|
35
42
|
|
36
43
|
def self.rw_attr(symbol, default)
|
37
44
|
attr_accessor(symbol)
|
@@ -40,39 +47,56 @@ module Gen
|
|
40
47
|
module_function((s + '=').to_sym)
|
41
48
|
instance_variable_set("@#{s}", default)
|
42
49
|
end
|
50
|
+
private_class_method :rw_attr
|
43
51
|
|
44
52
|
def self.mod_attr2_accessor(symbol, symbol2, docstr = nil, default = nil)
|
45
53
|
rw_attr(symbol, default)
|
46
54
|
rw_attr(symbol2, default) unless symbol2.nil?
|
47
55
|
add_doc(symbol, docstr)
|
48
56
|
end
|
57
|
+
private_class_method :mod_attr2_accessor
|
49
58
|
|
50
59
|
def self.mod_attr_accessor(symbol, docstr = nil, default = nil)
|
51
60
|
mod_attr2_accessor(symbol, nil, docstr, default)
|
52
61
|
end
|
62
|
+
private_class_method :mod_attr_accessor
|
53
63
|
|
54
64
|
mod_attr_reader :doc, 'OpenAPI document.'
|
55
65
|
mod_attr_reader :outdir, 'Output directory name.'
|
56
|
-
mod_attr_reader :d, 'Other documents object.', Docs.new
|
66
|
+
mod_attr_reader :d, 'Other documents object.', OpenAPISourceTools::Docs.new
|
67
|
+
mod_attr_reader :wd, 'Original working directory', Dir.pwd
|
68
|
+
mod_attr_reader :configuration, 'Generator internal configuration'
|
69
|
+
mod_attr_accessor :config, 'Configuration file name for next gem or Ruby file.'
|
70
|
+
mod_attr_accessor :separator, 'Key separator in config file names.', nil
|
57
71
|
mod_attr_accessor :in_name, 'OpenAPI document name, nil if stdin.'
|
58
72
|
mod_attr_accessor :in_basename, 'OpenAPI document basename, nil if stdin.'
|
59
|
-
|
60
|
-
mod_attr_accessor :
|
61
|
-
mod_attr_accessor :a, 'Intended for instance with defined attributes.'
|
73
|
+
mod_attr_reader :g, 'Hash for storing values visible to all tasks.', {}
|
74
|
+
mod_attr_accessor :x, 'Hash for storing values visible to tasks from processor.', {}
|
62
75
|
mod_attr_accessor :h, 'Instance of class with helper methods.'
|
76
|
+
mod_attr_accessor :tasks, 'Tasks array.', []
|
63
77
|
mod_attr2_accessor :task, :t, 'Current task instance.'
|
64
78
|
mod_attr_accessor :task_index, 'Current task index.'
|
65
|
-
mod_attr_accessor :loaders, 'Array of
|
66
|
-
|
79
|
+
mod_attr_accessor :loaders, 'Array of processor loader methods.', []
|
80
|
+
mod_attr_accessor :output, 'Output-formatting helper.', OpenAPISourceTools::Output.new
|
81
|
+
|
82
|
+
def self.load_config(config_prefix)
|
83
|
+
cfg = {}
|
84
|
+
cfgs = OpenAPISourceTools::ConfigLoader.find_files(name_prefix: config_prefix)
|
85
|
+
cfgs = OpenAPISourceTools::ConfigLoader.read_contents(cfgs)
|
86
|
+
cfgs.each { |c| cfg.deep_merge!(c) }
|
87
|
+
cfg
|
88
|
+
end
|
89
|
+
private_class_method :load_config
|
67
90
|
|
68
|
-
def self.setup(document_content, input_name, output_directory)
|
91
|
+
def self.setup(document_content, input_name, output_directory, config_prefix)
|
69
92
|
@doc = document_content
|
70
93
|
@outdir = output_directory
|
71
94
|
unless input_name.nil?
|
72
95
|
@in_name = File.basename(input_name)
|
73
96
|
@in_basename = File.basename(input_name, '.*')
|
74
97
|
end
|
75
|
-
|
98
|
+
@configuration = load_config(config_prefix)
|
99
|
+
add_task(task: OpenAPISourceTools::HelperTask.new)
|
76
100
|
end
|
77
101
|
|
78
102
|
def self.add_task(task:, name: nil, executable: false, x: nil)
|
@@ -85,11 +109,12 @@ module Gen
|
|
85
109
|
end
|
86
110
|
|
87
111
|
def self.add_write_content(name:, content:, executable: false)
|
88
|
-
add_task(task: WriteTask.new(name, content, executable))
|
112
|
+
add_task(task: OpenAPISourceTools::WriteTask.new(name, content, executable))
|
89
113
|
end
|
90
114
|
|
91
115
|
def self.add(source:, template: nil, template_name: nil, name: nil, executable: false, x: nil)
|
92
|
-
add_task(task: Task.new(source, template, template_name),
|
116
|
+
add_task(task: OpenAPISourceTools::Task.new(source, template, template_name),
|
117
|
+
name:, executable:, x:)
|
93
118
|
end
|
94
119
|
|
95
120
|
def self.document
|