diff --git a/README.md b/README.md index 1996c10..9a787d7 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,14 @@ Domain and Range entailment include specific rules for schema.org vocabularies. * If `resource` is of type `schema:Role`, it is range acceptable if it has the same property with an acceptable value. * If `resource` is of type `rdf:List` (must be previously entailed), it is range acceptable if all members of the list are otherwise range acceptable on the same property. +### Limiting vocabularies used for reasoning + +As loading vocabularies can dominate processing time, the `RDF::Vocabulary.limit_vocabs` method can be used to set a specific set of vocabularies over which to reason. For example: + + RDF::Vocabulary.limit_vocabs(:rdf, :rdf, :schema) + +will limit the vocabularies which are returned from `RDF::Vocabulary.each`, which is used for reasoning and other operations over vocabularies and terms. + ## Examples ### Determine super-classes of a class diff --git a/VERSION b/VERSION index a918a2a..ee6cdce 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.6.0 +0.6.1 diff --git a/etc/doap.ttl b/etc/doap.ttl index b3b6ace..e19c07a 100644 --- a/etc/doap.ttl +++ b/etc/doap.ttl @@ -1,3 +1,4 @@ +@base . @prefix rdf: . @prefix rdfs: . @prefix dc: . @@ -7,10 +8,10 @@ @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:license ; + doap:license ; doap:shortdesc "RDFS/OWL/Schema.org Reasoner for RDF.rb."@en ; doap:description """ Reasons over RDFS/OWL vocabularies to generate statements which are @@ -25,8 +26,8 @@ ; doap:category , ; - doap:download-page ; - doap:mailing-list ; + doap:download-page <> ; + doap:mailing-list ; doap:bug-database ; doap:blog ; doap:developer ; diff --git a/lib/rdf/reasoner.rb b/lib/rdf/reasoner.rb index bfac0d8..1c9c660 100644 --- a/lib/rdf/reasoner.rb +++ b/lib/rdf/reasoner.rb @@ -6,7 +6,7 @@ module RDF ## # RDFS/OWL reasonsing for RDF.rb. # - # @see http://www.w3.org/TR/2013/REC-sparql11-entailment-20130321/ + # @see https://www.w3.org/TR/2013/REC-sparql11-entailment-20130321/ # @author [Gregg Kellogg](https://greggkellogg.net/) module Reasoner require 'rdf/reasoner/format' @@ -15,7 +15,7 @@ module Reasoner autoload :Schema, 'rdf/reasoner/schema' autoload :VERSION, 'rdf/reasoner/version' - # See http://www.pelagodesign.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/ + # See https://www.pelagodesign.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/ # # ISO_8601 = %r(^ diff --git a/lib/rdf/reasoner/extensions.rb b/lib/rdf/reasoner/extensions.rb index 1641940..bccc680 100644 --- a/lib/rdf/reasoner/extensions.rb +++ b/lib/rdf/reasoner/extensions.rb @@ -34,9 +34,9 @@ def entail(method, &block) # @param [Hash{Symbol => Object}] options ({}) # @option options [Array] :types # Fully entailed types of resource, if not provided, they are queried - def domain_compatible?(resource, queryable, **options) + def domain_compatible?(resource, queryable, options = {}) %w(owl rdfs schema).map {|r| "domain_compatible_#{r}?".to_sym}.all? do |meth| - !self.respond_to?(meth) || self.send(meth, resource, queryable, **options) + !self.respond_to?(meth) || self.send(meth, resource, queryable, options) end end @@ -50,9 +50,9 @@ def domain_compatible?(resource, queryable, **options) # @param [Hash{Symbol => Object}] options ({}) # @option options [Array] :types # Fully entailed types of resource, if not provided, they are queried - def range_compatible?(resource, queryable, **options) + def range_compatible?(resource, queryable, options = {}) %w(owl rdfs schema).map {|r| "range_compatible_#{r}?".to_sym}.all? do |meth| - !self.respond_to?(meth) || self.send(meth, resource, queryable, **options) + !self.respond_to?(meth) || self.send(meth, resource, queryable, options) end end end @@ -89,7 +89,7 @@ def entail(method, &block) # @param [Hash{Symbol => Object}] options ({}) # @option options [Array] :types # Fully entailed types of resource, if not provided, they are queried - def domain_compatible?(resource, queryable, **options) + def domain_compatible?(resource, queryable, options = {}) %w(owl rdfs schema).map {|r| "domain_compatible_#{r}?".to_sym}.all? do |meth| !self.respond_to?(meth) || self.send(meth, resource, queryable, **options) end @@ -105,9 +105,9 @@ def domain_compatible?(resource, queryable, **options) # @param [Hash{Symbol => Object}] options ({}) # @option options [Array] :types # Fully entailed types of resource, if not provided, they are queried - def range_compatible?(resource, queryable, **options) + def range_compatible?(resource, queryable, options = {}) %w(owl rdfs schema).map {|r| "range_compatible_#{r}?".to_sym}.all? do |meth| - !self.respond_to?(meth) || self.send(meth, resource, queryable, **options) + !self.respond_to?(meth) || self.send(meth, resource, queryable, options) end end end diff --git a/lib/rdf/reasoner/rdfs.rb b/lib/rdf/reasoner/rdfs.rb index bfa8b69..fb5a703 100644 --- a/lib/rdf/reasoner/rdfs.rb +++ b/lib/rdf/reasoner/rdfs.rb @@ -122,7 +122,7 @@ def subClass ## # For a Term: yield or return inferred subPropertyOf 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: yield or return inferred statements having a subPropertyOf relationship to predicate of this statement + # For a Statement: yield or return inferred statements having a subPropertyOf relationship to predicate of this statements # @private def _entail_subPropertyOf case self @@ -249,7 +249,7 @@ def _entail_range # @param [Hash{Symbol => Object}] options ({}) # @option options [Array] :types # Fully entailed types of resource, if not provided, they are queried - def domain_compatible_rdfs?(resource, queryable, **options) + def domain_compatible_rdfs?(resource, queryable, options = {}) raise RDF::Reasoner::Error, "#{self} can't get domains" unless property? domains = Array(self.domain).reject(&:node?) - [RDF::OWL.Thing, RDF::RDFS.Resource] @@ -276,7 +276,7 @@ def domain_compatible_rdfs?(resource, queryable, **options) # @param [Hash{Symbol => Object}] options ({}) # @option options [Array] :types # Fully entailed types of resource, if not provided, they are queried - def range_compatible_rdfs?(resource, queryable, **options) + def range_compatible_rdfs?(resource, queryable, options = {}) raise RDF::Reasoner::Error, "#{self} can't get ranges" unless property? if !(ranges = Array(self.range).reject(&:node?) - [RDF::OWL.Thing, RDF::RDFS.Resource]).empty? if resource.literal? diff --git a/lib/rdf/reasoner/schema.rb b/lib/rdf/reasoner/schema.rb index da16039..a02b950 100644 --- a/lib/rdf/reasoner/schema.rb +++ b/lib/rdf/reasoner/schema.rb @@ -22,7 +22,7 @@ module Schema # @param [Hash{Symbol => Object}] options # @option options [Array] :types # Fully entailed types of resource, if not provided, they are queried - def domain_compatible_schema?(resource, queryable, **options) + def domain_compatible_schema?(resource, queryable, options = {}) raise RDF::Reasoner::Error, "#{self} can't get domains" unless property? domains = Array(self.domainIncludes) - [RDF::OWL.Thing] @@ -52,7 +52,7 @@ def domain_compatible_schema?(resource, queryable, **options) # @param [Hash{Symbol => Object}] options ({}) # @option options [Array] :types # Fully entailed types of resource, if not provided, they are queried - def range_compatible_schema?(resource, queryable, **options) + def range_compatible_schema?(resource, queryable, options = {}) raise RDF::Reasoner::Error, "#{self} can't get ranges" unless property? if !(ranges = Array(self.rangeIncludes) - [RDF::OWL.Thing]).empty? if resource.literal? diff --git a/rdf-reasoner.gemspec b/rdf-reasoner.gemspec index c958a91..b87def8 100755 --- a/rdf-reasoner.gemspec +++ b/rdf-reasoner.gemspec @@ -25,8 +25,8 @@ Gem::Specification.new do |gem| gem.required_ruby_version = '>= 2.4' gem.requirements = [] - gem.add_runtime_dependency 'rdf', '~> 3.1' - gem.add_runtime_dependency 'rdf-vocab', '~> 3.1' + gem.add_runtime_dependency 'rdf', '~> 3.1', '>= 3.1.2' + gem.add_runtime_dependency 'rdf-vocab', '~> 3.1', '>= 3.1.5' gem.add_runtime_dependency 'rdf-xsd', '~> 3.1' gem.add_development_dependency 'rdf-spec', '~> 3.1' diff --git a/script/reason b/script/reason index 61a2f66..6c263cb 100755 --- a/script/reason +++ b/script/reason @@ -2,7 +2,7 @@ require 'rubygems' $:.unshift(File.expand_path("../../lib", __FILE__)) require 'rdf/reasoner' -%w(linkeddata rdf/turtle rdf/vocab).each do |req| +%w(linkeddata rdf/turtle rdf/rdfa rdf/vocab).each do |req| begin require req rescue LoadError @@ -10,11 +10,35 @@ require 'rdf/reasoner' end require 'getoptlong' +require 'ruby-prof' -def run(reader, **options) - repo = RDF::Repository.new << reader +def run(reader, file_name:, **options) + if options[:profile] + repo = RDF::Repository.new << reader + + output_dir = File.expand_path("../../doc/profiles/#{File.basename file_name, '.*'}", __FILE__) + FileUtils.mkdir_p(output_dir) + profile = RubyProf::Profile.new + #profile.exclude_methods!(Array, :each, :map) + profile.exclude_method!(Hamster::Hash, :each) + profile.exclude_method!(Hamster::Trie, :each) + #profile.exclude_method!(Kernel, :require) + profile.exclude_method!(Object, :run) + profile.exclude_common_methods! + profile.start + run(repo, file_name: file_name, **options.merge(profile: false)) + result = profile.stop + + # Print a graph profile to text + printer = RubyProf::MultiPrinter.new(result) + printer.print(path: output_dir, profile: "profile") + puts "output saved in #{output_dir}" + return + end + + repo = reader.is_a?(RDF::Queryable) ? reader : RDF::Repository.new << reader stmt_cnt = repo.count - prefixes = reader.prefixes + prefixes = reader.respond_to?(:prefixes) ? reader.prefixes : {} start = Time.new if options[:entail] repo.entail! @@ -34,9 +58,13 @@ def run(reader, **options) messages.each {|m| options[:output].puts " #{m}"} end end + elsif !options[:output_format] + # No output + secs = Time.new - start + STDERR.puts "\nReade #{repo.count} statements in #{secs} seconds" unless options[:quiet] else writer_options = options[:parser_options].merge(prefixes: prefixes, standard_prefixes: true) - RDF::Writer.for(options[:output_format] || :ttl).new(options[:output], writer_options) do |w| + RDF::Writer.for(options[:output_format]).new(options[:output], writer_options) do |w| w << repo end end @@ -49,7 +77,7 @@ parser_options = {base: nil} options = { parser_options: parser_options, output: STDOUT, - output_format: :ttl, + output_format: nil, input_format: nil, } input = nil @@ -59,10 +87,12 @@ OPT_ARGS = [ ["--format", GetoptLong::REQUIRED_ARGUMENT,"Specify output format when converting to RDF"], ["--input-format", GetoptLong::REQUIRED_ARGUMENT,"Format of the input document, when converting from RDF."], ["--output", "-o", GetoptLong::REQUIRED_ARGUMENT,"Output to the specified file path"], + ["--profile", GetoptLong::NO_ARGUMENT, "Run profiler with output to doc/profiles/"], ["--quiet", GetoptLong::NO_ARGUMENT, "Supress most output other than progress indicators"], ["--uri", GetoptLong::REQUIRED_ARGUMENT,"URI to be used as the document base"], ["--validate", GetoptLong::NO_ARGUMENT, "Validate input graph with reasoner"], - ["--help", "-?", GetoptLong::NO_ARGUMENT, "This message"] + ['--vocabs', GetoptLong::REQUIRED_ARGUMENT,"Comma-separated list of vocabulary identifiers over which to limit reasoning"], + ["--help", "-?", GetoptLong::NO_ARGUMENT, "This message"], ] def usage STDERR.puts %{Usage: #{$0} [options] file ...} @@ -85,24 +115,28 @@ opts.each do |opt, arg| case opt when '--entail' then options[:entail] = true when '--format' then options[:output_format] = arg.to_sym - when '--input-format' then options[:input_format] = arg.to_sym + when '--input-format' then parser_options[:format] = arg.to_sym when '--output' then options[:output] = File.open(arg, "w") + when '--profile' then options[:profile] = true when '--quiet' then options[:quiet] = true when '--uri' then parser_options[:base] = arg when '--validate' then options[:validate] = true + when '--vocabs' then (options[:vocabs] ||= []).concat(arg.split(',').map(&:strip)) when '--help' then usage end end +RDF::Vocabulary.limit_vocabs(*options[:vocabs]) if options[:vocabs] + if ARGV.empty? s = input ? input : $stdin.read - RDF::Reader.for(options[:input_format] || :ntriples).new(input, **options) do |reader| + RDF::Reader.for(parser_options[:format] || :ntriples).new(input, file_name: 'stdin', **options) do |reader| run(reader, **options) end else ARGV.each do |file| - RDF::Reader.open(file, parser_options) do |reader| - run(reader, **options) + RDF::Reader.open(file, **parser_options) do |reader| + run(reader, file_name: file, **options) end end end diff --git a/spec/rdfs_spec.rb b/spec/rdfs_spec.rb index 9b3a8b2..381c625 100644 --- a/spec/rdfs_spec.rb +++ b/spec/rdfs_spec.rb @@ -100,7 +100,6 @@ RDF::Vocab::FOAF.aimChatID => [RDF::Vocab::FOAF.aimChatID, RDF::Vocab::FOAF.nick], RDF::Vocab::FOAF.name => [RDF::Vocab::FOAF.name, RDF::RDFS.label], RDF::Vocab::CC.license => [RDF::Vocab::CC.license, RDF::Vocab::DC.license], - RDF::Vocab::DC.date => [RDF::Vocab::DC.date, RDF::Vocab::DC11.date], }.each do |prop, entails| context prop.pname do describe RDF::Vocabulary::Term do @@ -140,10 +139,7 @@ # 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 + RDF::Vocab::DC.source => %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| diff --git a/spec/suite_helper.rb b/spec/suite_helper.rb index 226022d..98034c8 100644 --- a/spec/suite_helper.rb +++ b/spec/suite_helper.rb @@ -60,7 +60,7 @@ def self.open_file(filename_or_url, **options, &block) remote_document end else - original_open_file(filename_or_url, options, &block) + original_open_file(filename_or_url, **options, &block) end end end diff --git a/spec/suite_spec.rb b/spec/suite_spec.rb index 7acca16..9490f4c 100644 --- a/spec/suite_spec.rb +++ b/spec/suite_spec.rb @@ -81,6 +81,7 @@ def extract_vocab(graph, ndx) if vocab_stmt vocab_subject = vocab_stmt.subject base = if vocab_subject.fragment + vocab_subject = vocab_subject.dup vocab_subject.fragment = "" vocab_subject else