openapi-sourcetools 0.12.0 → 0.13
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-addmediatypes +59 -0
- data/bin/openapi-addrequestbodies +57 -0
- data/bin/openapi-addschemas +30 -14
- data/bin/openapi-generate-checkconfig +73 -0
- data/bin/openapi-logicsplit +96 -0
- data/bin/openapi-order +21 -12
- data/bin/openapi-schema-in +143 -0
- data/bin/openapi-schema-ls +100 -0
- data/bin/openapi-schema-out +144 -0
- data/lib/openapi/sourcetools/apiobjects.rb +19 -13
- data/lib/openapi/sourcetools/common.rb +1 -1
- data/lib/openapi/sourcetools/config.rb +3 -2
- data/lib/openapi/sourcetools/generate.rb +1 -0
- data/lib/openapi/sourcetools/loaders.rb +3 -3
- data/lib/openapi/sourcetools/order.rb +42 -18
- data/lib/openapi/sourcetools/schemaio.rb +264 -0
- data/lib/openapi/sourcetools/task.rb +1 -1
- data/lib/openapi/sourcetools/version.rb +2 -1
- metadata +17 -2
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Copyright © 2026 Ismo Kärkkäinen
|
|
5
|
+
# Licensed under Universal Permissive License. See LICENSE.txt.
|
|
6
|
+
|
|
7
|
+
require_relative '../lib/openapi/sourcetools/common'
|
|
8
|
+
require_relative '../lib/openapi/sourcetools/schemaio'
|
|
9
|
+
require 'optparse'
|
|
10
|
+
require 'yaml'
|
|
11
|
+
require 'json'
|
|
12
|
+
include OpenAPISourceTools
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Functions related to saving the document path dump.
|
|
16
|
+
|
|
17
|
+
def to_json(doc)
|
|
18
|
+
JSON.dump(doc)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_pretty_json(doc)
|
|
22
|
+
JSON.pretty_generate(doc)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def to_yaml(doc)
|
|
26
|
+
YAML.dump(doc)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def main
|
|
30
|
+
input_file = nil
|
|
31
|
+
output_file = nil
|
|
32
|
+
doc_paths = :openapi
|
|
33
|
+
output_default = nil
|
|
34
|
+
format = 'pretty_json'
|
|
35
|
+
formats = {
|
|
36
|
+
'yaml' => method(:to_yaml),
|
|
37
|
+
'json' => method(:to_json),
|
|
38
|
+
'pretty_json' => method(:to_pretty_json)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
parser = OptionParser.new do |opts|
|
|
42
|
+
opts.summary_indent = ' '
|
|
43
|
+
opts.summary_width = 22
|
|
44
|
+
opts.banner = 'Usage: openapi-schema-ls [options]'
|
|
45
|
+
opts.separator ''
|
|
46
|
+
opts.separator 'Options:'
|
|
47
|
+
opts.on('-i', '--input FILE', 'Read input from FILE, not stdin.') do |f|
|
|
48
|
+
input_file = f
|
|
49
|
+
end
|
|
50
|
+
opts.on('-o', '--output FILE', 'Write result to FILE, not stdout.') do |f|
|
|
51
|
+
output_file = f
|
|
52
|
+
end
|
|
53
|
+
opts.on('--inputpaths DATA', 'Input path data.') do |data|
|
|
54
|
+
doc_paths = data
|
|
55
|
+
end
|
|
56
|
+
opts.on('--dump DATA', 'Dump internal path data to output and exit.') do |data|
|
|
57
|
+
output_default = data
|
|
58
|
+
end
|
|
59
|
+
opts.on('-f', '--format FORMAT', "DATA output format: #{formats.keys.sort!.join(', ')}.") do |fmt|
|
|
60
|
+
format = fmt
|
|
61
|
+
end
|
|
62
|
+
opts.on('-h', '--help', 'Print this help and exit.') do
|
|
63
|
+
$stdout.puts <<EOF
|
|
64
|
+
#{opts}
|
|
65
|
+
Lists named schemas in the input document.
|
|
66
|
+
|
|
67
|
+
The document paths DATA is either a file name, internal data name, or a
|
|
68
|
+
JSON-encoded object with necessary keys. Same data also applies to both
|
|
69
|
+
openapi-schema-in and openapi-schema-out. Internal data names are:
|
|
70
|
+
#{SchemaIO::BUILTINS.keys.sort!.join(', ')}
|
|
71
|
+
|
|
72
|
+
DATA defaults to OpenAPI format.
|
|
73
|
+
EOF
|
|
74
|
+
return 0
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
parser.order!
|
|
78
|
+
|
|
79
|
+
return Common.aargh("Unknown format: #{format}", 1) unless formats.key?(format)
|
|
80
|
+
|
|
81
|
+
unless output_default.nil?
|
|
82
|
+
doc = SchemaIO.get(output_default)
|
|
83
|
+
return Commpon.aargh("Not internal data format: #{output_default}", 1) if doc.nil?
|
|
84
|
+
out = (format == 'yaml') ? doc : formats[format].call(YAML.safe_load(doc))
|
|
85
|
+
return Common.dump_result(output_file, out, 3)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
dp = SchemaIO.key_data(doc_paths)
|
|
89
|
+
return Common.aargh("Not file or internal format name or format as JSON object: #{doc_paths}", 2) if dp.nil?
|
|
90
|
+
|
|
91
|
+
doc = Common.load_source(input_file)
|
|
92
|
+
return 2 if doc.nil?
|
|
93
|
+
|
|
94
|
+
src = SchemaIO::SchemaAccess.new(dp, doc)
|
|
95
|
+
out = src.list_schema_names.join("\n")
|
|
96
|
+
|
|
97
|
+
Common.dump_result(output_file, out, 3)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
exit(main) if File.basename(__FILE__) == File.basename($PROGRAM_NAME)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Copyright © 2026 Ismo Kärkkäinen
|
|
5
|
+
# Licensed under Universal Permissive License. See LICENSE.txt.
|
|
6
|
+
|
|
7
|
+
require_relative '../lib/openapi/sourcetools/common'
|
|
8
|
+
require_relative '../lib/openapi/sourcetools/schemaio'
|
|
9
|
+
require 'optparse'
|
|
10
|
+
require 'yaml'
|
|
11
|
+
require 'json'
|
|
12
|
+
include OpenAPISourceTools
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Functions related to saving the output.
|
|
16
|
+
|
|
17
|
+
def to_json(doc, _line_width)
|
|
18
|
+
JSON.dump(doc)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_pretty_json(doc, _line_width)
|
|
22
|
+
JSON.pretty_generate(doc)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def to_yaml(doc, line_width)
|
|
26
|
+
YAML.dump(doc, line_width: line_width)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def main
|
|
30
|
+
input_file = nil
|
|
31
|
+
output_file = nil
|
|
32
|
+
name = nil
|
|
33
|
+
refs = true
|
|
34
|
+
format = 'pretty_json'
|
|
35
|
+
formats = {
|
|
36
|
+
'yaml' => method(:to_yaml),
|
|
37
|
+
'json' => method(:to_json),
|
|
38
|
+
'pretty_json' => method(:to_pretty_json)
|
|
39
|
+
}
|
|
40
|
+
line_width = 80
|
|
41
|
+
doc_paths = :openapi
|
|
42
|
+
schema_paths = :jsonschema
|
|
43
|
+
additions = {}
|
|
44
|
+
|
|
45
|
+
parser = OptionParser.new do |opts|
|
|
46
|
+
opts.summary_indent = ' '
|
|
47
|
+
opts.summary_width = 27
|
|
48
|
+
opts.banner = 'Usage: openapi-schema-out [options]'
|
|
49
|
+
opts.separator ''
|
|
50
|
+
opts.separator 'Options:'
|
|
51
|
+
opts.on('-i', '--input FILE', 'Read input from FILE, not stdin.') do |f|
|
|
52
|
+
input_file = f
|
|
53
|
+
end
|
|
54
|
+
opts.on('-o', '--output FILE', 'Write result to FILE, not stdout.') do |f|
|
|
55
|
+
output_file = f
|
|
56
|
+
end
|
|
57
|
+
opts.on('-n', '--name NAME', 'Use schema NAME.') do |n|
|
|
58
|
+
name = n
|
|
59
|
+
end
|
|
60
|
+
opts.on('--[no-]refs', "Include referred schemas (default #{Common.yesno(refs)}).") do |r|
|
|
61
|
+
refs = r
|
|
62
|
+
end
|
|
63
|
+
opts.on('-f', '--format FORMAT', "Schema output format: #{formats.keys.sort!.join(', ')}.") do |fmt|
|
|
64
|
+
format = fmt
|
|
65
|
+
end
|
|
66
|
+
opts.on('-w', '--width WIDTH', "Set YAML line width, default: #{line_width}") do |w|
|
|
67
|
+
line_width = w.to_i
|
|
68
|
+
end
|
|
69
|
+
opts.on('--inputpaths DATA', 'Input path data.') do |data|
|
|
70
|
+
doc_paths = data
|
|
71
|
+
end
|
|
72
|
+
opts.on('--schemapaths DATA', 'Schema path data.') do |data|
|
|
73
|
+
schema_paths = data
|
|
74
|
+
end
|
|
75
|
+
opts.on('-a', '--add KEY:[json:]VALUE', 'Add KEY with (JSON) VALUE to output schema.') do |kv|
|
|
76
|
+
k, v = kv.split(':', 2)
|
|
77
|
+
return Common.aargh("Invalid key-value pair: #{kv}", 1) if k.nil? || v.nil?
|
|
78
|
+
return Common.aargh("Empty key: #{kv}", 1) if k.empty?
|
|
79
|
+
v = JSON.parse(v[5..]) if v.downcase.start_with?('json:')
|
|
80
|
+
additions[k] = v
|
|
81
|
+
rescue JSON::ParserError
|
|
82
|
+
return Common.aargh("Invalid JSON in argument value: #{kv}", 1)
|
|
83
|
+
end
|
|
84
|
+
opts.on('-h', '--help', 'Print this help and exit.') do
|
|
85
|
+
$stdout.puts <<EOF
|
|
86
|
+
#{opts}
|
|
87
|
+
Takes schema with name from input and places it into output.
|
|
88
|
+
|
|
89
|
+
The document and schema paths DATA is either a file name, internal data name,
|
|
90
|
+
or a JSON-encoded object with necessary keys. Internal data names are:
|
|
91
|
+
#{SchemaIO::BUILTINS.keys.sort!.join(', ')}
|
|
92
|
+
|
|
93
|
+
Depending on input, schema NAME is used to find and store the schema in the
|
|
94
|
+
location indicated by DATA for output.
|
|
95
|
+
|
|
96
|
+
DATA values default to input being OpenAPI format and output is JSON schema
|
|
97
|
+
with schema at root and referenced schemas, if output, under "$defs" key.
|
|
98
|
+
|
|
99
|
+
Adding KEY:VALUE pairs can be used to add keys to the output schema with
|
|
100
|
+
string values. Using KEY:json:VALUE will parse VALUE as JSON and use that.
|
|
101
|
+
The format indicator is case-insensitive. Value can be empty but key cannot
|
|
102
|
+
be empty. Pass value starting with "json:" as 'key:json:"json:the_rest"'.
|
|
103
|
+
Usable for adding "$schema" and "$id" keys to the output schema.
|
|
104
|
+
EOF
|
|
105
|
+
return 0
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
parser.order!
|
|
109
|
+
|
|
110
|
+
return Common.aargh('Schema name must be given.', 1) if name.nil?
|
|
111
|
+
|
|
112
|
+
return Common.aargh("Unknown format: #{format}", 1) unless formats.key?(format)
|
|
113
|
+
|
|
114
|
+
dp = SchemaIO.key_data(doc_paths)
|
|
115
|
+
return Common.aargh("Not file or internal format name or format as JSON object: #{doc_paths}", 2) if dp.nil?
|
|
116
|
+
sp = SchemaIO.key_data(schema_paths)
|
|
117
|
+
return Common.aargh("Not file or internal format name or format as JSON object: #{schema_paths}", 2) if sp.nil?
|
|
118
|
+
|
|
119
|
+
return Common.aargh(%(Name "#{name}" has document path separator "#{dp.separator}"), 1) if name.include?(dp.separator)
|
|
120
|
+
return Common.aargh(%(Name "#{name}" has schema path separator "#{sp.separator}"), 1) if name.include?(sp.separator)
|
|
121
|
+
|
|
122
|
+
doc = Common.load_source(input_file)
|
|
123
|
+
return 2 if doc.nil?
|
|
124
|
+
return Common.aargh('Document is not an object.', 2) unless doc.is_a?(Hash)
|
|
125
|
+
|
|
126
|
+
src = SchemaIO::SchemaAccess.new(dp, doc)
|
|
127
|
+
tgt = SchemaIO::SchemaAccess.new(sp, {})
|
|
128
|
+
|
|
129
|
+
desired = src.find_named_schema(name)
|
|
130
|
+
return Common.aargh("Schema not found: #{name}", 1) if desired.nil?
|
|
131
|
+
desired.merge!(additions) unless additions.empty?
|
|
132
|
+
tgt.put_named_schema(name, desired)
|
|
133
|
+
|
|
134
|
+
if refs
|
|
135
|
+
refd = src.find_referenced_schemas(name, desired)
|
|
136
|
+
refd.each { |n, s| tgt.put_referenced_schema(n, s) }
|
|
137
|
+
end
|
|
138
|
+
SchemaIO.alter_refs(tgt.document, src, tgt) # After referenced schemas have been found.
|
|
139
|
+
|
|
140
|
+
out = formats[format].call(tgt.document, line_width)
|
|
141
|
+
Common.dump_result(output_file, out, 3)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
exit(main) if File.basename(__FILE__) == File.basename($PROGRAM_NAME)
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Copyright © 2024-
|
|
3
|
+
# Copyright © 2024-2026 Ismo Kärkkäinen
|
|
4
4
|
# Licensed under Universal Permissive License. See LICENSE.txt.
|
|
5
5
|
|
|
6
6
|
module OpenAPISourceTools
|
|
7
|
+
# Various schema-related constants.
|
|
8
|
+
module Schemas
|
|
9
|
+
ARRAY_VALUED = %w[allOf anyOf oneOf prefixItems].freeze
|
|
10
|
+
OTHER_VALUED = %w[items additionalProperties unevaluatedItems unevaluatedProperties if then else not].freeze
|
|
11
|
+
KEY2SCHEMA = %w[properties patternProperties dependentSchemas].freeze
|
|
12
|
+
HANDLED = [].concat(ARRAY_VALUED, OTHER_VALUED, KEY2SCHEMA).freeze
|
|
13
|
+
IGNORED = %w[example examples].freeze
|
|
14
|
+
end
|
|
15
|
+
|
|
7
16
|
# Various classes for handling objects in the API specification.
|
|
8
17
|
# Used in various programs.
|
|
9
18
|
module ApiObjects
|
|
19
|
+
LOGIC_KEYS = Set.new(%w[allOf anyOf oneOf not if then else dependentSchemas]).freeze
|
|
20
|
+
DOCUMENT_KEYS = %w[example examples title summary description deprecated readOnly writeOnly].freeze
|
|
10
21
|
|
|
11
22
|
def self.same(a, b, ignored_keys = Set.new(%w[summary description]))
|
|
12
23
|
return a == b unless a.is_a?(Hash) && b.is_a?(Hash)
|
|
@@ -89,7 +100,7 @@ module OpenAPISourceTools
|
|
|
89
100
|
next if value.key?('$ref')
|
|
90
101
|
@components.to_reference_object(value)
|
|
91
102
|
end
|
|
92
|
-
|
|
103
|
+
elsif !Schemas::IGNORED.include?(key)
|
|
93
104
|
replace(value)
|
|
94
105
|
end
|
|
95
106
|
end
|
|
@@ -114,6 +125,7 @@ module OpenAPISourceTools
|
|
|
114
125
|
obj.each do |key, value|
|
|
115
126
|
if key == @key && value.is_a?(Hash)
|
|
116
127
|
value.keys.sort!.each do |sub_key|
|
|
128
|
+
next if Schemas::IGNORED.include?(sub_key)
|
|
117
129
|
sub_value = value[sub_key]
|
|
118
130
|
next unless sub_value.is_a?(Hash)
|
|
119
131
|
next if sub_value.key?('$ref')
|
|
@@ -173,7 +185,7 @@ module OpenAPISourceTools
|
|
|
173
185
|
@items.each do |k, v|
|
|
174
186
|
return ref_string(k) if ApiObjects.same(obj, v, @ignored_keys)
|
|
175
187
|
end
|
|
176
|
-
# One of the numbers will not match existing keys. More
|
|
188
|
+
# One of the numbers will not match existing keys. More numbers than keys.
|
|
177
189
|
(@items.size + 1).times do |n|
|
|
178
190
|
# 'x' is to simplify find and replace (Schema1x vs Schema1 and Schema10)
|
|
179
191
|
cand = "#{@prefix}#{n}x"
|
|
@@ -240,17 +252,11 @@ module OpenAPISourceTools
|
|
|
240
252
|
pk = @parts[k]
|
|
241
253
|
ppk = pp[k]
|
|
242
254
|
if pk.key?('fixed')
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
else
|
|
246
|
-
return -1
|
|
247
|
-
end
|
|
255
|
+
return -1 unless ppk.key?('fixed')
|
|
256
|
+
c = pk['fixed'] <=> ppk['fixed']
|
|
248
257
|
else
|
|
249
|
-
if ppk.key?('fixed')
|
|
250
|
-
|
|
251
|
-
else
|
|
252
|
-
c = pk.fetch('parameter', '') <=> ppk.fetch('parameter', '')
|
|
253
|
-
end
|
|
258
|
+
return 1 if ppk.key?('fixed')
|
|
259
|
+
c = pk.fetch('parameter', '') <=> ppk.fetch('parameter', '')
|
|
254
260
|
end
|
|
255
261
|
return c unless c.zero?
|
|
256
262
|
end
|
|
@@ -10,7 +10,7 @@ module OpenAPISourceTools
|
|
|
10
10
|
# Common methods used in programs and elsewhere gathered into one place.
|
|
11
11
|
module Common
|
|
12
12
|
def self.aargh(message, return_value = nil)
|
|
13
|
-
message = message.
|
|
13
|
+
message = message.join("\n") if message.is_a? Array
|
|
14
14
|
$stderr.puts message
|
|
15
15
|
return_value
|
|
16
16
|
end
|
|
@@ -8,11 +8,11 @@ require_relative 'gen'
|
|
|
8
8
|
require 'find'
|
|
9
9
|
require 'yaml'
|
|
10
10
|
|
|
11
|
+
# Gem namespace.
|
|
11
12
|
module OpenAPISourceTools
|
|
12
13
|
# Configuration file find and load convenience functions.
|
|
13
14
|
# See the first 3 methods. The rest are intended to be internal helpers.
|
|
14
15
|
module ConfigLoader
|
|
15
|
-
|
|
16
16
|
# A function to find all files with a given prefix.
|
|
17
17
|
# Prefix is taken from Gen.config if nil.
|
|
18
18
|
# Returns an array of ConfigFileInfo objects.
|
|
@@ -43,9 +43,10 @@ module OpenAPISourceTools
|
|
|
43
43
|
|
|
44
44
|
# Maps an array of ConfigFileInfo objects to an array of their contents.
|
|
45
45
|
def self.contents_array(config_file_infos)
|
|
46
|
-
config_file_infos.map(&:content).
|
|
46
|
+
config_file_infos.map(&:content).compact
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
+
# The result with data about the config file.
|
|
49
50
|
class ConfigFileInfo
|
|
50
51
|
include Comparable
|
|
51
52
|
|
|
@@ -82,7 +82,7 @@ module OpenAPISourceTools
|
|
|
82
82
|
d = name.downcase
|
|
83
83
|
if d.start_with?(YAML_PREFIX)
|
|
84
84
|
name = name.slice(YAML_PREFIX.size...name.size)
|
|
85
|
-
elsif
|
|
85
|
+
elsif YAML_EXTS.index { |s| d.end_with?(s) }.nil?
|
|
86
86
|
return false
|
|
87
87
|
end
|
|
88
88
|
n, _sep, f = name.partition(':')
|
|
@@ -140,9 +140,9 @@ module OpenAPISourceTools
|
|
|
140
140
|
method(:req_loader),
|
|
141
141
|
method(:eval_loader),
|
|
142
142
|
method(:ruby_loader),
|
|
143
|
+
method(:config_loader),
|
|
143
144
|
method(:yaml_loader),
|
|
144
145
|
method(:bin_loader),
|
|
145
|
-
method(:config_loader),
|
|
146
146
|
method(:separator_loader)
|
|
147
147
|
]
|
|
148
148
|
end
|
|
@@ -153,7 +153,7 @@ module OpenAPISourceTools
|
|
|
153
153
|
- #{Loaders::EVAL_PREFIX}code : runs code to add gem tasks again.
|
|
154
154
|
- ruby_file#{Loaders::RUBY_EXT} : changes to Ruby file directory and requires the file.
|
|
155
155
|
- #{Loaders::YAML_PREFIX}name:filename : Loads YAML file into Gen.d.name.
|
|
156
|
-
- name:filename.{#{
|
|
156
|
+
- name:filename.{#{Loaders::YAML_EXTS.map { |s| s[1...s.size] }.join('|')}} : Loads YAML file into Gen.d.name.
|
|
157
157
|
- #{Loaders::BIN_PREFIX}name:filename : Loads binary file into Gen.d.name.
|
|
158
158
|
- #{Loaders::CONFIG_PREFIX}name : Sets Gen.config for next gem/Ruby file configuration loading.
|
|
159
159
|
- #{Loaders::SEPARATOR_PREFIX}string : Sets Gen.separator to string.
|
|
@@ -10,7 +10,7 @@ module OpenAPISourceTools
|
|
|
10
10
|
# Used as replacement for '*'.
|
|
11
11
|
ANY = Regexp.new('^.*$')
|
|
12
12
|
|
|
13
|
-
def self.array_item_compare(a, b)
|
|
13
|
+
def self.array_item_compare(a, b, patterns)
|
|
14
14
|
a[:values].size.times do |k|
|
|
15
15
|
av = a[:values][k]
|
|
16
16
|
bv = b[:values][k]
|
|
@@ -18,8 +18,14 @@ module OpenAPISourceTools
|
|
|
18
18
|
return 1 unless bv.nil?
|
|
19
19
|
else
|
|
20
20
|
return -1 if bv.nil?
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
if (av.is_a?(TrueClass) || av.is_a?(FalseClass)) && (bv.is_a?(TrueClass) || bv.is_a?(FalseClass))
|
|
22
|
+
r = (av ? 1 : 0) <=> (bv ? 1 : 0)
|
|
23
|
+
else
|
|
24
|
+
r = av <=> bv
|
|
25
|
+
end
|
|
26
|
+
next if r.nil? || r.zero?
|
|
27
|
+
r = -r unless patterns[k].ascending
|
|
28
|
+
return r
|
|
23
29
|
end
|
|
24
30
|
end
|
|
25
31
|
0
|
|
@@ -129,18 +135,23 @@ module OpenAPISourceTools
|
|
|
129
135
|
# Hence items.size, and the more multiple, the earlier.
|
|
130
136
|
d = @items.size <=> other.items.size
|
|
131
137
|
return d unless d.zero?
|
|
132
|
-
-@items.count(:multi) <=> -other.items.count(:multi)
|
|
138
|
+
d = -@items.count(:multi) <=> -other.items.count(:multi)
|
|
139
|
+
return d unless d.zero?
|
|
140
|
+
# Questionable if I should compare to the object_id or equality.
|
|
141
|
+
# Now if you use same regular expression, they are the same.
|
|
142
|
+
-@items.count(ANY) <=> -other.items.count(ANY)
|
|
133
143
|
end
|
|
134
144
|
end
|
|
135
145
|
|
|
136
146
|
# Book-keeping class for pattern and its location.
|
|
137
147
|
class KeyPattern
|
|
138
|
-
attr_reader :pattern, :index, :priority
|
|
148
|
+
attr_reader :pattern, :index, :priority, :ascending
|
|
139
149
|
|
|
140
|
-
def initialize(pattern, index, priority)
|
|
150
|
+
def initialize(pattern, index, priority, sort_operator)
|
|
141
151
|
@pattern = pattern
|
|
142
152
|
@index = index
|
|
143
153
|
@priority = priority
|
|
154
|
+
@ascending = sort_operator == '<'
|
|
144
155
|
end
|
|
145
156
|
|
|
146
157
|
def match?(string)
|
|
@@ -160,22 +171,34 @@ module OpenAPISourceTools
|
|
|
160
171
|
false
|
|
161
172
|
end
|
|
162
173
|
|
|
174
|
+
def self.ordervalue(item)
|
|
175
|
+
return [ item, '<' ] if item.is_a?(String)
|
|
176
|
+
return nil unless item.is_a?(Hash)
|
|
177
|
+
return nil unless item.size == 1
|
|
178
|
+
key = item.keys.first
|
|
179
|
+
return nil unless key.is_a?(String)
|
|
180
|
+
return nil unless ['<', '>'].include?(item[key])
|
|
181
|
+
[ key, item[key] ]
|
|
182
|
+
end
|
|
183
|
+
|
|
163
184
|
attr_reader :patterns
|
|
164
185
|
|
|
165
186
|
def initialize(keys, regexp_prefix)
|
|
166
187
|
@patterns = []
|
|
167
188
|
has_asterisk = false
|
|
168
|
-
keys.each_with_index do |
|
|
189
|
+
keys.each_with_index do |ov, idx|
|
|
190
|
+
string = ov.first
|
|
191
|
+
sort_operator = ov.last
|
|
169
192
|
if string.start_with?(regexp_prefix)
|
|
170
|
-
@patterns.push(KeyPattern.new(Regexp.new(string[regexp_prefix.size..]), idx, idx))
|
|
193
|
+
@patterns.push(KeyPattern.new(Regexp.new(string[regexp_prefix.size..]), idx, idx, sort_operator))
|
|
171
194
|
elsif string == '*'
|
|
172
|
-
@patterns.push(KeyPattern.new(Regexp.new('^.*$'), idx, keys.size)) unless has_asterisk # First used.
|
|
195
|
+
@patterns.push(KeyPattern.new(Regexp.new('^.*$'), idx, keys.size, sort_operator)) unless has_asterisk # First used.
|
|
173
196
|
has_asterisk = true
|
|
174
197
|
else
|
|
175
|
-
@patterns.push(KeyPattern.new(Regexp.new("^#{Regexp.escape(string)}$"), idx, idx))
|
|
198
|
+
@patterns.push(KeyPattern.new(Regexp.new("^#{Regexp.escape(string)}$"), idx, idx, sort_operator))
|
|
176
199
|
end
|
|
177
200
|
end
|
|
178
|
-
@patterns.push(KeyPattern.new(Regexp.new('^.*$'), @patterns.size, keys.size + 1)) unless has_asterisk
|
|
201
|
+
@patterns.push(KeyPattern.new(Regexp.new('^.*$'), @patterns.size, keys.size + 1, '<')) unless has_asterisk
|
|
179
202
|
end
|
|
180
203
|
|
|
181
204
|
def apply_array(array)
|
|
@@ -207,13 +230,13 @@ module OpenAPISourceTools
|
|
|
207
230
|
end
|
|
208
231
|
values.push(amended)
|
|
209
232
|
end
|
|
210
|
-
values.sort! { |a, b| OpenAPISourceTools::Ordering.array_item_compare(a, b) }
|
|
233
|
+
values.sort! { |a, b| OpenAPISourceTools::Ordering.array_item_compare(a, b, @patterns) }
|
|
211
234
|
values.each_with_index { |amended, i| array[i] = amended[:item] }
|
|
212
235
|
true
|
|
213
236
|
end
|
|
214
237
|
|
|
215
|
-
def apply_hash(
|
|
216
|
-
return true if
|
|
238
|
+
def apply_hash(unordered_hash)
|
|
239
|
+
return true if unordered_hash.size < 2
|
|
217
240
|
groups = @patterns.map do |kp|
|
|
218
241
|
{
|
|
219
242
|
kp: kp,
|
|
@@ -221,7 +244,7 @@ module OpenAPISourceTools
|
|
|
221
244
|
}
|
|
222
245
|
end
|
|
223
246
|
s2orig = {}
|
|
224
|
-
|
|
247
|
+
unordered_hash.each_key do |key|
|
|
225
248
|
skey = key.is_a?(String) ? key : key.to_s
|
|
226
249
|
s2orig[skey] = key
|
|
227
250
|
best_priority = groups.size + 1
|
|
@@ -238,10 +261,11 @@ module OpenAPISourceTools
|
|
|
238
261
|
ordered = {}
|
|
239
262
|
groups.each do |group|
|
|
240
263
|
group[:keys].sort!
|
|
241
|
-
group[:keys].
|
|
264
|
+
group[:keys].reverse! unless group[:kp].ascending
|
|
265
|
+
group[:keys].each { |k| ordered[s2orig[k]] = unordered_hash[s2orig[k]] }
|
|
242
266
|
end
|
|
243
|
-
|
|
244
|
-
|
|
267
|
+
unordered_hash.clear
|
|
268
|
+
unordered_hash.merge!(ordered)
|
|
245
269
|
true
|
|
246
270
|
end
|
|
247
271
|
|