jddf 0.1.0 → 0.2.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/.gitmodules +3 -0
- data/.rubocop.yml +29 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +17 -1
- data/README.md +91 -24
- data/bin/bundle +105 -0
- data/bin/htmldiff +29 -0
- data/bin/ldiff +29 -0
- data/bin/rake +29 -0
- data/bin/rspec +29 -0
- data/bin/rubocop +29 -0
- data/bin/ruby-parse +29 -0
- data/bin/ruby-rewrite +29 -0
- data/jddf.gemspec +3 -1
- data/lib/jddf.rb +7 -4
- data/lib/jddf/schema.rb +235 -0
- data/lib/jddf/validator.rb +272 -0
- data/lib/jddf/version.rb +4 -2
- metadata +28 -3
- data/.travis.yml +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b488c9c91ad6f8eed43e3295b1a7bfbce20985d3aee9217eb2c9defc5f737e8c
|
4
|
+
data.tar.gz: 42676f592a08589204ccbb03aa68118714c60c10b0415a0a194cee9b65a7e228
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c9a69812ceae8889294f9b6adc0848d14b7f66511145156af42a34fe7e9f1de5aa678b684372249d8cef6a643b48f4b7cc3c9b8a341f3d960bb449593785cf4c
|
7
|
+
data.tar.gz: 0e04a88ceac3add797277390fcffed2256229abef37f68cb0515291f85096ff9475a27c6372ec9c267c90976aabef96768cc550afa81e945ebffb8e5f3cc843b
|
data/.gitmodules
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
Metrics/AbcSize:
|
2
|
+
Enabled: false
|
3
|
+
|
4
|
+
Metrics/MethodLength:
|
5
|
+
Enabled: false
|
6
|
+
|
7
|
+
Metrics/CyclomaticComplexity:
|
8
|
+
Enabled: false
|
9
|
+
|
10
|
+
Metrics/BlockLength:
|
11
|
+
Enabled: false
|
12
|
+
|
13
|
+
Metrics/ModuleLength:
|
14
|
+
Enabled: false
|
15
|
+
|
16
|
+
Metrics/PerceivedComplexity:
|
17
|
+
Enabled: false
|
18
|
+
|
19
|
+
Lint/ShadowingOuterLocalVariable:
|
20
|
+
Enabled: false
|
21
|
+
|
22
|
+
Metrics/ClassLength:
|
23
|
+
Enabled: false
|
24
|
+
|
25
|
+
Metrics/BlockNesting:
|
26
|
+
Enabled: false
|
27
|
+
|
28
|
+
Style/Next:
|
29
|
+
Enabled: false
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,12 +1,18 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
jddf (0.
|
4
|
+
jddf (0.2.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
|
+
ast (2.4.0)
|
9
10
|
diff-lcs (1.3)
|
11
|
+
jaro_winkler (1.5.3)
|
12
|
+
parallel (1.18.0)
|
13
|
+
parser (2.6.5.0)
|
14
|
+
ast (~> 2.4.0)
|
15
|
+
rainbow (3.0.0)
|
10
16
|
rake (10.5.0)
|
11
17
|
rspec (3.8.0)
|
12
18
|
rspec-core (~> 3.8.0)
|
@@ -21,6 +27,15 @@ GEM
|
|
21
27
|
diff-lcs (>= 1.2.0, < 2.0)
|
22
28
|
rspec-support (~> 3.8.0)
|
23
29
|
rspec-support (3.8.3)
|
30
|
+
rubocop (0.75.0)
|
31
|
+
jaro_winkler (~> 1.5.1)
|
32
|
+
parallel (~> 1.10)
|
33
|
+
parser (>= 2.6)
|
34
|
+
rainbow (>= 2.2.2, < 4.0)
|
35
|
+
ruby-progressbar (~> 1.7)
|
36
|
+
unicode-display_width (>= 1.4.0, < 1.7)
|
37
|
+
ruby-progressbar (1.10.1)
|
38
|
+
unicode-display_width (1.6.0)
|
24
39
|
|
25
40
|
PLATFORMS
|
26
41
|
ruby
|
@@ -30,6 +45,7 @@ DEPENDENCIES
|
|
30
45
|
jddf!
|
31
46
|
rake (~> 10.0)
|
32
47
|
rspec (~> 3.0)
|
48
|
+
rubocop (~> 0.75.0)
|
33
49
|
|
34
50
|
BUNDLED WITH
|
35
51
|
1.17.2
|
data/README.md
CHANGED
@@ -1,39 +1,106 @@
|
|
1
|
-
#
|
1
|
+
# jddf-ruby [![Gem Version][badge]][rubygems]
|
2
2
|
|
3
|
-
|
3
|
+
> Documentation on rubydoc.info: https://www.rubydoc.info/github/jddf/jddf-ruby
|
4
4
|
|
5
|
-
|
5
|
+
This gem is a Ruby implementation of [JSON Data Definition Format][jddf], a
|
6
|
+
schema language for JSON. You can use this gem to:
|
6
7
|
|
7
|
-
|
8
|
+
1. Validate input data against a schema,
|
9
|
+
2. Get a list of validation errors from that input data, or
|
10
|
+
3. Build your own tooling on top of JSON Data Definition Format
|
8
11
|
|
9
|
-
|
12
|
+
[jddf]: https://jddf.io
|
13
|
+
[badge]: https://badge.fury.io/rb/jddf.svg
|
14
|
+
[rubygems]: https://rubygems.org/gems/jddf
|
10
15
|
|
11
|
-
|
12
|
-
gem 'jddf'
|
13
|
-
```
|
16
|
+
## Installing
|
14
17
|
|
15
|
-
|
18
|
+
You can install this gem by running:
|
16
19
|
|
17
|
-
|
20
|
+
```bash
|
21
|
+
gem install jddf
|
22
|
+
```
|
18
23
|
|
19
|
-
Or
|
24
|
+
Or if you're using Bundler:
|
20
25
|
|
21
|
-
|
26
|
+
```ruby
|
27
|
+
gem 'jddf'
|
28
|
+
```
|
22
29
|
|
23
30
|
## Usage
|
24
31
|
|
25
|
-
|
32
|
+
The two most important classes offered by the `JDDF` module are:
|
26
33
|
|
27
|
-
|
34
|
+
* `Schema`, which represents a JDDF schema,
|
35
|
+
* `Validator`, which can validate a `Schema` against any parsed JSON data, and
|
36
|
+
* `ValidationError`, which represents a single validation problem with the
|
37
|
+
input. `Validator#validate` returns an array of these.
|
28
38
|
|
29
|
-
|
39
|
+
Here's a working example:
|
30
40
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
41
|
+
```ruby
|
42
|
+
require 'jddf'
|
43
|
+
|
44
|
+
# In this example, we're passing in a Hash directly into Schema#from_json, but
|
45
|
+
# this type of Hash is exactly what JSON#parse returns.
|
46
|
+
schema = JDDF::Schema.from_json({
|
47
|
+
'properties' => {
|
48
|
+
'name' => { 'type' => 'string' },
|
49
|
+
'age' => { 'type' => 'uint32' },
|
50
|
+
'phones' => {
|
51
|
+
'elements' => { 'type' => 'string' }
|
52
|
+
}
|
53
|
+
}
|
54
|
+
})
|
55
|
+
|
56
|
+
# Like before, in order to keep things simple we're construct raw Ruby values
|
57
|
+
# here. But you can also get this sort of data by parsing JSON using the
|
58
|
+
# standard library's JSON#parse.
|
59
|
+
#
|
60
|
+
# This input data is perfect. It satisfies all the schema requirements.
|
61
|
+
input_ok = {
|
62
|
+
'name' => 'John Doe',
|
63
|
+
'age' => 43,
|
64
|
+
'phones' => [
|
65
|
+
'+44 1234567',
|
66
|
+
'+44 2345678'
|
67
|
+
]
|
68
|
+
}
|
69
|
+
|
70
|
+
# This input data has problems. "name" is missing, "age" has the wrong type,
|
71
|
+
# and "phones[1]" has the wrong type.
|
72
|
+
input_bad = {
|
73
|
+
'age' => '43',
|
74
|
+
'phones' => [
|
75
|
+
'+44 1234567',
|
76
|
+
442345678
|
77
|
+
]
|
78
|
+
}
|
79
|
+
|
80
|
+
# Validator can validate schemas against inputs. Validator#validate returns an
|
81
|
+
# array of ValidationError.
|
82
|
+
#
|
83
|
+
# These ValidationError instances are portable -- every implementation of JDDF,
|
84
|
+
# across every language, returns the same errors.
|
85
|
+
validator = JDDF::Validator.new
|
86
|
+
result_ok = validator.validate(schema, input_ok)
|
87
|
+
result_bad = validator.validate(schema, input_bad)
|
88
|
+
|
89
|
+
p result_ok.size # 0
|
90
|
+
p result_bad.size # 3
|
91
|
+
|
92
|
+
# This error indicates that "name" is missing.
|
93
|
+
#
|
94
|
+
# #<struct JDDF::ValidationError instance_path=[], schema_path=["properties", "name"]
|
95
|
+
p result_bad[0]
|
96
|
+
|
97
|
+
# This error indicates that "age" has the wrong type.
|
98
|
+
#
|
99
|
+
# #<struct JDDF::ValidationError instance_path=["age"], schema_path=["properties", "age", "type"]>
|
100
|
+
p result_bad[1]
|
101
|
+
|
102
|
+
# This error indicates that "phones[1]" has the wrong type.
|
103
|
+
#
|
104
|
+
# #<struct JDDF::ValidationError instance_path=["phones", "1"], schema_path=["properties", "phones", "elements", "type"]>
|
105
|
+
p result_bad[2]
|
106
|
+
```
|
data/bin/bundle
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'bundle' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "rubygems"
|
12
|
+
|
13
|
+
m = Module.new do
|
14
|
+
module_function
|
15
|
+
|
16
|
+
def invoked_as_script?
|
17
|
+
File.expand_path($0) == File.expand_path(__FILE__)
|
18
|
+
end
|
19
|
+
|
20
|
+
def env_var_version
|
21
|
+
ENV["BUNDLER_VERSION"]
|
22
|
+
end
|
23
|
+
|
24
|
+
def cli_arg_version
|
25
|
+
return unless invoked_as_script? # don't want to hijack other binstubs
|
26
|
+
return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
|
27
|
+
bundler_version = nil
|
28
|
+
update_index = nil
|
29
|
+
ARGV.each_with_index do |a, i|
|
30
|
+
if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
|
31
|
+
bundler_version = a
|
32
|
+
end
|
33
|
+
next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
|
34
|
+
bundler_version = $1 || ">= 0.a"
|
35
|
+
update_index = i
|
36
|
+
end
|
37
|
+
bundler_version
|
38
|
+
end
|
39
|
+
|
40
|
+
def gemfile
|
41
|
+
gemfile = ENV["BUNDLE_GEMFILE"]
|
42
|
+
return gemfile if gemfile && !gemfile.empty?
|
43
|
+
|
44
|
+
File.expand_path("../../Gemfile", __FILE__)
|
45
|
+
end
|
46
|
+
|
47
|
+
def lockfile
|
48
|
+
lockfile =
|
49
|
+
case File.basename(gemfile)
|
50
|
+
when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
|
51
|
+
else "#{gemfile}.lock"
|
52
|
+
end
|
53
|
+
File.expand_path(lockfile)
|
54
|
+
end
|
55
|
+
|
56
|
+
def lockfile_version
|
57
|
+
return unless File.file?(lockfile)
|
58
|
+
lockfile_contents = File.read(lockfile)
|
59
|
+
return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
|
60
|
+
Regexp.last_match(1)
|
61
|
+
end
|
62
|
+
|
63
|
+
def bundler_version
|
64
|
+
@bundler_version ||= begin
|
65
|
+
env_var_version || cli_arg_version ||
|
66
|
+
lockfile_version || "#{Gem::Requirement.default}.a"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def load_bundler!
|
71
|
+
ENV["BUNDLE_GEMFILE"] ||= gemfile
|
72
|
+
|
73
|
+
# must dup string for RG < 1.8 compatibility
|
74
|
+
activate_bundler(bundler_version.dup)
|
75
|
+
end
|
76
|
+
|
77
|
+
def activate_bundler(bundler_version)
|
78
|
+
if Gem::Version.correct?(bundler_version) && Gem::Version.new(bundler_version).release < Gem::Version.new("2.0")
|
79
|
+
bundler_version = "< 2"
|
80
|
+
end
|
81
|
+
gem_error = activation_error_handling do
|
82
|
+
gem "bundler", bundler_version
|
83
|
+
end
|
84
|
+
return if gem_error.nil?
|
85
|
+
require_error = activation_error_handling do
|
86
|
+
require "bundler/version"
|
87
|
+
end
|
88
|
+
return if require_error.nil? && Gem::Requirement.new(bundler_version).satisfied_by?(Gem::Version.new(Bundler::VERSION))
|
89
|
+
warn "Activating bundler (#{bundler_version}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_version}'`"
|
90
|
+
exit 42
|
91
|
+
end
|
92
|
+
|
93
|
+
def activation_error_handling
|
94
|
+
yield
|
95
|
+
nil
|
96
|
+
rescue StandardError, LoadError => e
|
97
|
+
e
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
m.load_bundler!
|
102
|
+
|
103
|
+
if m.invoked_as_script?
|
104
|
+
load Gem.bin_path("bundler", "bundle")
|
105
|
+
end
|
data/bin/htmldiff
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'htmldiff' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("diff-lcs", "htmldiff")
|
data/bin/ldiff
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'ldiff' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("diff-lcs", "ldiff")
|
data/bin/rake
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rake' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rake", "rake")
|
data/bin/rspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rspec' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rspec-core", "rspec")
|
data/bin/rubocop
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rubocop' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rubocop", "rubocop")
|
data/bin/ruby-parse
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'ruby-parse' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("parser", "ruby-parse")
|
data/bin/ruby-rewrite
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'ruby-rewrite' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("parser", "ruby-rewrite")
|
data/jddf.gemspec
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
3
|
lib = File.expand_path("../lib", __FILE__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
@@ -5,7 +6,7 @@ require "jddf/version"
|
|
5
6
|
|
6
7
|
Gem::Specification.new do |spec|
|
7
8
|
spec.name = "jddf"
|
8
|
-
spec.version =
|
9
|
+
spec.version = JDDF::VERSION
|
9
10
|
spec.authors = ["Ulysse Carion"]
|
10
11
|
spec.email = ["ulysse@segment.com"]
|
11
12
|
|
@@ -25,4 +26,5 @@ Gem::Specification.new do |spec|
|
|
25
26
|
spec.add_development_dependency "bundler", "~> 1.17"
|
26
27
|
spec.add_development_dependency "rake", "~> 10.0"
|
27
28
|
spec.add_development_dependency "rspec", "~> 3.0"
|
29
|
+
spec.add_development_dependency "rubocop", "~> 0.75.0"
|
28
30
|
end
|
data/lib/jddf.rb
CHANGED
data/lib/jddf/schema.rb
ADDED
@@ -0,0 +1,235 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JDDF
|
4
|
+
SCHEMA_KEYWORDS = %i[
|
5
|
+
definitions
|
6
|
+
ref
|
7
|
+
type
|
8
|
+
enum
|
9
|
+
elements
|
10
|
+
properties
|
11
|
+
optional_properties
|
12
|
+
additional_properties
|
13
|
+
values
|
14
|
+
discriminator
|
15
|
+
].freeze
|
16
|
+
|
17
|
+
DISCRIMINATOR_KEYWORDS = %i[
|
18
|
+
tag
|
19
|
+
mapping
|
20
|
+
].freeze
|
21
|
+
|
22
|
+
TYPES = %i[
|
23
|
+
boolean
|
24
|
+
int8
|
25
|
+
uint8
|
26
|
+
int16
|
27
|
+
uint16
|
28
|
+
int32
|
29
|
+
uint32
|
30
|
+
float32
|
31
|
+
float64
|
32
|
+
string
|
33
|
+
timestamp
|
34
|
+
].freeze
|
35
|
+
|
36
|
+
Schema = Struct.new(*SCHEMA_KEYWORDS) do
|
37
|
+
def self.from_json(hash)
|
38
|
+
raise TypeError.new, 'hash must be a Hash' unless hash.is_a?(Hash)
|
39
|
+
|
40
|
+
schema = Schema.new
|
41
|
+
|
42
|
+
if hash.include?('definitions')
|
43
|
+
unless hash['definitions'].is_a?(Hash)
|
44
|
+
raise TypeError, 'definitions not Hash'
|
45
|
+
end
|
46
|
+
|
47
|
+
schema.definitions = hash['definitions'].map do |key, schema|
|
48
|
+
[key, from_json(schema)]
|
49
|
+
end.to_h
|
50
|
+
end
|
51
|
+
|
52
|
+
if hash.include?('ref')
|
53
|
+
raise TypeError, 'ref not String' unless hash['ref'].is_a?(String)
|
54
|
+
|
55
|
+
schema.ref = hash['ref']
|
56
|
+
end
|
57
|
+
|
58
|
+
if hash.include?('type')
|
59
|
+
raise TypeError, 'type not String' unless hash['type'].is_a?(String)
|
60
|
+
|
61
|
+
unless TYPES.map(&:to_s).include?(hash['type'])
|
62
|
+
raise TypeError, "type not in #{TYPES}"
|
63
|
+
end
|
64
|
+
|
65
|
+
schema.type = hash['type'].to_sym
|
66
|
+
end
|
67
|
+
|
68
|
+
if hash.include?('enum')
|
69
|
+
raise TypeError, 'enum not Array' unless hash['enum'].is_a?(Array)
|
70
|
+
raise ArgumentError, 'enum is empty array' if hash['enum'].empty?
|
71
|
+
|
72
|
+
hash['enum'].each do |value|
|
73
|
+
raise TypeError, 'enum element not String' unless value.is_a?(String)
|
74
|
+
end
|
75
|
+
|
76
|
+
schema.enum = hash['enum'].to_set
|
77
|
+
|
78
|
+
if schema.enum.size != hash['enum'].size
|
79
|
+
raise ArgumentError, 'enum contains duplicates'
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
if hash.include?('elements')
|
84
|
+
raise TypeError, 'elements not Hash' unless hash['elements'].is_a?(Hash)
|
85
|
+
|
86
|
+
schema.elements = from_json(hash['elements'])
|
87
|
+
end
|
88
|
+
|
89
|
+
if hash.include?('properties')
|
90
|
+
unless hash['properties'].is_a?(Hash)
|
91
|
+
raise TypeError, 'properties not Hash'
|
92
|
+
end
|
93
|
+
|
94
|
+
schema.properties = hash['properties'].map do |key, schema|
|
95
|
+
[key, from_json(schema)]
|
96
|
+
end.to_h
|
97
|
+
end
|
98
|
+
|
99
|
+
if hash.include?('optionalProperties')
|
100
|
+
unless hash['optionalProperties'].is_a?(Hash)
|
101
|
+
raise TypeError, 'optionalProperties not Hash'
|
102
|
+
end
|
103
|
+
|
104
|
+
optional_properties = hash['optionalProperties'].map do |key, schema|
|
105
|
+
[key, from_json(schema)]
|
106
|
+
end.to_h
|
107
|
+
|
108
|
+
schema.optional_properties = optional_properties
|
109
|
+
end
|
110
|
+
|
111
|
+
if hash.include?('additionalProperties')
|
112
|
+
unless [true, false].include?(hash['additionalProperties'])
|
113
|
+
raise TypeError, 'additionalProperties not boolean'
|
114
|
+
end
|
115
|
+
|
116
|
+
schema.additional_properties = hash['additionalProperties']
|
117
|
+
end
|
118
|
+
|
119
|
+
if hash.include?('values')
|
120
|
+
raise TypeError, 'values not Hash' unless hash['values'].is_a?(Hash)
|
121
|
+
|
122
|
+
schema.values = from_json(hash['values'])
|
123
|
+
end
|
124
|
+
|
125
|
+
if hash.include?('discriminator')
|
126
|
+
unless hash['discriminator'].is_a?(Hash)
|
127
|
+
raise TypeError, 'discriminator not Hash'
|
128
|
+
end
|
129
|
+
|
130
|
+
schema.discriminator = Discriminator.from_json(hash['discriminator'])
|
131
|
+
end
|
132
|
+
|
133
|
+
schema
|
134
|
+
end
|
135
|
+
|
136
|
+
def form
|
137
|
+
return :ref if ref
|
138
|
+
return :type if type
|
139
|
+
return :enum if enum
|
140
|
+
return :elements if elements
|
141
|
+
return :properties if properties || optional_properties
|
142
|
+
return :values if values
|
143
|
+
return :discriminator if discriminator
|
144
|
+
|
145
|
+
:empty
|
146
|
+
end
|
147
|
+
|
148
|
+
def verify(root = self)
|
149
|
+
empty = true
|
150
|
+
|
151
|
+
if ref
|
152
|
+
empty = false
|
153
|
+
|
154
|
+
unless root.definitions&.keys&.include?(ref)
|
155
|
+
raise ArgumentError, 'reference to non-existent definition'
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
if type
|
160
|
+
raise ArgumentError, 'invalid form' unless empty
|
161
|
+
|
162
|
+
empty = false
|
163
|
+
end
|
164
|
+
|
165
|
+
if enum
|
166
|
+
raise ArgumentError, 'invalid form' unless empty
|
167
|
+
|
168
|
+
empty = false
|
169
|
+
end
|
170
|
+
|
171
|
+
if elements
|
172
|
+
raise ArgumentError, 'invalid form' unless empty
|
173
|
+
|
174
|
+
empty = false
|
175
|
+
|
176
|
+
elements.verify(root)
|
177
|
+
end
|
178
|
+
|
179
|
+
if properties || optional_properties
|
180
|
+
raise ArgumentError, 'invalid form' unless empty
|
181
|
+
|
182
|
+
empty = false
|
183
|
+
|
184
|
+
properties&.values&.each { |schema| schema.verify(root) }
|
185
|
+
optional_properties&.values&.each { |schema| schema.verify(root) }
|
186
|
+
end
|
187
|
+
|
188
|
+
if values
|
189
|
+
raise ArgumentError, 'invalid form' unless empty
|
190
|
+
|
191
|
+
empty = false
|
192
|
+
|
193
|
+
values.verify(root)
|
194
|
+
end
|
195
|
+
|
196
|
+
if properties && optional_properties
|
197
|
+
unless (properties.keys & optional_properties.keys).empty?
|
198
|
+
raise ArgumentError, 'properties and optional_properties share key'
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
if discriminator
|
203
|
+
raise ArgumentError, 'invalid form' unless empty
|
204
|
+
|
205
|
+
discriminator.mapping.values.each do |schema|
|
206
|
+
schema.verify(root)
|
207
|
+
|
208
|
+
unless schema.form == :properties
|
209
|
+
raise ArgumentError, 'mapping value not of properties form'
|
210
|
+
end
|
211
|
+
|
212
|
+
if schema&.properties&.include?(discriminator.tag) ||
|
213
|
+
schema&.optional_properties&.include?(discriminator.tag)
|
214
|
+
raise ArgumentError, 'tag appears in mapping properties'
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
self
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
Discriminator = Struct.new(*DISCRIMINATOR_KEYWORDS) do
|
224
|
+
def self.from_json(hash)
|
225
|
+
raise TypeError, 'tag not String' unless hash['tag'].is_a?(String)
|
226
|
+
raise TypeError, 'mapping not Hash' unless hash['mapping'].is_a?(Hash)
|
227
|
+
|
228
|
+
mapping = hash['mapping'].map do |key, schema|
|
229
|
+
[key, Schema.from_json(schema)]
|
230
|
+
end.to_h
|
231
|
+
|
232
|
+
Discriminator.new(hash['tag'], mapping)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
@@ -0,0 +1,272 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module JDDF
|
6
|
+
# ValidationError
|
7
|
+
ValidationError = Struct.new(:instance_path, :schema_path)
|
8
|
+
|
9
|
+
# MaxDepthExceededError
|
10
|
+
class MaxDepthExceededError < StandardError
|
11
|
+
def initialize(msg = 'max depth exceeded while validating')
|
12
|
+
super
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Validator
|
17
|
+
class Validator
|
18
|
+
# MaxErrorsError
|
19
|
+
class MaxErrorsError < StandardError
|
20
|
+
end
|
21
|
+
|
22
|
+
private_constant :MaxErrorsError
|
23
|
+
|
24
|
+
# VM
|
25
|
+
class VM
|
26
|
+
attr_accessor :max_depth
|
27
|
+
attr_accessor :max_errors
|
28
|
+
attr_accessor :root_schema
|
29
|
+
attr_accessor :instance_tokens
|
30
|
+
attr_accessor :schema_tokens
|
31
|
+
attr_accessor :errors
|
32
|
+
|
33
|
+
def validate(schema, instance, parent_tag = nil)
|
34
|
+
case schema.form
|
35
|
+
when :ref
|
36
|
+
raise MaxDepthExceededError if schema_tokens.size == max_depth
|
37
|
+
|
38
|
+
schema_tokens << ['definitions', schema.ref]
|
39
|
+
validate(root_schema.definitions[schema.ref], instance)
|
40
|
+
schema_tokens.pop
|
41
|
+
when :type
|
42
|
+
push_schema_token('type')
|
43
|
+
|
44
|
+
case schema.type
|
45
|
+
when :boolean
|
46
|
+
push_error if instance != true && instance != false
|
47
|
+
when :float32, :float64
|
48
|
+
push_error unless instance.is_a?(Numeric)
|
49
|
+
when :int8
|
50
|
+
validate_int(instance, -128, 127)
|
51
|
+
when :uint8
|
52
|
+
validate_int(instance, 0, 255)
|
53
|
+
when :int16
|
54
|
+
validate_int(instance, -32_768, 32_767)
|
55
|
+
when :uint16
|
56
|
+
validate_int(instance, 0, 65_535)
|
57
|
+
when :int32
|
58
|
+
validate_int(instance, -2_147_483_648, 2_147_483_647)
|
59
|
+
when :uint32
|
60
|
+
validate_int(instance, 0, 4_294_967_295)
|
61
|
+
when :string
|
62
|
+
push_error unless instance.is_a?(String)
|
63
|
+
when :timestamp
|
64
|
+
begin
|
65
|
+
DateTime.rfc3339(instance)
|
66
|
+
rescue TypeError, ArgumentError
|
67
|
+
push_error
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
pop_schema_token
|
72
|
+
when :enum
|
73
|
+
push_schema_token('enum')
|
74
|
+
push_error unless schema.enum.include?(instance)
|
75
|
+
pop_schema_token
|
76
|
+
when :elements
|
77
|
+
push_schema_token('elements')
|
78
|
+
|
79
|
+
if instance.is_a?(Array)
|
80
|
+
instance.each_with_index do |sub_instance, index|
|
81
|
+
push_instance_token(index.to_s)
|
82
|
+
validate(schema.elements, sub_instance)
|
83
|
+
pop_instance_token
|
84
|
+
end
|
85
|
+
else
|
86
|
+
push_error
|
87
|
+
end
|
88
|
+
|
89
|
+
pop_schema_token
|
90
|
+
when :properties
|
91
|
+
if instance.is_a?(Hash)
|
92
|
+
if schema.properties
|
93
|
+
push_schema_token('properties')
|
94
|
+
|
95
|
+
schema.properties.each do |key, sub_schema|
|
96
|
+
push_schema_token(key)
|
97
|
+
|
98
|
+
if instance.include?(key)
|
99
|
+
push_instance_token(key)
|
100
|
+
validate(sub_schema, instance[key])
|
101
|
+
pop_instance_token
|
102
|
+
else
|
103
|
+
push_error
|
104
|
+
end
|
105
|
+
|
106
|
+
pop_schema_token
|
107
|
+
end
|
108
|
+
|
109
|
+
pop_schema_token
|
110
|
+
end
|
111
|
+
|
112
|
+
if schema.optional_properties
|
113
|
+
push_schema_token('optionalProperties')
|
114
|
+
|
115
|
+
schema.optional_properties.each do |key, sub_schema|
|
116
|
+
push_schema_token(key)
|
117
|
+
|
118
|
+
if instance.include?(key)
|
119
|
+
push_instance_token(key)
|
120
|
+
validate(sub_schema, instance[key])
|
121
|
+
pop_instance_token
|
122
|
+
end
|
123
|
+
|
124
|
+
pop_schema_token
|
125
|
+
end
|
126
|
+
|
127
|
+
pop_schema_token
|
128
|
+
end
|
129
|
+
|
130
|
+
unless schema.additional_properties
|
131
|
+
instance.keys.each do |key|
|
132
|
+
in_properties =
|
133
|
+
schema.properties&.include?(key)
|
134
|
+
in_optional_properties =
|
135
|
+
schema.optional_properties&.include?(key)
|
136
|
+
is_parent_tag = parent_tag == key
|
137
|
+
|
138
|
+
unless in_properties || in_optional_properties || is_parent_tag
|
139
|
+
push_instance_token(key)
|
140
|
+
push_error
|
141
|
+
pop_instance_token
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
else
|
146
|
+
if schema.properties.nil?
|
147
|
+
push_schema_token('optionalProperties')
|
148
|
+
else
|
149
|
+
push_schema_token('properties')
|
150
|
+
end
|
151
|
+
|
152
|
+
push_error
|
153
|
+
pop_schema_token
|
154
|
+
end
|
155
|
+
when :values
|
156
|
+
push_schema_token('values')
|
157
|
+
|
158
|
+
if instance.is_a?(Hash)
|
159
|
+
instance.each do |key, value|
|
160
|
+
push_instance_token(key)
|
161
|
+
validate(schema.values, value)
|
162
|
+
pop_instance_token
|
163
|
+
end
|
164
|
+
else
|
165
|
+
push_error
|
166
|
+
end
|
167
|
+
|
168
|
+
pop_schema_token
|
169
|
+
when :discriminator
|
170
|
+
push_schema_token('discriminator')
|
171
|
+
|
172
|
+
if instance.is_a?(Hash)
|
173
|
+
if instance.include?(schema.discriminator.tag)
|
174
|
+
tag_value = instance[schema.discriminator.tag]
|
175
|
+
|
176
|
+
if tag_value.is_a?(String)
|
177
|
+
push_schema_token('mapping')
|
178
|
+
|
179
|
+
if schema.discriminator.mapping.include?(tag_value)
|
180
|
+
sub_schema = schema.discriminator.mapping[tag_value]
|
181
|
+
|
182
|
+
push_schema_token(tag_value)
|
183
|
+
validate(sub_schema, instance, schema.discriminator.tag)
|
184
|
+
pop_schema_token
|
185
|
+
else
|
186
|
+
push_instance_token(schema.discriminator.tag)
|
187
|
+
push_error
|
188
|
+
pop_instance_token
|
189
|
+
end
|
190
|
+
|
191
|
+
pop_schema_token
|
192
|
+
else
|
193
|
+
push_instance_token(schema.discriminator.tag)
|
194
|
+
push_schema_token('tag')
|
195
|
+
push_error
|
196
|
+
pop_schema_token
|
197
|
+
pop_instance_token
|
198
|
+
end
|
199
|
+
else
|
200
|
+
push_schema_token('tag')
|
201
|
+
push_error
|
202
|
+
pop_schema_token
|
203
|
+
end
|
204
|
+
else
|
205
|
+
push_error
|
206
|
+
end
|
207
|
+
|
208
|
+
pop_schema_token
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def validate_int(instance, min, max)
|
213
|
+
if instance.is_a?(Numeric)
|
214
|
+
if instance.modulo(1).nonzero? || instance < min || instance > max
|
215
|
+
push_error
|
216
|
+
end
|
217
|
+
else
|
218
|
+
push_error
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def push_instance_token(token)
|
223
|
+
instance_tokens << token
|
224
|
+
end
|
225
|
+
|
226
|
+
def pop_instance_token
|
227
|
+
instance_tokens.pop
|
228
|
+
end
|
229
|
+
|
230
|
+
def push_schema_token(token)
|
231
|
+
schema_tokens.last << token
|
232
|
+
end
|
233
|
+
|
234
|
+
def pop_schema_token
|
235
|
+
schema_tokens.last.pop
|
236
|
+
end
|
237
|
+
|
238
|
+
def push_error
|
239
|
+
error = ValidationError.new
|
240
|
+
error.instance_path = instance_tokens.clone
|
241
|
+
error.schema_path = schema_tokens.last.clone
|
242
|
+
|
243
|
+
errors << error
|
244
|
+
|
245
|
+
raise MaxErrorsError if errors.size == max_errors
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
private_constant :VM
|
250
|
+
|
251
|
+
attr_accessor :max_depth
|
252
|
+
attr_accessor :max_errors
|
253
|
+
|
254
|
+
def validate(schema, instance)
|
255
|
+
vm = VM.new
|
256
|
+
vm.max_depth = max_depth
|
257
|
+
vm.max_errors = max_errors
|
258
|
+
vm.root_schema = schema
|
259
|
+
vm.instance_tokens = []
|
260
|
+
vm.schema_tokens = [[]]
|
261
|
+
vm.errors = []
|
262
|
+
|
263
|
+
begin
|
264
|
+
vm.validate(schema, instance)
|
265
|
+
rescue MaxErrorsError # rubocop:disable Lint/HandleExceptions
|
266
|
+
# There is nothing to do here. MaxErrorsError is just a circuit-breaker.
|
267
|
+
end
|
268
|
+
|
269
|
+
vm.errors
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
data/lib/jddf/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jddf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ulysse Carion
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-10-
|
11
|
+
date: 2019-10-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.75.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.75.0
|
55
69
|
description:
|
56
70
|
email:
|
57
71
|
- ulysse@segment.com
|
@@ -60,17 +74,28 @@ extensions: []
|
|
60
74
|
extra_rdoc_files: []
|
61
75
|
files:
|
62
76
|
- ".gitignore"
|
77
|
+
- ".gitmodules"
|
63
78
|
- ".rspec"
|
64
|
-
- ".
|
79
|
+
- ".rubocop.yml"
|
65
80
|
- Gemfile
|
66
81
|
- Gemfile.lock
|
67
82
|
- LICENSE.txt
|
68
83
|
- README.md
|
69
84
|
- Rakefile
|
85
|
+
- bin/bundle
|
70
86
|
- bin/console
|
87
|
+
- bin/htmldiff
|
88
|
+
- bin/ldiff
|
89
|
+
- bin/rake
|
90
|
+
- bin/rspec
|
91
|
+
- bin/rubocop
|
92
|
+
- bin/ruby-parse
|
93
|
+
- bin/ruby-rewrite
|
71
94
|
- bin/setup
|
72
95
|
- jddf.gemspec
|
73
96
|
- lib/jddf.rb
|
97
|
+
- lib/jddf/schema.rb
|
98
|
+
- lib/jddf/validator.rb
|
74
99
|
- lib/jddf/version.rb
|
75
100
|
homepage: https://github.com/jddf/jddf-ruby
|
76
101
|
licenses:
|