diff --git a/.travis.yml b/.travis.yml index 3e17ac6..3a1eb50 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,17 @@ language: ruby bundler_args: --without debug script: "bundle exec rspec spec" -before_install: "gem update --system" +before_install: + - 'gem update --system --conservative || (gem i "rubygems-update:~>2.7" --no-document && update_rubygems)' + - 'gem update bundler --conservative' env: - CI=true rvm: - - 2.2 + - 2.2.2 - 2.3 - 2.4 - 2.5 + - 2.6 - jruby-9 - rbx-3 cache: bundler diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e462370..a12efba 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ Community contributions are essential for keeping Ruby RDF great. We want to kee This repository uses [Git Flow](https://github.com/nvie/gitflow) to manage development and release activity. All submissions _must_ be on a feature branch based on the _develop_ branch to ease staging and integration. -* create or respond to an issue on the [Github Repository](http://github.com/ruby-rdf/rdf-reasoner/issues) +* create or respond to an issue on the [Github Repository](https://githubhub.com/ruby-rdf/rdf-reasoner/issues) * Fork and clone the repo: `git clone git@github.com:your-username/rdf-reasoner.git` * Install bundle: @@ -30,7 +30,7 @@ This repository uses [Git Flow](https://github.com/nvie/gitflow) to manage devel of thumb, additions larger than about 15 lines of code), we need an explicit [public domain dedication][PDD] on record from you. -[YARD]: http://yardoc.org/ -[YARD-GS]: http://rubydoc.info/docs/yard/file/docs/GettingStarted.md -[PDD]: http://lists.w3.org/Archives/Public/public-rdf-ruby/2010May/0013.html +[YARD]: https://yardoc.org/ +[YARD-GS]: https://rubydoc.info/docs/yard/file/docs/GettingStarted.md +[PDD]: https://lists.w3.org/Archives/Public/public-rdf-ruby/2010May/0013.html [pr]: https://github.com/ruby-rdf/rdf-reasoner/compare/ diff --git a/Gemfile b/Gemfile index d103a0c..cb88497 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,4 @@ -source "http://rubygems.org" +source "https://rubygems.org" gemspec @@ -23,8 +23,3 @@ group :debug do gem "redcarpet", platforms: :ruby gem "byebug", platforms: :mri end - -platforms :rbx do - gem 'rubysl', '~> 2.0' - gem 'rubinius', '~> 2.0' -end diff --git a/README.md b/README.md index 095c246..89e704a 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Reasons over RDFS/OWL vocabularies and schema.org to generate statements which a * Entail `rdfs:subPropertyOf` generating an array of terms which are ancestors of the subject. * Entail `rdfs:domain` and `rdfs:range` adding `rdf:type` assertions on the subject or object. * Inverse `rdfs:subClassOf` entailment, to find descendant classes of the subject term. +* Inverse `rdfs:subPropertyOf` entailment, to find descendant properties of the subject term. * Entail `owl:equivalentClass` generating an array of terms equivalent to the subject. * Entail `owl:equivalentProperty` generating an array of terms equivalent to the subject. * `domainCompatible?` determines if a particular resource is compatible with the domain definition of a given predicate, based on the intersection of entailed subclasses with the property domain. @@ -51,7 +52,7 @@ Domain and Range entailment include specific rules for schema.org vocabularies. RDF::Reasoner.apply(:rdfs) graph = RDF::Graph.load("etc/doap.ttl") - subj = RDF::URI("http://rubygems.org/gems/rdf-reasoner") + subj = RDF::URI("https://rubygems.org/gems/rdf-reasoner") RDF::Vocab::DOAP.name.domain_compatible?(subj, graph) # => true ### Determine if a resource is compatible with the ranges of a property @@ -103,16 +104,16 @@ The `rdf` command-line interface is extended with `entail` and `lint` commands. ## Dependencies -* [Ruby](http://ruby-lang.org/) (>= 2.2.2) -* [RDF.rb](http://rubygems.org/gems/rdf) (~> 3.0) +* [Ruby](https://ruby-lang.org/) (>= 2.2.2) +* [RDF.rb](https://rubygems.org/gems/rdf) (~> 3.0) ## Mailing List -* +* ## Authors -* [Gregg Kellogg](http://github.com/gkellogg) - +* [Gregg Kellogg](https://githubhub.com/gkellogg) - ## Contributing @@ -132,17 +133,17 @@ The `rdf` command-line interface is extended with `entail` and `lint` commands. ## License This is free and unencumbered public domain software. For more information, -see or the accompanying {file:UNLICENSE} file. - -[Ruby]: http://ruby-lang.org/ -[RDF]: http://www.w3.org/RDF/ -[YARD]: http://yardoc.org/ -[YARD-GS]: http://rubydoc.info/docs/yard/file/docs/GettingStarted.md -[PDD]: http://lists.w3.org/Archives/Public/public-rdf-ruby/2010May/0013.html -[SPARQL]: http://en.wikipedia.org/wiki/SPARQL -[SPARQL Query]: http://www.w3.org/TR/2013/REC-sparql11-query-20130321/ -[SPARQL Entailment]:http://www.w3.org/TR/sparql11-entailment/ -[RDF 1.1]: http://www.w3.org/TR/rdf11-concepts -[RDF.rb]: http://www.rubydoc.info/github/ruby-rdf/rdf/ -[RDF Schema]: http://www.w3.org/TR/rdf-schema/ +see or the accompanying {file:UNLICENSE} file. + +[Ruby]: https://ruby-lang.org/ +[RDF]: https://www.w3.org/RDF/ +[YARD]: https://yardoc.org/ +[YARD-GS]: https://rubydoc.info/docs/yard/file/docs/GettingStarted.md +[PDD]: https://lists.w3.org/Archives/Public/public-rdf-ruby/2010May/0013.html +[SPARQL]: https://en.wikipedia.org/wiki/SPARQL +[SPARQL Query]: https://www.w3.org/TR/2013/REC-sparql11-query-20130321/ +[SPARQL Entailment]:https://www.w3.org/TR/sparql11-entailment/ +[RDF 1.1]: https://www.w3.org/TR/rdf11-concepts +[RDF.rb]: https://www.rubydoc.info/github/ruby-rdf/rdf/ +[RDF Schema]: https://www.w3.org/TR/rdf-schema/ [Rack]: https://rack.github.io/ diff --git a/UNLICENSE b/UNLICENSE index 68a49da..efb9808 100644 --- a/UNLICENSE +++ b/UNLICENSE @@ -21,4 +21,4 @@ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -For more information, please refer to +For more information, please refer to diff --git a/VERSION b/VERSION index 4b9fcbe..cb0c939 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.5.1 +0.5.2 diff --git a/etc/doap.ttl b/etc/doap.ttl index 6faf513..549beb4 100644 --- a/etc/doap.ttl +++ b/etc/doap.ttl @@ -7,9 +7,9 @@ @prefix ex: . @prefix xsd: . - a doap:Project, earl:TestSubject, earl:Software ; + a doap:Project, earl:TestSubject, earl:Software ; doap:name "RDF::Reasoner" ; - doap:homepage ; + doap:homepage ; doap:license ; doap:shortdesc "RDFS/OWL/Schema.org Reasoner for RDF.rb."@en ; doap:description """ @@ -25,14 +25,14 @@ ; doap:category , ; - doap:download-page ; + doap:download-page ; doap:mailing-list ; - doap:bug-database ; - doap:blog ; - doap:developer ; - doap:maintainer ; - doap:documenter ; - foaf:maker ; + doap:bug-database ; + doap:blog ; + doap:developer ; + doap:maintainer ; + doap:documenter ; + foaf:maker ; dc:title "RDF::Reasoner" ; dc:description """ Reasons over RDFS/OWL vocabularies to generate statements which are @@ -42,8 +42,8 @@ SPARQL Entailment Regimes. """@en ; dc:date "2014-06-01"^^xsd:date ; - dc:creator ; - dc:isPartOf . + dc:creator ; + dc:isPartOf . - a foaf:Person, foaf:Agent, dc:Agent; + a foaf:Person, foaf:Agent, dc:Agent; foaf:name "Gregg Kellogg". \ No newline at end of file diff --git a/lib/rdf/reasoner.rb b/lib/rdf/reasoner.rb index 68dc02d..bfac0d8 100644 --- a/lib/rdf/reasoner.rb +++ b/lib/rdf/reasoner.rb @@ -7,7 +7,7 @@ module RDF # RDFS/OWL reasonsing for RDF.rb. # # @see http://www.w3.org/TR/2013/REC-sparql11-entailment-20130321/ - # @author [Gregg Kellogg](http://greggkellogg.net/) + # @author [Gregg Kellogg](https://greggkellogg.net/) module Reasoner require 'rdf/reasoner/format' autoload :OWL, 'rdf/reasoner/owl' diff --git a/lib/rdf/reasoner/format.rb b/lib/rdf/reasoner/format.rb index 06973e2..9dd59c6 100644 --- a/lib/rdf/reasoner/format.rb +++ b/lib/rdf/reasoner/format.rb @@ -5,7 +5,7 @@ module RDF::Reasoner # @example Obtaining an LD Patch format class # RDF::Format.for(:reasoner) #=> RDF::Reasoner::Format # - # @see http://www.w3.org/TR/ldpatch/ + # @see https://www.w3.org/TR/ldpatch/ class Format < RDF::Format ## diff --git a/lib/rdf/reasoner/rdfs.rb b/lib/rdf/reasoner/rdfs.rb index a94468e..359ac1f 100644 --- a/lib/rdf/reasoner/rdfs.rb +++ b/lib/rdf/reasoner/rdfs.rb @@ -34,6 +34,20 @@ def subPropertyOf_cache @@subPropertyOf_cache ||= RDF::Util::Cache.new(-1) end + ## + # @return [RDF::Util::Cache] + # @private + def subProperty_cache + @@subProperty_cache ||= RDF::Util::Cache.new(-1) + end + + ## + # @return [RDF::Util::Cache] + # @private + def descendant_property_cache + @@descendant_property_cache ||= RDF::Util::Cache.new(-1) + end + ## # For a Term: yield or return inferred subClassOf relationships by recursively applying to named super classes to get a complete set of classes in the ancestor chain of this class # For a Statement: if predicate is `rdf:types`, yield or return inferred statements having a subClassOf relationship to the type of this statement @@ -139,6 +153,50 @@ def _entail_subPropertyOf end end + ## + # For a Term: yield or return inferred subProperty relationships + # by recursively applying to named subproperties to get a complete + # set of properties in the descendant chain of this property + # + # For a Statement: this is a no-op, as it's not useful in this context + # @private + + def _entail_subProperty + case self + when RDF::URI, RDF::Node + unless property? + yield self if block_given? + return Array(self) + end + + terms = descendant_property_cache[self] ||= ( + Array(self.subProperty).map do |c| + c._entail_subProperty rescue c + end.flatten + Array(self)).compact + + terms.each {|t| yield t } if block_given? + terms + else [] + end + end + + ## + # Get the immediate subproperties of this property. + # + # This iterates over terms defined in the vocabulary of this term, + # as well as the vocabularies imported by this vocabulary. + # @return [Array] + def subProperty + raise RDF::Reasoner::Error, + "#{self} Can't entail subProperty" unless property? + vocabs = [self.vocab] + self.vocab.imported_from + subProperty_cache[self] ||= vocabs.map do |v| + Array(v.properties).select do |p| + p.property? && Array(p.subPropertyOf).include?(self) + end + end.flatten.compact + end + ## # For a Statement: yield or return inferred statements having an rdf:type of the domain of the statement predicate # @todo Should be able to entail owl:unionOf, which is a BNode. This should be allowed, and also add BNode values of that node, recursively, similar to SPARQL concise_bounded_description.uu @@ -303,6 +361,7 @@ def self.included(mod) mod.add_entailment :subClassOf, :_entail_subClassOf mod.add_entailment :subClass, :_entail_subClass mod.add_entailment :subPropertyOf, :_entail_subPropertyOf + mod.add_entailment :subProperty, :_entail_subProperty mod.add_entailment :domain, :_entail_domain mod.add_entailment :range, :_entail_range end @@ -319,4 +378,4 @@ def self.included(mod) # Extend Mutable with these methods ::RDF::Mutable.send(:include, RDFS) -end \ No newline at end of file +end diff --git a/rdf-reasoner.gemspec b/rdf-reasoner.gemspec index 68886de..ea5a1fc 100755 --- a/rdf-reasoner.gemspec +++ b/rdf-reasoner.gemspec @@ -6,7 +6,7 @@ Gem::Specification.new do |gem| gem.date = File.mtime('VERSION').strftime('%Y-%m-%d') gem.name = "rdf-reasoner" - gem.homepage = "http://github.com/gkellogg/rdf-reasoner" + gem.homepage = "https://githubhub.com/gkellogg/rdf-reasoner" gem.license = 'Unlicense' gem.summary = "RDFS/OWL Reasoner for RDF.rb" @@ -16,7 +16,6 @@ Gem::Specification.new do |gem| gem.platform = Gem::Platform::RUBY gem.files = %w(AUTHORS README.md UNLICENSE VERSION) + Dir.glob('lib/**/*.rb') gem.require_paths = %w(lib) - gem.has_rdoc = false gem.description = %(Reasons over RDFS/OWL vocabularies to generate statements which are entailed based on base RDFS/OWL rules along with vocabulary information. It can also be used to ask specific @@ -32,10 +31,9 @@ Gem::Specification.new do |gem| gem.add_development_dependency 'rdf-spec', '~> 3.0' gem.add_development_dependency 'rdf-turtle', '~> 3.0' - #gem.add_development_dependency 'json-ld', '~> 3.0' - gem.add_development_dependency 'json-ld', '>= 2.2', '< 4.0' + gem.add_development_dependency 'json-ld', '~> 3.0' gem.add_development_dependency 'equivalent-xml', '~> 0.6' - gem.add_development_dependency 'rspec', '~> 3.7' - gem.add_development_dependency 'yard' , '~> 0.9.12' + gem.add_development_dependency 'rspec', '~> 3.8' + gem.add_development_dependency 'yard' , '~> 0.9.19' gem.post_install_message = nil end diff --git a/spec/rdfs_spec.rb b/spec/rdfs_spec.rb index af8f092..9b3a8b2 100644 --- a/spec/rdfs_spec.rb +++ b/spec/rdfs_spec.rb @@ -137,6 +137,61 @@ end end + # XXX this is cribbed from :subClass + describe :subProperty do + { + RDF::Vocab::DC.relation => %w(conformsTo hasFormat hasPart hasVersion + isFormatOf isPartOf isReferencedBy isReplacedBy isRequiredBy + isVersionOf references relation replaces requires source).map { + |t| RDF::Vocab::DC[t] } + %w(derived_from djmix_of mashup_of medley_of + remaster_of remix_of sampled_version_of).map {|t| RDF::Vocab::MO[t] }, + RDF::Vocab::SIOC.space_of => %w(host_of).map {|t| RDF::Vocab::SIOC[t] }, + }.each do |prop, entails| + context prop.pname do + describe RDF::Vocabulary::Term do + specify { + expect(prop.entail(:subProperty).map(&:pname)).to include( + *entails.map(&:pname))} + specify { + expect {|b| prop.entail(:subProperty, &b) + }.to yield_control.at_least(entails.length)} + end + + # XXX all of these can probably be rolled up too + + stmt = RDF::Statement(RDF::URI('a'), prop, RDF::Literal(true)) + + describe RDF::Statement do + subject {stmt} + let(:results) {entails.map {|r| + RDF::Statement(RDF::URI("a"), r, RDF::Literal(true))}} + specify {expect(subject.entail(:subProperty)).to be_empty} + specify {expect(subject.entail(:subProperty)).to all(be_inferred)} + specify {expect {|b| + subject.entail(:subProperty, &b)}.not_to yield_control} + end + + describe RDF::Enumerable do + subject {[stmt].extend(RDF::Enumerable)} + specify { + expect(subject.entail(:subProperty)).to be_a(RDF::Enumerable)} + specify {expect(subject.entail(:subProperty).to_a).to be_empty} + specify { + expect {|b| subject.entail(:subProperty, &b)}.not_to yield_control} + end + + describe RDF::Mutable do + subject {RDF::Graph.new << stmt} + let(:results) {subject.dup} + specify {expect(subject.entail(:subProperty)).to be_a(RDF::Graph)} + specify {expect( + subject.entail(:subProperty)).to be_equivalent_graph(results)} + specify {expect(subject.entail!(:subProperty)).to equal subject} + end + end + end + end unless ENV['CI'] + describe :domain do { RDF::Vocab::FOAF.account => [RDF::Vocab::FOAF.Agent], diff --git a/spec/readme_spec.rb b/spec/readme_spec.rb index 1bd8a85..dddc447 100644 --- a/spec/readme_spec.rb +++ b/spec/readme_spec.rb @@ -25,7 +25,7 @@ it "Determine if a resource is compatible with the domains of a property" do RDF::Reasoner.apply(:rdfs) graph = RDF::Graph.load("etc/doap.ttl") - subj = RDF::URI("http://rubygems.org/gems/rdf-reasoner") + subj = RDF::URI("https://rubygems.org/gems/rdf-reasoner") expect(RDF::Vocab::DOAP.name).to be_domain_compatible(subj, graph) end @@ -40,7 +40,7 @@ RDF::Reasoner.apply(:owl) graph = RDF::Graph.load("etc/doap.ttl") graph.entail!(:equivalentClass) - expect(graph).to have_statement(RDF::Statement(RDF::URI("http://greggkellogg.net/foaf#me"), RDF.type, RDF::Vocab::DC.Agent)) + expect(graph).to have_statement(RDF::Statement(RDF::URI("https://greggkellogg.net/foaf#me"), RDF.type, RDF::Vocab::DC.Agent)) end it "Yield all entailed statements for all entailment methods" do diff --git a/spec/suite_helper.rb b/spec/suite_helper.rb index 40dc0a5..7429116 100644 --- a/spec/suite_helper.rb +++ b/spec/suite_helper.rb @@ -6,7 +6,7 @@ # For now, override RDF::Utils::File.open_file to look for the file locally before attempting to retrieve it module RDF::Util module File - REMOTE_PATH = "http://www.w3.org/2013/rdf-mt-tests/" + REMOTE_PATH = "https://www.w3.org/2013/rdf-mt-tests/" LOCAL_PATH = ::File.expand_path("../w3c-rdf/rdf-mt", __FILE__) + '/' class << self diff --git a/spec/suite_spec.rb b/spec/suite_spec.rb index ecca64f..7acca16 100644 --- a/spec/suite_spec.rb +++ b/spec/suite_spec.rb @@ -41,6 +41,8 @@ case result_graph when RDF::Enumerable # Add source triples to result to use equivalence + # FIXME: entailment test should be subgraph, considering BNode equivalence. + # Could be implemented in N3 as {G2 . {G1} => log:Success} or {G2} log:includes {G1} action_graph.each {|s| result_graph << s} expect(action_graph).to be_equivalent_graph(result_graph, t) when false