shacl 0.1.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 +7 -0
- data/LICENSE +24 -0
- data/README.md +159 -0
- data/VERSION +1 -0
- data/etc/doap.ttl +35 -0
- data/lib/rdf/vocab/shacl.rb +2073 -0
- data/lib/shacl.rb +93 -0
- data/lib/shacl/algebra.rb +34 -0
- data/lib/shacl/algebra/and.rb +51 -0
- data/lib/shacl/algebra/node_shape.rb +80 -0
- data/lib/shacl/algebra/not.rb +30 -0
- data/lib/shacl/algebra/operator.rb +329 -0
- data/lib/shacl/algebra/or.rb +46 -0
- data/lib/shacl/algebra/property_shape.rb +190 -0
- data/lib/shacl/algebra/qualified_value_shape.rb +47 -0
- data/lib/shacl/algebra/shape.rb +499 -0
- data/lib/shacl/algebra/xone.rb +65 -0
- data/lib/shacl/context.rb +41 -0
- data/lib/shacl/format.rb +88 -0
- data/lib/shacl/refinements.rb +198 -0
- data/lib/shacl/shapes.rb +160 -0
- data/lib/shacl/validation_report.rb +109 -0
- data/lib/shacl/validation_result.rb +153 -0
- data/lib/shacl/version.rb +19 -0
- metadata +217 -0
@@ -0,0 +1,109 @@
|
|
1
|
+
$:.unshift(File.expand_path("../..", __FILE__))
|
2
|
+
|
3
|
+
require 'rdf'
|
4
|
+
require 'sxp'
|
5
|
+
require_relative 'refinements'
|
6
|
+
require_relative 'validation_report'
|
7
|
+
|
8
|
+
module SHACL
|
9
|
+
# A SHACL [Validateion Report](https://www.w3.org/TR/shacl/#results-validation-report).
|
10
|
+
#
|
11
|
+
# Collects the individual {SHACL::ValidationResult} instances and provides a `conforms` boolean accessor.
|
12
|
+
#
|
13
|
+
# Allows the report to be serialized as a set of RDF Statements
|
14
|
+
class ValidationReport
|
15
|
+
include RDF::Enumerable
|
16
|
+
using SHACL::Refinements
|
17
|
+
|
18
|
+
##
|
19
|
+
# All results, both conforming and non-conforming
|
20
|
+
attr_reader :all_results
|
21
|
+
|
22
|
+
##
|
23
|
+
# Creates a report from the set of results
|
24
|
+
#
|
25
|
+
# @param [Array<ValidationResult>] results
|
26
|
+
# @return [ValidationReport]
|
27
|
+
def initialize(results)
|
28
|
+
@all_results = Array(results)
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# The non-conforming results
|
33
|
+
#
|
34
|
+
# @return [Array<ValidationResult>]
|
35
|
+
def results
|
36
|
+
@all_results.reject(&:conform?)
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# The number of non-conforming results
|
41
|
+
#
|
42
|
+
# @return [Integer]
|
43
|
+
def count
|
44
|
+
results.length
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Do the individual results indicate conformance?
|
49
|
+
#
|
50
|
+
# @return [Boolean]
|
51
|
+
def conform?
|
52
|
+
results.empty?
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# The number of results
|
57
|
+
#
|
58
|
+
def to_sxp_bin
|
59
|
+
[:ValidationReport, conform?, results].to_sxp_bin
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_sxp
|
63
|
+
self.to_sxp_bin.to_sxp
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_s
|
67
|
+
results.map(&:to_s).join("\n")
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# Two reports are eq if they have the same number of results and each result equals a result in the other report.
|
72
|
+
# @param [ValidationReport] other
|
73
|
+
# @return [Boolean]
|
74
|
+
def ==(other)
|
75
|
+
return false unless other.is_a?(ValidationReport)
|
76
|
+
count == other.count && other.results.all? {|r| results.include?(r)}
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# Create a hash of messages appropriate for linter-like output.
|
81
|
+
#
|
82
|
+
# @return [Hash{Symbol => Hash{Symbol => Array<String>}}]
|
83
|
+
def linter_messages
|
84
|
+
results.inject({}) {|memo, result| memo.deep_merge(result.linter_message)}
|
85
|
+
end
|
86
|
+
|
87
|
+
##
|
88
|
+
# Yields statements for this report
|
89
|
+
#
|
90
|
+
# @yield [statement]
|
91
|
+
# each statement
|
92
|
+
# @yieldparam [RDF::Statement] statement
|
93
|
+
# @yieldreturn [void] ignored
|
94
|
+
# @return [void]
|
95
|
+
def each(&block)
|
96
|
+
subject = RDF::Node.new
|
97
|
+
block.call(RDF::Statement(subject, RDF.type, RDF::Vocab::SHACL.ValidationReport))
|
98
|
+
block.call(RDF::Statement(subject, RDF::Vocab::SHACL.conforms, RDF::Literal(conform?)))
|
99
|
+
results.each do |result|
|
100
|
+
result_subject = nil
|
101
|
+
result.each do |statement|
|
102
|
+
result_subject ||= statement.subject
|
103
|
+
yield(statement)
|
104
|
+
end
|
105
|
+
yield(RDF::Statement(subject, RDF::Vocab::SHACL.result, result_subject))
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
$:.unshift(File.expand_path("../..", __FILE__))
|
2
|
+
|
3
|
+
require 'rdf'
|
4
|
+
require 'sxp'
|
5
|
+
require_relative 'context'
|
6
|
+
require_relative 'refinements'
|
7
|
+
|
8
|
+
module SHACL
|
9
|
+
# A SHACL [Validateion Result](https://www.w3.org/TR/shacl/#results-validation-result).
|
10
|
+
#
|
11
|
+
# Also allows for a successful result, if the `resultSeverity` `nil`.
|
12
|
+
ValidationResult = Struct.new(
|
13
|
+
:focus,
|
14
|
+
:path,
|
15
|
+
:shape,
|
16
|
+
:resultSeverity,
|
17
|
+
:component,
|
18
|
+
:details,
|
19
|
+
:value,
|
20
|
+
:message) do
|
21
|
+
|
22
|
+
include RDF::Enumerable
|
23
|
+
using SHACL::Refinements
|
24
|
+
|
25
|
+
##
|
26
|
+
# Initializer calculates lexical values for URIs
|
27
|
+
def initialize(*args)
|
28
|
+
args = args.map do |v|
|
29
|
+
if v.respond_to?(:qname) && !v.lexical && v.qname
|
30
|
+
v = RDF::URI.new(v.to_s) if v.frozen?
|
31
|
+
v.lexical = v.qname.join(':')
|
32
|
+
end
|
33
|
+
v
|
34
|
+
end
|
35
|
+
super(*args)
|
36
|
+
end
|
37
|
+
|
38
|
+
# A result conforms if it is not a violation
|
39
|
+
#
|
40
|
+
# @return [Boolean]
|
41
|
+
def conform?
|
42
|
+
resultSeverity.nil?
|
43
|
+
end
|
44
|
+
alias_method :conforms?, :conform?
|
45
|
+
|
46
|
+
def to_sxp_bin
|
47
|
+
%i(value focus path shape resultSeverity component details message).inject([:ValidationResult]) do |memo, sym|
|
48
|
+
v = self.send(sym)
|
49
|
+
v ? (memo + [[sym, *v]]) : memo
|
50
|
+
end.to_sxp_bin
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_sxp
|
54
|
+
self.to_sxp_bin.to_sxp
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# Create a hash of messages appropriate for linter-like output.
|
59
|
+
#
|
60
|
+
# @return [Hash{Symbol => Hash{Symbol => Array<String>}}]
|
61
|
+
def linter_message
|
62
|
+
case
|
63
|
+
when path then {path: {path.to_sxp => [to_s]}}
|
64
|
+
when focus then {focus: {focus.to_sxp => [to_s]}}
|
65
|
+
else {shape: {shape.to_sxp => [to_s]}}
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Some humanized result for the report
|
71
|
+
def to_s
|
72
|
+
"Result for: " +
|
73
|
+
%i(value focus path shape resultSeverity component details message).map do |sym|
|
74
|
+
v = self.send(sym)
|
75
|
+
if v.respond_to?(:humanize)
|
76
|
+
v.humanize
|
77
|
+
elsif v.respond_to?(:lexical)
|
78
|
+
v.lexical
|
79
|
+
else
|
80
|
+
v.to_sxp
|
81
|
+
end
|
82
|
+
(sym == :value ? v.to_sxp : "#{sym}: #{v.to_sxp}") if v
|
83
|
+
end.compact.join("\n ")
|
84
|
+
end
|
85
|
+
|
86
|
+
##
|
87
|
+
# Yields statements for this result
|
88
|
+
#
|
89
|
+
# @yield [statement]
|
90
|
+
# each statement
|
91
|
+
# @yieldparam [RDF::Statement] statement
|
92
|
+
# @yieldreturn [void] ignored
|
93
|
+
# @return [void]
|
94
|
+
def each(&block)
|
95
|
+
subject = RDF::Node.new
|
96
|
+
block.call(RDF::Statement(subject, RDF.type, RDF::Vocab::SHACL.ValidationResult))
|
97
|
+
|
98
|
+
block.call(RDF::Statement(subject, RDF::Vocab::SHACL.focusNode, focus)) if focus
|
99
|
+
case path
|
100
|
+
when RDF::URI
|
101
|
+
block.call(RDF::Statement(subject, RDF::Vocab::SHACL.resultPath, path))
|
102
|
+
when SPARQL::Algebra::Expression
|
103
|
+
path.each_statement(&block)
|
104
|
+
block.call(RDF::Statement(subject, RDF::Vocab::SHACL.resultPath, path.subject))
|
105
|
+
end
|
106
|
+
block.call(RDF::Statement(subject, RDF::Vocab::SHACL.resultSeverity, resultSeverity)) if resultSeverity
|
107
|
+
block.call(RDF::Statement(subject, RDF::Vocab::SHACL.sourceConstraintComponent, component)) if component
|
108
|
+
block.call(RDF::Statement(subject, RDF::Vocab::SHACL.sourceShape, shape)) if shape
|
109
|
+
block.call(RDF::Statement(subject, RDF::Vocab::SHACL.value, value)) if value
|
110
|
+
block.call(RDF::Statement(subject, RDF::Vocab::SHACL.detail, RDF::Literal(details))) if details
|
111
|
+
block.call(RDF::Statement(subject, RDF::Vocab::SHACL.resultMessage, RDF::Literal(message))) if message
|
112
|
+
end
|
113
|
+
|
114
|
+
# Transform a JSON representation of a result, into a native representation
|
115
|
+
# @param [Hash] input
|
116
|
+
# @return [ValidationResult]
|
117
|
+
def self.from_json(input, **options)
|
118
|
+
input = JSON.parse(input) if input.is_a?(String)
|
119
|
+
input = JSON::LD::API.compact(input,
|
120
|
+
"http://github.com/ruby-rdf/shacl/",
|
121
|
+
expandContext: "http://github.com/ruby-rdf/shacl/")
|
122
|
+
raise ArgumentError, "Expect report to be a hash" unless input.is_a?(Hash)
|
123
|
+
result = self.new
|
124
|
+
|
125
|
+
result.focus = Algebra::Operator.to_rdf(:focus, input['focusNode'], base: nil, vocab: false) if input['focusNode']
|
126
|
+
result.path = Algebra::Operator.parse_path(input['resultPath'], **options) if input['resultPath']
|
127
|
+
result.resultSeverity = Algebra::Operator.iri(input['resultSeverity'], **options) if input['resultSeverity']
|
128
|
+
result.component = Algebra::Operator.iri(input['sourceConstraintComponent'], **options) if input['sourceConstraintComponent']
|
129
|
+
result.shape = Algebra::Operator.iri(input['sourceShape'], **options) if input['sourceShape']
|
130
|
+
result.value = Algebra::Operator.to_rdf(:value, input['value'], **options) if input['value']
|
131
|
+
result.details = Algebra::Operator.to_rdf(:details, input['details'], **options) if input['details']
|
132
|
+
result.message = Algebra::Operator.to_rdf(:message, input['message'], **options) if input['message']
|
133
|
+
result
|
134
|
+
end
|
135
|
+
|
136
|
+
# To results are eql? if their overlapping properties are equal
|
137
|
+
# @param [ValidationResult] other
|
138
|
+
# @return [Boolean]
|
139
|
+
def ==(other)
|
140
|
+
return false unless other.is_a?(ValidationResult)
|
141
|
+
%i(focus path resultSeverity component shape value).all? do |prop|
|
142
|
+
ours = self.send(prop)
|
143
|
+
theirs = other.send(prop)
|
144
|
+
theirs.nil? || (ours && ours.node? && theirs.node?) || ours && ours.eql?(theirs)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Inspect as SXP
|
149
|
+
def inspect
|
150
|
+
SXP::Generator.string to_sxp_bin
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module SHACL
|
2
|
+
module VERSION
|
3
|
+
FILE = File.expand_path('../../../VERSION', __FILE__)
|
4
|
+
MAJOR, MINOR, TINY, EXTRA = File.read(FILE).chomp.split('.')
|
5
|
+
STRING = [MAJOR, MINOR, TINY, EXTRA].compact.join('.').freeze
|
6
|
+
|
7
|
+
##
|
8
|
+
# @return [String]
|
9
|
+
def self.to_s() STRING end
|
10
|
+
|
11
|
+
##
|
12
|
+
# @return [String]
|
13
|
+
def self.to_str() STRING end
|
14
|
+
|
15
|
+
##
|
16
|
+
# @return [Array(String, String, String, String)]
|
17
|
+
def self.to_a() [MAJOR, MINOR, TINY, EXTRA].compact end
|
18
|
+
end
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,217 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: shacl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gregg Kellogg
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-12-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rdf
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.1'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 3.1.8
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '3.1'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 3.1.8
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: json-ld
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '3.1'
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 3.1.7
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '3.1'
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 3.1.7
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: sxp
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - "~>"
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '1.1'
|
60
|
+
type: :runtime
|
61
|
+
prerelease: false
|
62
|
+
version_requirements: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - "~>"
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '1.1'
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: sparql
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - "~>"
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '3.1'
|
74
|
+
type: :runtime
|
75
|
+
prerelease: false
|
76
|
+
version_requirements: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - "~>"
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '3.1'
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: rdf-spec
|
83
|
+
requirement: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - "~>"
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '3.1'
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - "~>"
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '3.1'
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: rdf-turtle
|
97
|
+
requirement: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - "~>"
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '3.1'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - "~>"
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '3.1'
|
109
|
+
- !ruby/object:Gem::Dependency
|
110
|
+
name: rdf-xsd
|
111
|
+
requirement: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - "~>"
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '3.1'
|
116
|
+
type: :development
|
117
|
+
prerelease: false
|
118
|
+
version_requirements: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - "~>"
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '3.1'
|
123
|
+
- !ruby/object:Gem::Dependency
|
124
|
+
name: rspec
|
125
|
+
requirement: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - "~>"
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '3.10'
|
130
|
+
type: :development
|
131
|
+
prerelease: false
|
132
|
+
version_requirements: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - "~>"
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '3.10'
|
137
|
+
- !ruby/object:Gem::Dependency
|
138
|
+
name: rspec-its
|
139
|
+
requirement: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - "~>"
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '1.3'
|
144
|
+
type: :development
|
145
|
+
prerelease: false
|
146
|
+
version_requirements: !ruby/object:Gem::Requirement
|
147
|
+
requirements:
|
148
|
+
- - "~>"
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
version: '1.3'
|
151
|
+
- !ruby/object:Gem::Dependency
|
152
|
+
name: yard
|
153
|
+
requirement: !ruby/object:Gem::Requirement
|
154
|
+
requirements:
|
155
|
+
- - "~>"
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0.9'
|
158
|
+
type: :development
|
159
|
+
prerelease: false
|
160
|
+
version_requirements: !ruby/object:Gem::Requirement
|
161
|
+
requirements:
|
162
|
+
- - "~>"
|
163
|
+
- !ruby/object:Gem::Version
|
164
|
+
version: '0.9'
|
165
|
+
description: Implements SHACL Core and SHACL-SPARQL within the RDF.rb ecosystem.
|
166
|
+
email: public-rdf-ruby@w3.org
|
167
|
+
executables: []
|
168
|
+
extensions: []
|
169
|
+
extra_rdoc_files: []
|
170
|
+
files:
|
171
|
+
- LICENSE
|
172
|
+
- README.md
|
173
|
+
- VERSION
|
174
|
+
- etc/doap.ttl
|
175
|
+
- lib/rdf/vocab/shacl.rb
|
176
|
+
- lib/shacl.rb
|
177
|
+
- lib/shacl/algebra.rb
|
178
|
+
- lib/shacl/algebra/and.rb
|
179
|
+
- lib/shacl/algebra/node_shape.rb
|
180
|
+
- lib/shacl/algebra/not.rb
|
181
|
+
- lib/shacl/algebra/operator.rb
|
182
|
+
- lib/shacl/algebra/or.rb
|
183
|
+
- lib/shacl/algebra/property_shape.rb
|
184
|
+
- lib/shacl/algebra/qualified_value_shape.rb
|
185
|
+
- lib/shacl/algebra/shape.rb
|
186
|
+
- lib/shacl/algebra/xone.rb
|
187
|
+
- lib/shacl/context.rb
|
188
|
+
- lib/shacl/format.rb
|
189
|
+
- lib/shacl/refinements.rb
|
190
|
+
- lib/shacl/shapes.rb
|
191
|
+
- lib/shacl/validation_report.rb
|
192
|
+
- lib/shacl/validation_result.rb
|
193
|
+
- lib/shacl/version.rb
|
194
|
+
homepage: https://ruby-rdf.github.com/shacl
|
195
|
+
licenses:
|
196
|
+
- Unlicense
|
197
|
+
metadata: {}
|
198
|
+
post_install_message:
|
199
|
+
rdoc_options: []
|
200
|
+
require_paths:
|
201
|
+
- lib
|
202
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
203
|
+
requirements:
|
204
|
+
- - ">="
|
205
|
+
- !ruby/object:Gem::Version
|
206
|
+
version: '2.4'
|
207
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
208
|
+
requirements:
|
209
|
+
- - ">="
|
210
|
+
- !ruby/object:Gem::Version
|
211
|
+
version: '0'
|
212
|
+
requirements: []
|
213
|
+
rubygems_version: 3.1.4
|
214
|
+
signing_key:
|
215
|
+
specification_version: 4
|
216
|
+
summary: Implementation of Shapes Constraint Language (SHACL) for RDF.rb
|
217
|
+
test_files: []
|