rbs_json_schema 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c42556d48a48168a502401070844f1d77e797044fce25023d5afbed1129e46f5
4
+ data.tar.gz: 4cf37da298eb6c81ce8868b7bc1733d27e66c2de394d08e05d66cdb0e4c32072
5
+ SHA512:
6
+ metadata.gz: '0871e0d1ef8128f33e2493981b97c1d244d26f8d8b08f649cc0bbdd61a4775b44f40449acfb68c3025fb93ea1dbfede0ef73f1ff88177522adaa761529a460b9'
7
+ data.tar.gz: 8ff79062b035258cd8c33a4570fbd711a220a9b85ffcac19d5183cb2e844ca3744d3157db29893fb2141036320d335f3f637b5928f7901aaef975587b4baa4d8
@@ -0,0 +1,33 @@
1
+ name: Test
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request: {}
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: "ubuntu-latest"
12
+ strategy:
13
+ matrix:
14
+ container_tag:
15
+ - master-nightly-focal
16
+ - 3.0-focal
17
+ - 2.7-bionic
18
+ job:
19
+ - test
20
+ container:
21
+ image: rubylang/ruby:${{ matrix.container_tag }}
22
+ steps:
23
+ - uses: actions/checkout@v2
24
+ - name: Update rubygems & bundler
25
+ run: |
26
+ ruby -v
27
+ gem update --system
28
+ - name: bin/setup
29
+ run: |
30
+ bin/setup
31
+ - name: Run test
32
+ run: |
33
+ bundle exec rake ${{ matrix.job }}
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.0.2
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.6.5
6
+ before_install: gem install bundler -v 2.1.4
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # CHANGELOG
2
+
3
+ ## master
4
+
5
+ * Initial release
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in rbs_json_schema.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 12.0"
7
+ gem "minitest", "~> 5.0"
8
+ gem "steep"
data/Gemfile.lock ADDED
@@ -0,0 +1,62 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rbs_json_schema (0.1.0)
5
+ activesupport (>= 5.0.0)
6
+ rbs (>= 1.5.0)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activesupport (6.1.4.1)
12
+ concurrent-ruby (~> 1.0, >= 1.0.2)
13
+ i18n (>= 1.6, < 2)
14
+ minitest (>= 5.1)
15
+ tzinfo (~> 2.0)
16
+ zeitwerk (~> 2.3)
17
+ ast (2.4.2)
18
+ concurrent-ruby (1.1.9)
19
+ ffi (1.15.3)
20
+ i18n (1.8.10)
21
+ concurrent-ruby (~> 1.0)
22
+ language_server-protocol (3.16.0.3)
23
+ listen (3.7.0)
24
+ rb-fsevent (~> 0.10, >= 0.10.3)
25
+ rb-inotify (~> 0.9, >= 0.9.10)
26
+ minitest (5.14.4)
27
+ parallel (1.20.1)
28
+ parser (3.0.2.0)
29
+ ast (~> 2.4.1)
30
+ rainbow (3.0.0)
31
+ rake (12.3.3)
32
+ rb-fsevent (0.11.0)
33
+ rb-inotify (0.10.1)
34
+ ffi (~> 1.0)
35
+ rbs (1.5.1)
36
+ steep (0.46.0)
37
+ activesupport (>= 5.1)
38
+ language_server-protocol (>= 3.15, < 4.0)
39
+ listen (~> 3.0)
40
+ parallel (>= 1.0.0)
41
+ parser (>= 3.0)
42
+ rainbow (>= 2.2.2, < 4.0)
43
+ rbs (>= 1.2.0)
44
+ terminal-table (>= 2, < 4)
45
+ terminal-table (3.0.1)
46
+ unicode-display_width (>= 1.1.1, < 3)
47
+ tzinfo (2.0.4)
48
+ concurrent-ruby (~> 1.0)
49
+ unicode-display_width (2.0.0)
50
+ zeitwerk (2.4.2)
51
+
52
+ PLATFORMS
53
+ ruby
54
+
55
+ DEPENDENCIES
56
+ minitest (~> 5.0)
57
+ rake (~> 12.0)
58
+ rbs_json_schema!
59
+ steep
60
+
61
+ BUNDLED WITH
62
+ 2.2.22
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Soutaro Matsumoto
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Soutaro Matsumoto
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # rbs_json_schema
2
+
3
+ rbs_json_schema is a RBS type definition generator from JSON Schema.
4
+ It loads JSON files and writes RBS type definitioins.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'rbs_json_schema'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle install
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install rbs_json_schema
21
+
22
+ ## Usage
23
+
24
+ Execute `rbs_json_schema` command with JSON schema files.
25
+
26
+ ```
27
+ $ bundle exec rbs_json_schema schemas/address.json
28
+ ```
29
+
30
+ ## Development
31
+
32
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
33
+
34
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
35
+
36
+ ## Acknowledgement
37
+
38
+ This software is based on the work of Sushanth Sathesh Rao ([@raosush](https://github.com/raosush)) with the support from Google Summer of Code 2021.
39
+
40
+ ## Contributing
41
+
42
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/rbs_json_schema.
43
+
44
+
45
+ ## License
46
+
47
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
data/Steepfile ADDED
@@ -0,0 +1,19 @@
1
+ D = Steep::Diagnostic
2
+
3
+ target :lib do
4
+ signature "sig"
5
+ check "lib"
6
+
7
+ library "rbs", "json", "uri", "pathname", "strscan", "tsort", "optparse", "logger", "monitor", "rubygems", "net-http"
8
+
9
+ configure_code_diagnostics do |hash|
10
+ hash[D::Ruby::MethodDefinitionMissing] = :hint
11
+ end
12
+ end
13
+
14
+ target :test do
15
+ signature "sig"
16
+ check "test"
17
+
18
+ library "rbs", "json", "uri", "pathname", "strscan", "tsort", "optparse", "logger", "monitor", "rubygems", "tmpdir"
19
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "rbs_json_schema"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH << File.join(__dir__, "../lib")
4
+ require "rbs_json_schema"
5
+ require "rbs_json_schema/cli"
6
+
7
+ RBSJsonSchema::CLI.new(stdout: STDOUT, stderr: STDERR).run(ARGV)
@@ -0,0 +1,57 @@
1
+ require "optparse"
2
+
3
+ module RBSJsonSchema
4
+ class CLI
5
+ attr_reader :stdout
6
+ attr_reader :stderr
7
+
8
+ def initialize(stdout:, stderr:)
9
+ @stdout = stdout
10
+ @stderr = stderr
11
+ @options = {}
12
+ end
13
+
14
+ def run(args)
15
+ OptionParser.new do |opts|
16
+ opts.banner = <<~USAGE
17
+ Usage: rbs_json_schema [options...] [path...]
18
+
19
+ Generates RBS files from JSON Schema.
20
+
21
+ Options:
22
+ USAGE
23
+
24
+ opts.on("--[no-]stringify-keys", "Generate record types with string keys") do |bool|
25
+ @options[:stringify_keys] = bool
26
+ end
27
+
28
+ opts.on("-o OUTPUT", "Output the generated RBS to a specific location") do |location|
29
+ @options[:output] = location
30
+ end
31
+ end.parse!(args)
32
+
33
+ generator = Generator.new(stringify_keys: @options[:stringify_keys], output: @options[:output], stdout: stdout, stderr: stderr)
34
+ args.each do |path|
35
+ path =
36
+ begin
37
+ Pathname(path).realpath
38
+ rescue Errno::ENOENT => _
39
+ raise ValidationError.new(message: "#{path}: No such file or directory found!")
40
+ end
41
+
42
+ case
43
+ when path.file?
44
+ generator.generate(URI.parse("file://#{path}"))
45
+ when path.directory?
46
+ Dir["#{path}/*.{json}"].sort.each do |file|
47
+ file = Pathname(file).realpath
48
+ generator.generate(URI.parse("file://#{file}"))
49
+ end
50
+ else
51
+ raise ValidationError.new(message: "#{path}: No such file or directory found!")
52
+ end
53
+ end
54
+ generator.write_output
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,308 @@
1
+ module RBSJsonSchema
2
+ class Generator
3
+ attr_reader :stringify_keys
4
+ attr_reader :output
5
+ attr_reader :stdout
6
+ attr_reader :stderr
7
+ attr_reader :path_decls
8
+ attr_reader :generated_schemas
9
+
10
+ Alias = RBS::AST::Declarations::Alias
11
+
12
+ def initialize(stringify_keys:, output:, stdout:, stderr:)
13
+ @stringify_keys = stringify_keys ? true : false
14
+ @output = output
15
+ @stdout = stdout
16
+ @stderr = stderr
17
+ @path_decls = {}
18
+ @generated_schemas = {}
19
+ end
20
+
21
+ # IMPORTANT: Function invoked to generate RBS from JSON schema
22
+ # Generates RBS from JSON schema after validating options & writes to file/STDOUT
23
+ def generate(uri)
24
+ # Validate options received from CLI
25
+ validate_options()
26
+
27
+ @path_decls[uri.path] ||= RBS::AST::Declarations::Module.new(
28
+ name: generate_type_name_for_uri(uri, module_name: true),
29
+ type_params: RBS::AST::Declarations::ModuleTypeParams.empty,
30
+ members: [],
31
+ self_types: [],
32
+ annotations: [],
33
+ location: nil,
34
+ comment: nil
35
+ )
36
+ generate_rbs(uri, read_from_uri(uri))
37
+ end
38
+
39
+ # IMPORTANT: Function used to generate AST alias declarations from a URI & a schema document
40
+ def generate_rbs(uri, document)
41
+ # If schema is already generated for a URI, do not re-generate declarations/types
42
+ if fragment = uri.fragment
43
+ # return if fragment.empty? # If fragment is empty, implies top level schema which is always generated since it is the starting point of the algorithm
44
+
45
+ if @generated_schemas.dig(uri.path, fragment.empty? ? "#" : fragment) # Check if types have been generated for a particular path & fragment
46
+ return
47
+ end
48
+ else
49
+ if @generated_schemas.dig(uri.path, "#") # Check if types have been generated for a particular path
50
+ return
51
+ end
52
+ end
53
+
54
+ unless document.is_a?(Hash)
55
+ raise ValidationError.new(message: "Invalid JSON Schema: #{document}")
56
+ end
57
+
58
+ @generated_schemas[uri.path] ||= {}
59
+ if fragment = uri.fragment
60
+ @generated_schemas[uri.path][fragment.empty? ? "#" : fragment] = true
61
+ else
62
+ @generated_schemas[uri.path]["#"] = true
63
+ end
64
+ # Parse & generate declarations from remaining schema content
65
+ decl = Alias.new(
66
+ name: generate_type_name_for_uri(uri), # Normal type name with no prefix
67
+ type: translate_type(uri, document), # Obtain type of alias by parsing the schema document
68
+ annotations: [],
69
+ location: nil,
70
+ comment: nil
71
+ )
72
+ # Append the declaration if & only if the declaration has a valid RBS::Type assigned
73
+ if @path_decls[uri.path]
74
+ @path_decls[uri.path].members << decl if !decl.type.nil?
75
+ else
76
+ @path_decls[uri.path] = RBS::AST::Declarations::Module.new(
77
+ name: generate_type_name_for_uri(uri, module_name: true),
78
+ type_params: RBS::AST::Declarations::ModuleTypeParams.empty,
79
+ members: [],
80
+ self_types: [],
81
+ annotations: [],
82
+ location: nil,
83
+ comment: nil
84
+ )
85
+ @path_decls[uri.path].members << decl if !decl.type.nil?
86
+ end
87
+ end
88
+
89
+ def literal_type(literal)
90
+ # Assign literal type
91
+ case literal
92
+ when String, Integer, TrueClass, FalseClass
93
+ RBS::Types::Literal.new(literal: literal, location: nil)
94
+ when nil
95
+ RBS::Types::Bases::Nil.new(location: nil)
96
+ else
97
+ raise ValidationError.new(message: "Unresolved literal found: #{literal}")
98
+ end
99
+ end
100
+
101
+ def untyped_type
102
+ RBS::Types::Bases::Any.new(location: nil)
103
+ end
104
+
105
+ # Parse JSON schema & return the `RBS::Types` to be assigned
106
+ def translate_type(uri, schema)
107
+ case
108
+ when values = schema["enum"]
109
+ unless values.is_a?(Array)
110
+ raise ValidationError.new(message: "Invalid JSON Schema: enum: #{values}")
111
+ end
112
+
113
+ types = values.map { |literal| literal_type(literal) }
114
+ RBS::Types::Union.new(types: types, location: nil)
115
+ when const = schema["const"]
116
+ literal_type(const)
117
+ when schema["type"] == "array" || schema.key?("items")
118
+ case
119
+ when schema["items"].is_a?(Array)
120
+ # tuple
121
+ types = schema["items"].map { |definition| translate_type(uri, definition) }
122
+ RBS::Types::Tuple.new(types: types, location: nil)
123
+ when schema["items"].is_a?(Hash)
124
+ # array
125
+ elem_type = translate_type(uri, schema["items"])
126
+ RBS::BuiltinNames::Array.instance_type(elem_type)
127
+ else
128
+ RBS::BuiltinNames::Array.instance_type(untyped_type)
129
+ end
130
+ when schema["type"] == "object" || schema.key?("properties") || schema.key?("additionalProperties")
131
+ case
132
+ when properties = schema["properties"]
133
+ # @type var properties: json_schema
134
+ fields = properties.each.with_object({}) do |pair, hash|
135
+ # @type var hash: Hash[Symbol, RBS::Types::t]
136
+ key, value = pair
137
+
138
+ hash[key.to_sym] = translate_type(uri, value)
139
+ end
140
+
141
+ RBS::Types::Record.new(fields: fields, location: nil)
142
+ when prop = schema["additionalProperties"]
143
+ RBS::BuiltinNames::Hash.instance_type(
144
+ RBS::BuiltinNames::String.instance_type,
145
+ translate_type(uri, prop)
146
+ )
147
+ else
148
+ RBS::BuiltinNames::Hash.instance_type(
149
+ RBS::BuiltinNames::String.instance_type,
150
+ untyped_type
151
+ )
152
+ end
153
+ when one_of = schema["oneOf"]
154
+ RBS::Types::Union.new(
155
+ types: one_of.map { |defn| translate_type(uri, defn) },
156
+ location: nil
157
+ )
158
+ when all_of = schema["allOf"]
159
+ RBS::Types::Intersection.new(
160
+ types: all_of.map { |defn| translate_type(uri, defn) },
161
+ location: nil
162
+ )
163
+ when ty = schema["type"]
164
+ case ty
165
+ when "integer"
166
+ RBS::BuiltinNames::Integer.instance_type
167
+ when "number"
168
+ RBS::BuiltinNames::Numeric.instance_type
169
+ when "string"
170
+ RBS::BuiltinNames::String.instance_type
171
+ when "boolean"
172
+ RBS::Types::Bases::Bool.new(location: nil)
173
+ when "null"
174
+ RBS::Types::Bases::Nil.new(location: nil)
175
+ else
176
+ raise ValidationError.new(message: "Invalid JSON Schema: type: #{ty}")
177
+ end
178
+ when ref = schema["$ref"]
179
+ ref_uri =
180
+ begin
181
+ # Parse URI of `$ref`
182
+ URI.parse(schema["$ref"])
183
+ rescue URI::InvalidURIError => _
184
+ raise ValidationError.new(message: "Invalid URI encountered in: $ref = #{ref}")
185
+ end
186
+
187
+ resolved_uri = resolve_uri(uri, ref_uri) # Resolve `$ref` URI with respect to current URI
188
+ # Generate AST::Declarations::Alias
189
+ generate_rbs(resolved_uri, read_from_uri(resolved_uri))
190
+
191
+ # Assign alias type with appropriate namespace
192
+ RBS::Types::Alias.new(
193
+ name: generate_type_name_for_uri(resolved_uri, namespace: resolved_uri.path != uri.path),
194
+ location: nil
195
+ )
196
+ else
197
+ raise ValidationError.new(message: "Invalid JSON Schema: #{schema.keys.join(", ")}")
198
+ end
199
+ end
200
+
201
+ # Read contents from a URI
202
+ def read_from_uri(uri)
203
+ schema_source =
204
+ case uri.scheme
205
+ when "file", nil
206
+ File.read(uri.path)
207
+ when "http", "https"
208
+ Net::HTTP.get(uri)
209
+ else
210
+ raise ValidationError.new(message: "Could not read content from URI: #{uri}")
211
+ end
212
+
213
+ schema = JSON.parse(schema_source)
214
+
215
+ if (fragment = uri.fragment) && !fragment.empty?
216
+ case
217
+ when fragment.start_with?("/")
218
+ path = fragment.split("/")
219
+ path.shift()
220
+
221
+ schema.dig(*path) or
222
+ raise ValidationError.new(message: "JSON pointer doesn't point a schema: #{uri.fragment}")
223
+ else
224
+ raise ValidationError.new(message: "JSON pointer is expected: #{uri.fragment}")
225
+ end
226
+ else
227
+ schema
228
+ end
229
+ end
230
+
231
+ # Write output using `RBS::Writer` to a particular `IO`
232
+ def write_output
233
+ # If an output directory is given, open a file & write to it
234
+ if output = self.output
235
+ @path_decls.each do |path, decls|
236
+ name = decls.name.name.to_s.underscore
237
+ file_path = File.join(output, "#{name}.rbs")
238
+ File.open(file_path, 'w') do |io|
239
+ stdout.puts "Writing output to file: #{file_path}"
240
+ RBS::Writer.new(out: io).write([decls])
241
+ end
242
+ end
243
+ # If no output directory is given write to STDOUT
244
+ else
245
+ RBS::Writer.new(out: stdout).write(@path_decls.values)
246
+ end
247
+ end
248
+
249
+ # Utility function to assign type name from URI
250
+ private def generate_type_name_for_uri(uri, module_name: false, namespace: false)
251
+ dup_uri = uri.dup # Duplicate URI object for processing
252
+ path = dup_uri.path.split("/").last or raise # Extract path
253
+ path.gsub!(/(.json$)?/, '') # Remove JSON file extension if found
254
+ prefix = path.camelize # prefix is used to write module name, hence converted to camel case
255
+
256
+ # Return module_name
257
+ if module_name
258
+ return RBS::TypeName.new(
259
+ name: prefix.to_sym,
260
+ namespace: RBS::Namespace.empty
261
+ )
262
+ end
263
+
264
+ name = :t
265
+
266
+ if fragment = dup_uri.fragment
267
+ unless fragment.empty?
268
+ fragment[0] = "" if fragment.start_with?("/") # Remove initial slash if present in fragment
269
+ name = fragment.downcase.tr("/", "_")
270
+ end
271
+ end
272
+
273
+ # Return type name for type alias
274
+ RBS::TypeName.new(
275
+ name: name.to_sym,
276
+ namespace: namespace ? RBS::Namespace.new(path: [prefix.to_sym], absolute: false) : RBS::Namespace.empty
277
+ )
278
+ end
279
+
280
+ # Returns type name with prefixes & appropriate namespace
281
+ private def type_name(name, absolute: nil)
282
+ RBS::TypeName.new(
283
+ name: name.to_sym,
284
+ namespace: absolute ? RBS::Namespace.root : RBS::Namespace.empty
285
+ )
286
+ end
287
+
288
+ # Utility function to resolve two URIs
289
+ private def resolve_uri(uri, ref_uri)
290
+ begin
291
+ # Attempt to merge the two URIs
292
+ uri + ref_uri
293
+ rescue URI::BadURIError, ArgumentError => _
294
+ # Raise error in case of invalid URI
295
+ raise ValidationError.new(message: "Could not resolve URI: #{uri} + #{ref_uri}")
296
+ end
297
+ end
298
+
299
+ # Validate options given to the CLI
300
+ private def validate_options
301
+ if output = self.output
302
+ path = Pathname(output)
303
+ # Check if a valid directory exists?
304
+ raise ValidationError.new(message: "#{output}: Directory not found!") if !path.directory?
305
+ end
306
+ end
307
+ end
308
+ end
@@ -0,0 +1,11 @@
1
+ module RBSJsonSchema
2
+ class ValidationError < StandardError
3
+ attr_reader :message
4
+
5
+ def initialize(message:)
6
+ @message = message
7
+
8
+ super message
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module RBSJsonSchema
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,13 @@
1
+ require "uri"
2
+ require "json"
3
+ require "rbs"
4
+ require "active_support/core_ext/string/inflections"
5
+
6
+ require "rbs_json_schema/version"
7
+ require "rbs_json_schema/generator"
8
+ require "rbs_json_schema/validation_error"
9
+
10
+ module RBSJsonSchema
11
+ class Error < StandardError; end
12
+ # Your code goes here...
13
+ end
@@ -0,0 +1,30 @@
1
+ require_relative 'lib/rbs_json_schema/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "rbs_json_schema"
5
+ spec.version = RBSJsonSchema::VERSION
6
+ spec.authors = ["Soutaro Matsumoto"]
7
+ spec.email = ["matsumoto@soutaro.com"]
8
+
9
+ spec.summary = %q{Generate RBS type definitions from JSON Schema}
10
+ spec.description = %q{Generate RBS type definitions from JSON Schema}
11
+ spec.homepage = "https://github.com/ruby/rbs_json_schema"
12
+ spec.license = "MIT"
13
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
14
+
15
+ spec.metadata["homepage_uri"] = spec.homepage
16
+ spec.metadata["source_code_uri"] = "https://github.com/ruby/rbs_json_schema"
17
+ spec.metadata["changelog_uri"] = "https://github.com/soutaro/rbs_json_schema/blob/main/CHANGELOG.md"
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
+ end
24
+ spec.bindir = "exe"
25
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
+ spec.require_paths = ["lib"]
27
+
28
+ spec.add_runtime_dependency "rbs", ">=1.5.0"
29
+ spec.add_runtime_dependency "activesupport", ">=5.0.0"
30
+ end
@@ -0,0 +1,5 @@
1
+ class String
2
+ def underscore: () -> String
3
+
4
+ def camelize: () -> String
5
+ end
@@ -0,0 +1,3 @@
1
+ module Kernel
2
+ def __dir__: () -> String | ...
3
+ end
@@ -0,0 +1,9 @@
1
+ module Minitest
2
+ class Test
3
+ def assert_equal: (untyped, untyped) -> void
4
+
5
+ def assert: (boolish) -> void
6
+
7
+ def assert_raises: (singleton(Exception)) { () -> void } -> Exception
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ module URI
2
+ class Generic
3
+ def fragment=: (String?) -> String?
4
+ | ...
5
+
6
+ def +: (URI::Generic other) -> URI::Generic
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+ class CLITest < Minitest::Test
2
+ include RBSJsonSchema
3
+
4
+ RBS_PATH: Pathname
5
+ SCHEMA_PATH: Pathname
6
+
7
+ attr_reader stdout (): StringIO
8
+
9
+ attr_reader stderr (): StringIO
10
+
11
+ def with_cli: () { (RBSJsonSchema::CLI) -> void } -> void
12
+ end
@@ -0,0 +1,13 @@
1
+ class GeneratorTest < Minitest::Test
2
+ include RBSJsonSchema
3
+
4
+ attr_reader output (): String
5
+
6
+ attr_reader stdout (): StringIO
7
+
8
+ attr_reader stderr (): StringIO
9
+
10
+ def in_tmpdir: () { (Pathname) -> void } -> void
11
+
12
+ def generator: () -> RBSJsonSchema::Generator
13
+ end
@@ -0,0 +1,21 @@
1
+ module RBSJsonSchema
2
+ class CLI
3
+ interface _IO
4
+ def puts: (*untyped) -> void
5
+
6
+ def print: (*untyped) -> void
7
+
8
+ def flush: () -> void
9
+ end
10
+
11
+ attr_reader stdout: _IO
12
+
13
+ attr_reader stderr: _IO
14
+
15
+ @options: Hash[Symbol, untyped]
16
+
17
+ def initialize: (stdout: _IO, stderr: _IO) -> void
18
+
19
+ def run: (Array[String] args) -> void
20
+ end
21
+ end
@@ -0,0 +1,67 @@
1
+ module RBSJsonSchema
2
+ class Generator
3
+ attr_reader options: Hash[Symbol, value]
4
+
5
+ Alias: singleton(RBS::AST::Declarations::Alias)
6
+
7
+ interface _IO
8
+ def puts: (*untyped) -> void
9
+
10
+ def print: (*untyped) -> void
11
+
12
+ def flush: () -> void
13
+ end
14
+
15
+ attr_reader stdout: _IO
16
+
17
+ attr_reader stderr: _IO
18
+
19
+ attr_reader name: String
20
+
21
+ attr_reader path_decls: Hash[String, RBS::AST::Declarations::Module]
22
+
23
+ attr_reader generated_schemas: Hash[String, Hash[String, bool]]
24
+
25
+ type value = String | bool
26
+
27
+ type json_schema = Hash[String, untyped]
28
+
29
+ attr_reader stringify_keys: bool
30
+ attr_reader output: String?
31
+
32
+ def initialize: (stringify_keys: boolish, output: String?, stdout: _IO, stderr: _IO) -> untyped
33
+
34
+ def generate: (URI::Generic uri) -> void
35
+
36
+ def generate_rbs: (URI::Generic uri, json_schema document) -> void
37
+
38
+ def translate_type: (URI::Generic uri, json_schema schema) -> RBS::Types::t
39
+
40
+ # Returns JSON Schema object from given URI.
41
+ #
42
+ # ```rb
43
+ # generator.read_from_uri(URI.parse("file://./foo.json"))
44
+ # generator.read_from_uri(URI.parse("http://localhost/schemas/foo#/$defs/bar"))
45
+ # ```
46
+ #
47
+ # Reading from URI with `file` schema and `http` schema is supported.
48
+ #
49
+ def read_from_uri: (URI::Generic uri) -> json_schema
50
+
51
+ def write_output: () -> void
52
+
53
+ private
54
+
55
+ def literal_type: (untyped) -> (RBS::Types::Literal | RBS::Types::Bases::Nil)
56
+
57
+ def untyped_type: () -> RBS::Types::Bases::Any
58
+
59
+ def generate_type_name_for_uri: (URI::Generic uri, ?module_name: boolish, ?namespace: boolish) -> RBS::TypeName
60
+
61
+ def type_name: (String name, ?absolute: boolish) -> RBS::TypeName
62
+
63
+ def resolve_uri: (URI::Generic uri, URI::Generic ref_uri) -> URI::Generic
64
+
65
+ def validate_options: () -> void
66
+ end
67
+ end
@@ -0,0 +1,7 @@
1
+ module RBSJsonSchema
2
+ class ValidationError < StandardError
3
+ attr_reader message: String
4
+
5
+ def initialize: (message: String) -> void
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module RBSJsonSchema
2
+ VERSION: String
3
+ end
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rbs_json_schema
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Soutaro Matsumoto
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-08-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rbs
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.5.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 1.5.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 5.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 5.0.0
41
+ description: Generate RBS type definitions from JSON Schema
42
+ email:
43
+ - matsumoto@soutaro.com
44
+ executables:
45
+ - rbs_json_schema
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".github/workflows/test.yml"
50
+ - ".gitignore"
51
+ - ".ruby-version"
52
+ - ".travis.yml"
53
+ - CHANGELOG.md
54
+ - Gemfile
55
+ - Gemfile.lock
56
+ - LICENSE
57
+ - LICENSE.txt
58
+ - README.md
59
+ - Rakefile
60
+ - Steepfile
61
+ - bin/console
62
+ - bin/setup
63
+ - exe/rbs_json_schema
64
+ - lib/rbs_json_schema.rb
65
+ - lib/rbs_json_schema/cli.rb
66
+ - lib/rbs_json_schema/generator.rb
67
+ - lib/rbs_json_schema/validation_error.rb
68
+ - lib/rbs_json_schema/version.rb
69
+ - rbs_json_schema.gemspec
70
+ - sig/_patches/activesupport.rbs
71
+ - sig/_patches/dir.rbs
72
+ - sig/_patches/minitest.rbs
73
+ - sig/_patches/uri.rbs
74
+ - sig/_test/cli_test.rbs
75
+ - sig/_test/generator_test.rbs
76
+ - sig/rbs_json_schema/cli.rbs
77
+ - sig/rbs_json_schema/generator.rbs
78
+ - sig/rbs_json_schema/validation_error.rbs
79
+ - sig/rbs_json_schema/version.rbs
80
+ homepage: https://github.com/ruby/rbs_json_schema
81
+ licenses:
82
+ - MIT
83
+ metadata:
84
+ homepage_uri: https://github.com/ruby/rbs_json_schema
85
+ source_code_uri: https://github.com/ruby/rbs_json_schema
86
+ changelog_uri: https://github.com/soutaro/rbs_json_schema/blob/main/CHANGELOG.md
87
+ post_install_message:
88
+ rdoc_options: []
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: 2.3.0
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ requirements: []
102
+ rubygems_version: 3.2.22
103
+ signing_key:
104
+ specification_version: 4
105
+ summary: Generate RBS type definitions from JSON Schema
106
+ test_files: []