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
@@ -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.
|
data/lib/apiobjects.rb
DELETED
@@ -1,306 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative './common'
|
4
|
-
require 'set'
|
5
|
-
|
6
|
-
def same(a, b, ignored_keys = Set.new(%w[summary description]))
|
7
|
-
return a == b unless a.is_a?(Hash) && b.is_a?(Hash)
|
8
|
-
keys = Set.new(a.keys + b.keys) - ignored_keys
|
9
|
-
keys.to_a.each do |k|
|
10
|
-
return false unless a.key?(k) && b.key?(k)
|
11
|
-
return false unless same(a[k], b[k], ignored_keys)
|
12
|
-
end
|
13
|
-
true
|
14
|
-
end
|
15
|
-
|
16
|
-
def ref_string(name, schema_path)
|
17
|
-
"#{schema_path}/#{name}"
|
18
|
-
end
|
19
|
-
|
20
|
-
def reference(obj, schemas, schema_path, ignored_keys = Set.new(%w[summary description]), prefix = 'Schema')
|
21
|
-
# Check if identical schema has been added and if so, return the $ref string.
|
22
|
-
schemas.keys.sort.each do |k|
|
23
|
-
return ref_string(k, schema_path) if same(obj, schemas[k], ignored_keys)
|
24
|
-
end
|
25
|
-
# One of the numbers will not match existing keys. More number than keys.
|
26
|
-
(schemas.size + 1).times do |n|
|
27
|
-
# 'x' is to simplify find and replace (Schema1x vs Schema1 and Schema10)
|
28
|
-
k = "#{prefix}#{n}x"
|
29
|
-
next if schemas.key?(k)
|
30
|
-
schemas[k] = obj.merge
|
31
|
-
return ref_string(k, schema_path)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
class Components
|
36
|
-
attr_reader :path, :prefix, :anchor2ref, :schema_names
|
37
|
-
attr_accessor :items, :ignored_keys
|
38
|
-
|
39
|
-
def initialize(path, prefix, ignored_keys = %w[summary description examples example $anchor])
|
40
|
-
path = "#/#{path.join('/')}/" if path.is_a?(Array)
|
41
|
-
path = "#{path}/" unless path.end_with?('/')
|
42
|
-
@path = path
|
43
|
-
@prefix = prefix
|
44
|
-
@anchor2ref = {}
|
45
|
-
@schema_names = Set.new
|
46
|
-
@items = {}
|
47
|
-
@ignored_keys = Set.new(ignored_keys)
|
48
|
-
end
|
49
|
-
|
50
|
-
def add_options(opts)
|
51
|
-
opts.on('--use FIELD', 'Use FIELD in comparisons.') do |f|
|
52
|
-
@ignored_keys.delete(f)
|
53
|
-
end
|
54
|
-
opts.on('--ignore FIELD', 'Ignore FIELD in comparisons.') do |f|
|
55
|
-
@ignored_keys.add(f)
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
def help
|
60
|
-
%(All fields are used in object equality comparisons except:\n#{@ignored_keys.to_a.sort!.join("\n")})
|
61
|
-
end
|
62
|
-
|
63
|
-
def add_schema_name(name)
|
64
|
-
@schema_names.add(name)
|
65
|
-
end
|
66
|
-
|
67
|
-
def ref_string(name)
|
68
|
-
return nil if name.nil?
|
69
|
-
"#{@path}#{name}"
|
70
|
-
end
|
71
|
-
|
72
|
-
def reference(obj)
|
73
|
-
# Check if identical schema has been added. If so, return the $ref string.
|
74
|
-
@items.each do |k, v|
|
75
|
-
return ref_string(k) if same(obj, v, @ignored_keys)
|
76
|
-
end
|
77
|
-
# One of the numbers will not match existing keys. More number than keys.
|
78
|
-
(@items.size + 1).times do |n|
|
79
|
-
# 'x' is to simplify find and replace (Schema1x vs Schema1 and Schema10)
|
80
|
-
cand = "#{@prefix}#{n}x"
|
81
|
-
next if @items.key?(cand)
|
82
|
-
@items[cand] = obj.merge
|
83
|
-
@schema_names.add(cand)
|
84
|
-
return ref_string(cand)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def store_anchor(obj, ref = nil)
|
89
|
-
anchor_name = obj['$anchor']
|
90
|
-
return if anchor_name.nil?
|
91
|
-
ref = obj['$ref'] if ref.nil?
|
92
|
-
raise Exception, 'ref is nil and no $ref or it is nil' if ref.nil?
|
93
|
-
@anchor2ref[anchor_name] = ref
|
94
|
-
end
|
95
|
-
|
96
|
-
def alter_anchors
|
97
|
-
replacements = {}
|
98
|
-
@anchor2ref.each do |a, r|
|
99
|
-
next if @schema_names.member?(a)
|
100
|
-
replacements[a] = ref_string(a)
|
101
|
-
@schema_names.add(a)
|
102
|
-
end
|
103
|
-
replacements.each do |a, r|
|
104
|
-
@anchor2ref[a] = r
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
def anchor_ref_replacement(ref)
|
109
|
-
@anchor2ref[ref[1...ref.size]] || ref
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
class ServerPath
|
114
|
-
# Probably moves to a separate file once processpaths and frequencies receive
|
115
|
-
# some attention.
|
116
|
-
include Comparable
|
117
|
-
|
118
|
-
attr_accessor :parts
|
119
|
-
|
120
|
-
def initialize(parts)
|
121
|
-
@parts = parts
|
122
|
-
end
|
123
|
-
|
124
|
-
def <=>(other) # Variables are after fixed strings.
|
125
|
-
pp = other.is_a?(Array) ? other : p.parts
|
126
|
-
parts.each_index do |k|
|
127
|
-
return 1 if pp.size <= k # Longer comes after shorter.
|
128
|
-
pk = parts[k]
|
129
|
-
ppk = pp[k]
|
130
|
-
if pk.is_a? String
|
131
|
-
if ppk.is_a? String
|
132
|
-
c = pk <=> ppk
|
133
|
-
else
|
134
|
-
return -1
|
135
|
-
end
|
136
|
-
else
|
137
|
-
if ppk.is_a? String
|
138
|
-
return 1
|
139
|
-
else
|
140
|
-
c = pk.fetch('var', '') <=> ppk.fetch('var', '')
|
141
|
-
end
|
142
|
-
end
|
143
|
-
return c unless c.zero?
|
144
|
-
end
|
145
|
-
(parts.size < pp.size) ? -1 : 0
|
146
|
-
end
|
147
|
-
|
148
|
-
def compare(p, range = nil) # Not fit for sorting. Variable equals anything.
|
149
|
-
pp = p.is_a?(Array) ? p : p.parts
|
150
|
-
if range.nil?
|
151
|
-
range = 0...parts.size
|
152
|
-
elsif range.is_a? Number
|
153
|
-
range = range...(range + 1)
|
154
|
-
end
|
155
|
-
range.each do |k|
|
156
|
-
return 1 if pp.size <= k # Longer comes after shorter.
|
157
|
-
ppk = pp[k]
|
158
|
-
next unless ppk.is_a? String
|
159
|
-
pk = parts[k]
|
160
|
-
next unless pk.is_a? String
|
161
|
-
c = pk <=> ppk
|
162
|
-
return c unless c.zero?
|
163
|
-
end
|
164
|
-
(parts.size < pp.size) ? -1 : 0
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
# The rest probably ends up in a gem that orders schemas and does nothing else.
|
169
|
-
|
170
|
-
# Adds all refs found in the array to refs with given required state.
|
171
|
-
def gather_array_refs(refs, items, required)
|
172
|
-
items.each do |s|
|
173
|
-
r = s['$ref']
|
174
|
-
next if r.nil?
|
175
|
-
refs[r] = required || refs.fetch(r, false)
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
|
-
# For any key '$ref' adds to refs whether referred type is required.
|
180
|
-
# Requires that there are no in-lined schemas, openapi-addschemas has been run.
|
181
|
-
def gather_refs(refs, schema)
|
182
|
-
# This implies types mixed together according to examples. Needs mixed type.
|
183
|
-
# AND. Also, mixing may fail. Adds a new schema, do here.
|
184
|
-
items = schema['allOf']
|
185
|
-
return gather_array_refs(refs, items, true) unless items.nil?
|
186
|
-
# As long as one schema is fulfilled, it is ok. OR, first that fits.
|
187
|
-
items = schema['anyOf'] if items.nil?
|
188
|
-
# oneOf implies selection between different types. No multiple matches. XOR.
|
189
|
-
# Needs to ensure that later types do not match.
|
190
|
-
# Should check if there is enough difference to ensure single match.
|
191
|
-
# Use separate program run after addschemas to create allOf mixed schema
|
192
|
-
# and verify the others can be dealt with.
|
193
|
-
items = schema['oneOf'] if items.nil?
|
194
|
-
return gather_array_refs(refs, items, false) unless items.nil?
|
195
|
-
# Defaults below handle it if "type" is not "object".
|
196
|
-
reqs = schema.fetch('required', [])
|
197
|
-
schema.fetch('properties', {}).each do |name, spec|
|
198
|
-
r = spec['$ref']
|
199
|
-
next if r.nil?
|
200
|
-
refs[r] = reqs.include?(name) || refs.fetch(r, false)
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
class SchemaInfo
|
205
|
-
attr_accessor :ref, :schema, :direct_refs, :name, :post_refs
|
206
|
-
|
207
|
-
def initialize(ref, name, schema)
|
208
|
-
@ref = ref
|
209
|
-
@name = name
|
210
|
-
@schema = schema
|
211
|
-
@direct_refs = {}
|
212
|
-
gather_refs(@direct_refs, schema)
|
213
|
-
end
|
214
|
-
|
215
|
-
def set_post_refs(seen)
|
216
|
-
@post_refs = Set.new(@direct_refs.keys) - seen
|
217
|
-
end
|
218
|
-
|
219
|
-
def to_s
|
220
|
-
v = @direct_refs.keys.sort.map { |k| "#{k}:#{@direct_refs[k] ? 'req' : 'opt'}" }
|
221
|
-
"#{@ref}: #{v.join(' ')}"
|
222
|
-
end
|
223
|
-
end
|
224
|
-
|
225
|
-
def var_or_method_value(x, name)
|
226
|
-
if name.start_with?('@')
|
227
|
-
n = name
|
228
|
-
else
|
229
|
-
n = "@#{name}"
|
230
|
-
end
|
231
|
-
return x.instance_variable_get(n) if x.instance_variable_defined?(n)
|
232
|
-
return x.public_send(name) if x.respond_to?(name)
|
233
|
-
raise ArgumentError, "#{name} is not #{x.class} instance variable nor public method"
|
234
|
-
end
|
235
|
-
|
236
|
-
class SchemaOrderer
|
237
|
-
attr_accessor :schemas, :order, :orderer
|
238
|
-
|
239
|
-
def initialize(path, schema_specs)
|
240
|
-
@schemas = {}
|
241
|
-
schema_specs.each do |name, schema|
|
242
|
-
r = "#{path}#{name}"
|
243
|
-
@schemas[r] = SchemaInfo.new(r, name, schema)
|
244
|
-
end
|
245
|
-
end
|
246
|
-
|
247
|
-
def sort!(orderer = 'required_first')
|
248
|
-
case orderer
|
249
|
-
when 'required_first' then @order = required_first
|
250
|
-
when '<=>' then @order = @schemas.values.sort { |a, b| a <=> b }
|
251
|
-
else
|
252
|
-
@order = @schemas.values.sort do |a, b|
|
253
|
-
va = var_or_method_value(a, orderer)
|
254
|
-
vb = var_or_method_value(b, orderer)
|
255
|
-
va <=> vb
|
256
|
-
end
|
257
|
-
end
|
258
|
-
@orderer = orderer
|
259
|
-
seen = Set.new
|
260
|
-
@order.each do |si|
|
261
|
-
si.set_post_refs(seen)
|
262
|
-
seen.add(si.ref)
|
263
|
-
end
|
264
|
-
@order
|
265
|
-
end
|
266
|
-
|
267
|
-
def required_first
|
268
|
-
chosen = []
|
269
|
-
until chosen.size == @schemas.size
|
270
|
-
used = Set.new(chosen.map { |si| si.ref })
|
271
|
-
avail = @schemas.values.select { |si| !used.member?(si.ref) }
|
272
|
-
best = nil
|
273
|
-
avail.each do |si|
|
274
|
-
prereq = chosen.count { |x| x.direct_refs.fetch(si.ref, false) }
|
275
|
-
fulfilled = chosen.count { |x| si.direct_refs.fetch(x.ref, false) }
|
276
|
-
postreq = si.direct_refs.size - (prereq + fulfilled)
|
277
|
-
better = false
|
278
|
-
if best.nil?
|
279
|
-
better = true
|
280
|
-
else
|
281
|
-
# Minimize preceding types requiring this.
|
282
|
-
if prereq < best.first
|
283
|
-
better = true
|
284
|
-
elsif prereq == best.first
|
285
|
-
# Minimize remaining unfulfilled requires.
|
286
|
-
if postreq < best[1]
|
287
|
-
better = true
|
288
|
-
elsif postreq == best[1]
|
289
|
-
# Check mutual direct requirements.
|
290
|
-
best_req_si = best.last.direct_refs.fetch(si.ref, false)
|
291
|
-
si_req_best = si.direct_refs.fetch(best.last.ref, false)
|
292
|
-
if best_req_si
|
293
|
-
better = true unless si_req_best
|
294
|
-
end
|
295
|
-
# Order by name if no other difference.
|
296
|
-
better = si.ref < best.last.ref unless better
|
297
|
-
end
|
298
|
-
end
|
299
|
-
end
|
300
|
-
best = [ prereq, postreq, si ] if better
|
301
|
-
end
|
302
|
-
chosen.push(best.last)
|
303
|
-
end
|
304
|
-
chosen
|
305
|
-
end
|
306
|
-
end
|
data/lib/common.rb
DELETED
@@ -1,114 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Copyright © 2021-2024 Ismo Kärkkäinen
|
4
|
-
# Licensed under Universal Permissive License. See LICENSE.txt.
|
5
|
-
|
6
|
-
require 'pathname'
|
7
|
-
|
8
|
-
|
9
|
-
def aargh(message, return_value = nil)
|
10
|
-
message = message.map(&:to_s).join("\n") if message.is_a? Array
|
11
|
-
$stderr.puts message
|
12
|
-
return_value
|
13
|
-
end
|
14
|
-
|
15
|
-
def yesno(boolean)
|
16
|
-
boolean ? 'yes' : 'no'
|
17
|
-
end
|
18
|
-
|
19
|
-
def bury(doc, path, value)
|
20
|
-
(path.size - 1).times do |k|
|
21
|
-
p = path[k]
|
22
|
-
doc[p] = {} unless doc.key?(p)
|
23
|
-
doc = doc[p]
|
24
|
-
end
|
25
|
-
doc[path.last] = value
|
26
|
-
end
|
27
|
-
|
28
|
-
module Out
|
29
|
-
attr_reader :count
|
30
|
-
|
31
|
-
def put(message)
|
32
|
-
aargh(message)
|
33
|
-
count += 1
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def split_path(p, spec = false)
|
38
|
-
parts = []
|
39
|
-
p = p.strip
|
40
|
-
unless spec
|
41
|
-
q = p.index('?')
|
42
|
-
p.slice!(0...q) unless q.nil?
|
43
|
-
end
|
44
|
-
p.split('/').each do |s|
|
45
|
-
next if s.empty?
|
46
|
-
s = { var: s } if spec && s.include?('{')
|
47
|
-
parts.push(s)
|
48
|
-
end
|
49
|
-
parts
|
50
|
-
end
|
51
|
-
|
52
|
-
def load_source(input)
|
53
|
-
YAML.safe_load(input.nil? ? $stdin : File.read(input))
|
54
|
-
rescue Errno::ENOENT
|
55
|
-
aargh "Could not load #{input || 'stdin'}"
|
56
|
-
rescue StandardError => e
|
57
|
-
aargh "#{e}\nFailed to read #{input || 'stdin'}"
|
58
|
-
end
|
59
|
-
|
60
|
-
def dump_result(output, doc, error_return)
|
61
|
-
doc = YAML.dump(doc, line_width: 1_000_000) unless doc.is_a?(String)
|
62
|
-
if output.nil?
|
63
|
-
$stdout.puts doc
|
64
|
-
else
|
65
|
-
fp = Pathname.new output
|
66
|
-
fp.open('w') do |f|
|
67
|
-
f.puts doc
|
68
|
-
end
|
69
|
-
end
|
70
|
-
0
|
71
|
-
rescue StandardError => e
|
72
|
-
aargh([ e, "Failed to write output: #{output || 'stdout'}" ], error_return)
|
73
|
-
end
|
74
|
-
|
75
|
-
ServerPath = Struct.new(:parts) do
|
76
|
-
# Variables are after fixed strings.
|
77
|
-
def <=>(other)
|
78
|
-
pp = other.is_a?(Array) ? other : other.parts
|
79
|
-
parts.each_index do |k|
|
80
|
-
return 1 if pp.size <= k # Longer comes after shorter.
|
81
|
-
pk = parts[k]
|
82
|
-
ppk = pp[k]
|
83
|
-
if pk.is_a? String
|
84
|
-
return -1 unless ppk.is_a? String
|
85
|
-
c = pk <=> ppk
|
86
|
-
else
|
87
|
-
return 1 if ppk.is_a? String
|
88
|
-
c = pk.fetch('var', '') <=> ppk.fetch('var', '')
|
89
|
-
end
|
90
|
-
return c unless c.zero?
|
91
|
-
end
|
92
|
-
(parts.size < pp.size) ? -1 : 0
|
93
|
-
end
|
94
|
-
|
95
|
-
# Not fit for sorting. Variable equals anything.
|
96
|
-
def compare(p, range = nil)
|
97
|
-
pp = p.is_a?(Array) ? p : p.parts
|
98
|
-
if range.nil?
|
99
|
-
range = 0...parts.size
|
100
|
-
elsif range.is_a? Number
|
101
|
-
range = range...(range + 1)
|
102
|
-
end
|
103
|
-
range.each do |k|
|
104
|
-
return 1 if pp.size <= k # Longer comes after shorter.
|
105
|
-
ppk = pp[k]
|
106
|
-
next unless ppk.is_a? String
|
107
|
-
pk = parts[k]
|
108
|
-
next unless pk.is_a? String
|
109
|
-
c = pk <=> ppk
|
110
|
-
return c unless c.zero?
|
111
|
-
end
|
112
|
-
(parts.size < pp.size) ? -1 : 0
|
113
|
-
end
|
114
|
-
end
|