├── VERSION ├── lib ├── df.rb ├── rdf │ ├── .gitignore │ ├── util.rb │ ├── mixin │ │ ├── readable.rb │ │ ├── type_check.rb │ │ ├── indexable.rb │ │ ├── durable.rb │ │ ├── countable.rb │ │ ├── enumerator.rb │ │ ├── transactable.rb │ │ └── writable.rb │ ├── version.rb │ ├── model │ │ ├── resource.rb │ │ ├── literal │ │ │ ├── token.rb │ │ │ ├── boolean.rb │ │ │ ├── date.rb │ │ │ ├── integer.rb │ │ │ ├── decimal.rb │ │ │ ├── time.rb │ │ │ └── datetime.rb │ │ ├── dataset.rb │ │ ├── term.rb │ │ ├── value.rb │ │ └── node.rb │ ├── util │ │ ├── uuid.rb │ │ ├── aliasing.rb │ │ └── cache.rb │ ├── ntriples │ │ └── format.rb │ ├── ntriples.rb │ ├── vocab │ │ ├── rdfs.rb │ │ ├── writer.rb │ │ └── rdfv.rb │ ├── changeset.rb │ ├── query │ │ ├── variable.rb │ │ └── hash_pattern_normalizer.rb │ └── nquads.rb └── rdf.rb ├── AUTHORS ├── .gitignore ├── Guardfile ├── .yardopts ├── spec ├── version_spec.rb ├── mixin_mutable_spec.rb ├── mixin_durable_spec.rb ├── mixin_readable.rb ├── mixin_countable_spec.rb ├── mixin_indexable.rb ├── mixin_writable_spec.rb ├── model_resource_spec.rb ├── model_term_spec.rb ├── spec_helper.rb ├── repository_spec.rb ├── vocab_strict_spec.rb ├── query_hash_pattern_normalizer_spec.rb ├── mixin_queryable_spec.rb ├── util_aliasing_spec.rb ├── data │ ├── test.nq │ └── test.nt ├── transaction_spec.rb ├── mixin_enumerable_spec.rb ├── changeset_spec.rb ├── model_node_spec.rb ├── model_value_spec.rb ├── util_file_spec.rb ├── cli_spec.rb ├── format_spec.rb ├── query_variable_spec.rb └── util_logger_spec.rb ├── .travis.yml ├── examples ├── query_bench.rb └── rickroberts.rb ├── etc ├── .htaccess ├── Gemfile └── doap.ttl ├── CREDITS ├── .fasterer.yml ├── bin └── rdf ├── Gemfile ├── UNLICENSE ├── rdf.gemspec ├── CONTRIBUTING.md ├── Rakefile └── CHANGES.md /VERSION: -------------------------------------------------------------------------------- 1 | 2.0.1 2 | -------------------------------------------------------------------------------- /lib/df.rb: -------------------------------------------------------------------------------- 1 | require 'rdf' 2 | -------------------------------------------------------------------------------- /lib/rdf/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore any local symlinks to a development copy of RDF::Spec 2 | spec 3 | spec.rb 4 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | * Arto Bendiken 2 | * Ben Lavender 3 | * Gregg Kellogg 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .tmp 3 | .yardoc 4 | pkg 5 | tmp 6 | /*.gem 7 | /.rbx/ 8 | /doc/ 9 | Gemfile.lock 10 | .bundle/ 11 | *.sw? 12 | benchmark/ 13 | /.byebug_history 14 | /coverage/ 15 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | guard 'rspec' do 2 | watch(%r{^spec/.+_spec\.rb$}) 3 | watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}/" } 4 | watch('spec/spec_helper.rb') { "spec" } 5 | watch('lib/rdf.rb') { "spec" } 6 | end 7 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --title "RDF.rb: Linked Data for Ruby" 2 | --output-dir doc/yard 3 | --protected 4 | --no-private 5 | --hide-void-return 6 | --markup markdown 7 | --readme README.md 8 | - 9 | AUTHORS 10 | CREDITS 11 | UNLICENSE 12 | VERSION 13 | -------------------------------------------------------------------------------- /lib/rdf/util.rb: -------------------------------------------------------------------------------- 1 | module RDF; module Util 2 | autoload :Aliasing, 'rdf/util/aliasing' 3 | autoload :Cache, 'rdf/util/cache' 4 | autoload :File, 'rdf/util/file' 5 | autoload :Logger, 'rdf/util/logger' 6 | autoload :UUID, 'rdf/util/uuid' 7 | end; end 8 | -------------------------------------------------------------------------------- /spec/version_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), 'spec_helper') 2 | 3 | describe 'RDF::VERSION' do 4 | it "should match the VERSION file" do 5 | expect(RDF::VERSION.to_s).to eq File.read(File.join(File.dirname(__FILE__), '..', 'VERSION')).chomp 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/rdf/mixin/readable.rb: -------------------------------------------------------------------------------- 1 | module RDF 2 | ## 3 | module Readable 4 | extend RDF::Util::Aliasing::LateBound 5 | 6 | ## 7 | # Returns `true` if `self` is readable. 8 | # 9 | # @return [Boolean] 10 | # @see RDF::Writable#writable? 11 | def readable? 12 | true 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | bundler_args: --without debug 3 | script: "bundle exec rspec spec" 4 | env: 5 | - CI=true 6 | rvm: 7 | - 2.0 8 | - 2.1 9 | - 2.2.4 10 | - 2.3.1 11 | - jruby-9.0.4.0 12 | - rbx 13 | cache: bundler 14 | sudo: false 15 | addons: 16 | code_climate: 17 | repo_token: 5806cc8a21c03f4e2f9d3b9d98d5d9fe084b66243b1dbb27b467dbc795acdcac 18 | -------------------------------------------------------------------------------- /spec/mixin_mutable_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), 'spec_helper') 2 | require 'rdf/spec/mutable' 3 | 4 | describe RDF::Mutable do 5 | # @see lib/rdf/spec/mutable.rb in rdf-spec 6 | it_behaves_like 'an RDF::Mutable' do 7 | # The available reference implementations are `RDF::Repository` and 8 | # `RDF::Graph` 9 | let(:mutable) { RDF::Repository.new } 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /examples/query_bench.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'benchmark/ips' 4 | require 'rdf' 5 | require 'rdf/vocab' 6 | require 'rdf/ntriples' 7 | graph = RDF::Graph.load("etc/doap.nt") 8 | 9 | puts graph.query(predicate: RDF::Vocab::FOAF.name).is_a?(RDF::Queryable) 10 | 11 | Benchmark.ips do |x| 12 | x.config(:time => 10, :warmup => 5) 13 | x.report('query_pattern') { graph.query(predicate: RDF::Vocab::FOAF.name) {} } 14 | end 15 | -------------------------------------------------------------------------------- /spec/mixin_durable_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), 'spec_helper') 2 | require 'rdf/spec/durable' 3 | 4 | describe RDF::Durable do 5 | # The available reference implementations are `RDF::Repository` and 6 | # `RDF::Graph`, but a plain Ruby array will do fine as well 7 | # FIXME 8 | before { @load_durable = lambda { RDF::Repository.new } } 9 | 10 | # @see lib/rdf/spec/countable.rb in rdf-spec 11 | it_behaves_like 'an RDF::Durable' 12 | end 13 | -------------------------------------------------------------------------------- /spec/mixin_readable.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), 'spec_helper') 2 | require 'rdf/spec/readable' 3 | 4 | describe RDF::Readable do 5 | # @see lib/rdf/spec/readable.rb in rdf-spec 6 | it_behaves_like 'an RDF::Readable' do 7 | # The available reference implementations are `RDF::Repository` and 8 | # `RDF::Graph`, but a plain Ruby array will do fine as well: 9 | let(:readable) { double("Readable").extend(RDF::Readable) } 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/mixin_countable_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), 'spec_helper') 2 | require 'rdf/spec/countable' 3 | 4 | describe RDF::Countable do 5 | # @see lib/rdf/spec/countable.rb in rdf-spec 6 | it_behaves_like 'an RDF::Countable' do 7 | # The available reference implementations are `RDF::Repository` and 8 | # `RDF::Graph`, but a plain Ruby array will do fine as well: 9 | let(:countable) { RDF::Spec.quads.extend(described_class) } 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/mixin_indexable.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), 'spec_helper') 2 | require 'rdf/spec/indexable' 3 | 4 | describe RDF::Indexable do 5 | # @see lib/rdf/spec/indexable.rb in rdf-spec 6 | it_behaves_like 'an RDF::Indexable' do 7 | # The available reference implementations are `RDF::Repository` and 8 | # `RDF::Graph`, but a double will do fine as well: 9 | let(:indexable) do 10 | d = double("Indexable").extend(RDF::Indexable) 11 | allow(d).to receive(:index!) {d} 12 | d 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /etc/.htaccess: -------------------------------------------------------------------------------- 1 | # Turtle 2 | AddType text/turtle .ttl 3 | AddCharset UTF-8 .ttl 4 | 5 | # Notation3 6 | AddType text/n3 .n3 7 | AddCharset UTF-8 .n3 8 | 9 | # N-Triples 10 | AddType text/plain .nt 11 | AddCharset US-ASCII .nt 12 | 13 | # RDF/XML 14 | AddType application/rdf+xml .rdf 15 | AddCharset UTF-8 .rdf 16 | 17 | # RDF/JSON 18 | AddType application/json .json 19 | AddCharset UTF-8 .json 20 | 21 | # TriX 22 | AddType application/trix .xml 23 | AddCharset UTF-8 .xml 24 | 25 | # HTTP content negotiation 26 | Options +MultiViews +FollowSymlinks 27 | -------------------------------------------------------------------------------- /CREDITS: -------------------------------------------------------------------------------- 1 | * Călin Ardelean 2 | * Mark Borkum 3 | * Danny Gagne 4 | * Joey Geiger 5 | * Fumihiro Kato 6 | * Naoki Kawamukai 7 | * Tom Nixon 8 | * Hellekin O. Wolf 9 | * John Fieber 10 | * Keita Urashima 11 | * Pius Uzamere 12 | * Judson Lester 13 | * Peter Vandenabeele 14 | * Tom Johnson 15 | -------------------------------------------------------------------------------- /lib/rdf/version.rb: -------------------------------------------------------------------------------- 1 | module RDF 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 20 | -------------------------------------------------------------------------------- /lib/rdf/mixin/type_check.rb: -------------------------------------------------------------------------------- 1 | module RDF 2 | ## 3 | # An RDF type check mixin. 4 | # 5 | # This module implements #type_error, which will raise TypeError. 6 | # 7 | # @see RDF::Value 8 | # @see RDF::Literal 9 | # @see RDF::Literal 10 | module TypeCheck 11 | ## 12 | # Default implementation of type_error, which returns false. 13 | # Classes including RDF::TypeCheck will raise TypeError 14 | # instead. 15 | # 16 | # @raise [TypeError] 17 | def type_error(message) 18 | raise TypeError, message 19 | end 20 | end # TypeCheck 21 | end # RDF 22 | -------------------------------------------------------------------------------- /lib/rdf/mixin/indexable.rb: -------------------------------------------------------------------------------- 1 | module RDF 2 | ## 3 | # A mixin that can be used to mark RDF repository implementations as 4 | # indexable. 5 | # 6 | # @since 0.3.0 7 | module Indexable 8 | ## 9 | # Returns `true` if `self` is indexed at present. 10 | # 11 | # @abstract 12 | # @return [Boolean] 13 | def indexed? 14 | false 15 | end 16 | 17 | ## 18 | # Indexes `self`. 19 | # 20 | # @abstract 21 | # @return [self] 22 | def index! 23 | raise NotImplementedError, "#{self.class}#index!" 24 | end 25 | end # Indexable 26 | end # RDF 27 | -------------------------------------------------------------------------------- /examples/rickroberts.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'rdf' 4 | 5 | file = File.expand_path("../england.nt", __FILE__) 6 | triples = File.read(file) 7 | 8 | graph = RDF::Graph.new # takes similar amounts of time if this is a repository 9 | 10 | start = Time.now 11 | 12 | RDF::Reader.for(:ntriples).new(triples) do |reader| 13 | reader.each_statement do |statement| 14 | graph << statement 15 | end 16 | end 17 | 18 | #statements = [] 19 | #i = 0 20 | #RDF::Reader.for(:ntriples).open(file) {|r| r.each {|s| statements << s}} 21 | #statements.extend(RDF::Enumerable) 22 | finish = Time.now 23 | 24 | #statements.subjects.count 25 | puts "it took #{finish - start}s" 26 | -------------------------------------------------------------------------------- /spec/mixin_writable_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), 'spec_helper') 2 | require 'rdf/spec/writable' 3 | 4 | describe RDF::Writable do 5 | subject {RDF::Repository.new} 6 | 7 | # @see lib/rdf/spec/writable.rb in rdf-spec 8 | it_behaves_like 'an RDF::Writable' do 9 | # The available reference implementations are `RDF::Repository` and 10 | # `RDF::Graph`, but a plain Ruby array will do fine as well: 11 | let(:writable) { RDF::Repository.new } 12 | end 13 | 14 | describe "#freeze" do 15 | it "should make the object no longer writable" do 16 | subject.freeze 17 | 18 | expect(subject).not_to be_writable 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /.fasterer.yml: -------------------------------------------------------------------------------- 1 | speedups: 2 | parallel_assignment: false 3 | rescue_vs_respond_to: true 4 | module_eval: true 5 | shuffle_first_vs_sample: true 6 | for_loop_vs_each: true 7 | each_with_index_vs_while: false 8 | map_flatten_vs_flat_map: true 9 | reverse_each_vs_reverse_each: true 10 | select_first_vs_detect: true 11 | sort_vs_sort_by: true 12 | fetch_with_argument_vs_block: true 13 | keys_each_vs_each_key: true 14 | hash_merge_bang_vs_hash_brackets: true 15 | block_vs_symbol_to_proc: true 16 | proc_call_vs_yield: true 17 | gsub_vs_tr: true 18 | select_last_vs_reverse_detect: true 19 | getter_vs_attr_reader: false 20 | setter_vs_attr_writer: false 21 | 22 | exclude_paths: 23 | - 'vendor/**/*.rb' 24 | - 'spec/**/*.rb' 25 | -------------------------------------------------------------------------------- /lib/rdf/model/resource.rb: -------------------------------------------------------------------------------- 1 | module RDF 2 | ## 3 | # An RDF resource. 4 | module Resource 5 | include RDF::Term 6 | 7 | ## 8 | # Instantiates an {RDF::Node} or an {RDF::URI}, depending on the given 9 | # argument. 10 | # 11 | # @return [RDF::Resource] 12 | def self.new(*args, &block) 13 | case arg = args.shift 14 | when Symbol then Node.intern(arg, *args, &block) 15 | when /^_:(.*)$/ then Node.new($1, *args, &block) 16 | else URI.new(arg, *args, &block) 17 | end 18 | end 19 | 20 | ## 21 | # Returns `true` to indicate that this value is a resource. 22 | # 23 | # @return [Boolean] 24 | def resource? 25 | true 26 | end 27 | end # Resource 28 | end # RDF 29 | -------------------------------------------------------------------------------- /lib/rdf/mixin/durable.rb: -------------------------------------------------------------------------------- 1 | module RDF 2 | ## 3 | module Durable 4 | extend RDF::Util::Aliasing::LateBound 5 | 6 | ## 7 | # Returns `true` if `self` is durable. 8 | # 9 | # @return [Boolean] 10 | # @see #nondurable? 11 | def durable? 12 | true 13 | end 14 | 15 | alias_method :persistent?, :durable? 16 | 17 | ## 18 | # Returns `true` if `self` is nondurable. 19 | # 20 | # @return [Boolean] 21 | # @see #durable? 22 | def nondurable? 23 | !durable? 24 | end 25 | 26 | alias_method :ephemeral?, :nondurable? 27 | alias_method :nonpersistent?, :nondurable? 28 | alias_method :transient?, :nondurable? 29 | alias_method :volatile?, :nondurable? 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/model_resource_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), 'spec_helper') 2 | 3 | describe RDF::Resource do 4 | subject {Proc.new { |*args| RDF::Resource.new(*args) }} 5 | 6 | it "should instantiate blank nodes" do 7 | resource = subject.call('_:foobar') 8 | expect(resource).to be_a_node 9 | expect(resource.id).to eq 'foobar' 10 | end 11 | 12 | it "should instantiate URIs" do 13 | resource = subject.call('http://rubygems.org/gems/rdf') 14 | expect(resource).to be_a_uri 15 | expect(resource.to_s).to eq 'http://rubygems.org/gems/rdf' 16 | end 17 | 18 | context "as method" do 19 | it "with positional arg" do 20 | expect(described_class).to receive(:new).with('http://rubygems.org/gems/rdf') 21 | subject.call('http://rubygems.org/gems/rdf') 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/model_term_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), 'spec_helper') 2 | 3 | describe RDF::Term do 4 | subject {"".extend(described_class)} 5 | let(:other) {"foo".extend(described_class)} 6 | 7 | it "should not be instantiable" do 8 | expect { described_class.new }.to raise_error(NoMethodError) 9 | end 10 | 11 | it "#==" do 12 | is_expected.to eq subject 13 | is_expected.not_to eq other 14 | end 15 | 16 | it "#eql?" do 17 | is_expected.to eql subject 18 | is_expected.not_to eql other 19 | end 20 | 21 | it "#term?" do 22 | is_expected.to be_term 23 | end 24 | 25 | it "#to_term" do 26 | expect(subject.to_term).to equal subject 27 | end 28 | 29 | it "#to_base" do 30 | expect(subject.to_base).to be_a(String) 31 | end 32 | 33 | it "#compatible?" do 34 | is_expected.not_to be_compatible(other) 35 | end 36 | 37 | end 38 | -------------------------------------------------------------------------------- /bin/rdf: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))) 3 | require 'rubygems' 4 | require 'rdf/cli' 5 | 6 | options = RDF::CLI.options do 7 | self.on('-v', '--verbose', 'Enable verbose output. May be given more than once.') do 8 | self.options[:logger].level = Logger::INFO 9 | end 10 | 11 | self.on('-V', '--version', 'Display the RDF.rb version and exit.') do 12 | puts RDF::VERSION; exit 13 | end 14 | 15 | ARGV.select {|a| RDF::CLI.commands.include?(a)}.each do |cmd| 16 | # Load command-specific options 17 | Array(RDF::CLI::COMMANDS[cmd.to_sym][:options]).each do |cli_opt| 18 | on_args = cli_opt.on || [] 19 | on_args << cli_opt.description if cli_opt.description 20 | self.on(*on_args) do |arg| 21 | self.options[cli_opt.symbol] = cli_opt.call(arg) 22 | end 23 | end 24 | end 25 | end 26 | 27 | abort options.banner if ARGV.empty? && !options.options[:evaluate] 28 | 29 | # Add option_parser to parsed options to enable help 30 | RDF::CLI.exec(ARGV, options.options.merge(option_parser: options)) 31 | -------------------------------------------------------------------------------- /lib/rdf/mixin/countable.rb: -------------------------------------------------------------------------------- 1 | module RDF 2 | ## 3 | # @since 0.2.0 4 | module Countable 5 | autoload :Enumerator, 'rdf/mixin/enumerator' 6 | extend RDF::Util::Aliasing::LateBound 7 | 8 | ## 9 | # Returns `true` if `self` contains no RDF statements. 10 | # 11 | # @return [Boolean] 12 | def empty? 13 | each {return false} 14 | true 15 | end 16 | 17 | ## 18 | # Returns the number of RDF statements in `self`. 19 | # 20 | # @return [Integer] 21 | def count 22 | count = 0 23 | each { count += 1 } 24 | count 25 | end 26 | alias_method :size, :count 27 | 28 | ## 29 | # @private 30 | # @param [Symbol, #to_sym] method 31 | # @return [Enumerator] 32 | # @see Object#enum_for 33 | def enum_for(method = :each, *args) 34 | # Ensure that enumerators support the `#empty?` and `#count` methods: 35 | this = self 36 | Countable::Enumerator.new do |yielder| 37 | this.send(method, *args) {|y| yielder << y} 38 | end 39 | end 40 | alias_method :to_enum, :enum_for 41 | end # Countable 42 | end # RDF 43 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | 5 | group :develop do 6 | gem 'rdf-isomorphic', github: "ruby-rdf/rdf-isomorphic", branch: "develop" 7 | gem "rdf-reasoner", github: "ruby-rdf/rdf-reasoner", branch: "develop" 8 | gem "rdf-spec", github: "ruby-rdf/rdf-spec", branch: "develop" 9 | gem "rdf-vocab", github: "ruby-rdf/rdf-vocab", branch: "develop" 10 | gem "rdf-xsd", github: "ruby-rdf/rdf-xsd", branch: "develop" 11 | 12 | gem 'rest-client-components' 13 | gem 'benchmark-ips' 14 | end 15 | 16 | group :debug do 17 | gem 'psych', platforms: [:mri, :rbx] 18 | gem "wirble" 19 | gem "redcarpet", platforms: :ruby 20 | gem "byebug", platforms: :mri 21 | gem 'rubinius-debugger', platform: :rbx 22 | gem 'guard-rspec' 23 | end 24 | 25 | group :test do 26 | gem "rake" 27 | gem "equivalent-xml" 28 | gem 'fasterer' 29 | gem 'simplecov', require: false, platform: :mri 30 | gem 'coveralls', require: false, platform: :mri 31 | gem "codeclimate-test-reporter", require: false 32 | end 33 | 34 | platforms :rbx do 35 | gem 'rubysl', '~> 2.0' 36 | gem 'rubinius', '~> 2.0' 37 | end 38 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "bundler/setup" 2 | begin 3 | require "codeclimate-test-reporter" 4 | require 'simplecov' 5 | require 'coveralls' 6 | CodeClimate::TestReporter.start 7 | SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([ 8 | SimpleCov::Formatter::HTMLFormatter, 9 | Coveralls::SimpleCov::Formatter 10 | ]) 11 | SimpleCov.start do 12 | add_group "Mixins", 'lib/rdf/mixin' 13 | add_group "Models", 'lib/rdf/model' 14 | add_group "Query", 'lib/rdf/query' 15 | add_filter "/spec/" 16 | end 17 | rescue LoadError => e 18 | STDERR.puts "Coverage Skipped: #{e.message}" 19 | end 20 | require 'rdf' 21 | require 'rdf/vocab' 22 | require 'rdf/spec' 23 | require 'rdf/spec/matchers' 24 | 25 | RSpec.configure do |config| 26 | config.include(RDF::Spec::Matchers) 27 | config.filter_run focus: true 28 | config.run_all_when_everything_filtered = true 29 | config.exclusion_filter = {ruby: lambda { |version| 30 | RUBY_VERSION.to_s !~ /^#{version}/ 31 | }} 32 | end 33 | 34 | def fixture_path(filename) 35 | File.join(File.dirname(__FILE__), 'data', filename) 36 | end 37 | 38 | Encoding.default_external = Encoding::UTF_8 39 | Encoding.default_internal = Encoding::UTF_8 40 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /lib/rdf/util/uuid.rb: -------------------------------------------------------------------------------- 1 | module RDF; module Util 2 | ## 3 | # Utilities for UUID handling. 4 | # 5 | # @see http://en.wikipedia.org/wiki/Universally_unique_identifier 6 | module UUID 7 | ## 8 | # Generates a UUID string. 9 | # 10 | # This will make use of either the [UUID][] gem or the [UUIDTools][] 11 | # gem, whichever of the two happens to be available. 12 | # 13 | # [UUID]: http://rubygems.org/gems/uuid 14 | # [UUIDTools]: http://rubygems.org/gems/uuidtools 15 | # 16 | # @param [Hash{Symbol => Object}] options 17 | # any options to pass through to the underlying UUID library 18 | # @return [String] a UUID string 19 | # @raise [LoadError] if no UUID library is available 20 | # @see http://rubygems.org/gems/uuid 21 | # @see http://rubygems.org/gems/uuidtools 22 | def self.generate(options = {}) 23 | begin 24 | require 'uuid' 25 | ::UUID.generate(options[:format] || :default) 26 | rescue LoadError => e 27 | begin 28 | require 'uuidtools' 29 | ::UUIDTools::UUID.random_create.hexdigest 30 | rescue LoadError => e 31 | raise LoadError.new("no such file to load -- uuid or uuidtools") 32 | end 33 | end 34 | end 35 | end # UUID 36 | end; end # RDF::Util 37 | -------------------------------------------------------------------------------- /lib/rdf/model/literal/token.rb: -------------------------------------------------------------------------------- 1 | module RDF; class Literal 2 | ## 3 | # A token literal. 4 | # 5 | # @see http://www.w3.org/TR/xmlschema11-2/#token 6 | # @since 0.2.3 7 | class Token < Literal 8 | DATATYPE = RDF::XSD.token 9 | GRAMMAR = /\A[^\x0D\x0A\x09]+\z/i.freeze # FIXME 10 | 11 | ## 12 | # @param [Symbol, #to_s] value 13 | # @option options [String] :lexical (nil) 14 | def initialize(value, options = {}) 15 | @datatype = RDF::URI(options[:datatype] || self.class.const_get(:DATATYPE)) 16 | @string = options[:lexical] if options.has_key?(:lexical) 17 | @string ||= value if value.is_a?(String) 18 | @object = value.is_a?(Symbol) ? value : value.to_s 19 | end 20 | 21 | ## 22 | # Converts this literal into its canonical lexical representation. 23 | # 24 | # @return [RDF::Literal] `self` 25 | # @see http://www.w3.org/TR/xmlschema11-2/#boolean 26 | def canonicalize! 27 | @string = @object.to_s if @object 28 | self 29 | end 30 | 31 | ## 32 | # Returns the value as a symbol. 33 | # 34 | # @return [Symbol] 35 | def to_sym 36 | @object.to_sym 37 | end 38 | 39 | ## 40 | # Returns the value as a string. 41 | # 42 | # @return [String] 43 | def to_s 44 | @string || @object.to_s 45 | end 46 | end # Token 47 | end; end # RDF::Literal 48 | -------------------------------------------------------------------------------- /spec/repository_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), 'spec_helper') 2 | require 'rdf/spec/repository' 3 | 4 | describe RDF::Repository do 5 | 6 | # @see lib/rdf/spec/repository.rb in rdf-spec 7 | it_behaves_like 'an RDF::Repository' do 8 | let(:repository) { RDF::Repository.new } 9 | end 10 | 11 | it { is_expected.not_to be_durable } 12 | 13 | it "maintains arbitrary options" do 14 | repository = RDF::Repository.new(foo: :bar) 15 | expect(repository.options).to have_key(:foo) 16 | expect(repository.options[:foo]).to eq :bar 17 | end 18 | 19 | context "A non-validatable repository" do 20 | # @see lib/rdf/spec/repository.rb in rdf-spec 21 | it_behaves_like 'an RDF::Repository' do 22 | let(:repository) { RDF::Repository.new(with_validity: false) } 23 | end 24 | end 25 | 26 | describe '#apply_changeset' do 27 | let(:changeset) { double('changeset', deletes: dels, inserts: ins) } 28 | 29 | let(:dels) { [] } 30 | let(:ins) { [] } 31 | 32 | it 'applies atomically' do 33 | subject << existing_statement = RDF::Statement(:s, RDF.type, :o) 34 | dels << existing_statement 35 | ins << RDF::Statement(nil, nil, nil) 36 | 37 | expect do 38 | begin; subject.apply_changeset(changeset) rescue ArgumentError; end; 39 | end.not_to change { subject.statements } 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/rdf/mixin/enumerator.rb: -------------------------------------------------------------------------------- 1 | module RDF 2 | ## 3 | # Enumerators for different mixins. These are defined in a separate module, so that they are bound when used, allowing other mixins inheriting behavior to be included. 4 | module Enumerable 5 | # Extends Enumerator with {Queryable} and {Enumerable}, which is used by {Enumerable#each_statement} and {Queryable#enum_for} 6 | class Enumerator < ::Enumerator 7 | include Queryable 8 | include Enumerable 9 | 10 | def method_missing(method, *args) 11 | self.to_a if method.to_sym == :to_ary 12 | end 13 | 14 | # Make sure returned arrays are also queryable 15 | def to_a 16 | return super.to_a.extend(RDF::Queryable, RDF::Enumerable) 17 | end 18 | end 19 | end 20 | 21 | module Countable 22 | # Extends Enumerator with {Countable}, which is used by {Countable#enum_for} 23 | class Enumerator < ::Enumerator 24 | include Countable 25 | end 26 | end 27 | 28 | module Queryable 29 | # Extends Enumerator with {Queryable} and {Enumerable}, which is used by {Enumerable#each_statement} and {Queryable#enum_for} 30 | class Enumerator < ::Enumerator 31 | include Queryable 32 | include Enumerable 33 | 34 | # Make sure returned arrays are also queryable 35 | def to_a 36 | return super.to_a.extend(RDF::Queryable, RDF::Enumerable) 37 | end 38 | alias_method :to_ary, :to_a 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/rdf/model/dataset.rb: -------------------------------------------------------------------------------- 1 | module RDF 2 | ## 3 | # An RDF Dataset 4 | # 5 | # Datasets are immutable by default. {RDF::Repository} provides an interface 6 | # for mutable Datasets. 7 | # 8 | # @see https://www.w3.org/TR/rdf11-concepts/#section-dataset 9 | # @see https://www.w3.org/TR/rdf11-datasets/ 10 | class Dataset 11 | include RDF::Countable 12 | include RDF::Enumerable 13 | include RDF::Durable 14 | include RDF::Queryable 15 | 16 | ISOLATION_LEVELS = [ :read_uncommitted, 17 | :read_committed, 18 | :repeatable_read, 19 | :snapshot, 20 | :serializable].freeze 21 | 22 | ## 23 | # Returns a developer-friendly representation of this object. 24 | # 25 | # @return [String] 26 | def inspect 27 | sprintf("#<%s:%#0x(%s)>", self.class.name, __id__, uri.to_s) 28 | end 29 | 30 | ## 31 | # Outputs a developer-friendly representation of this object to 32 | # `stderr`. 33 | # 34 | # @return [void] 35 | def inspect! 36 | each_statement { |statement| statement.inspect! } 37 | nil 38 | end 39 | 40 | ## 41 | # @return [Symbol] a representation of the isolation level for reads of this 42 | # Dataset. One of `:read_uncommitted`, `:read_committed`, `:repeatable_read`, 43 | # `:snapshot`, or `:serializable`. 44 | def isolation_level 45 | :read_committed 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /etc/Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gem 'rdf', git: 'git://github.com/ruby-rdf/rdf.git' 4 | gem 'rdf-do', git: 'git://github.com/ruby-rdf/rdf-do.git' 5 | gem 'rdf-isomorphic', git: 'git://github.com/ruby-rdf/rdf-isomorphic.git' 6 | gem 'rdf-json', git: 'git://github.com/ruby-rdf/rdf-json.git' 7 | gem 'rdf-mongo', git: 'git://github.com/ruby-rdf/rdf-mongo.git' 8 | gem 'rdf-n3', git: 'git://github.com/ruby-rdf/rdf-n3.git' 9 | gem 'rdf-n3', git: 'git://github.com/ruby-rdf/rdf-trig.git' 10 | gem 'rdf-n3', git: 'git://github.com/ruby-rdf/rdf-turtle.git' 11 | gem 'rdf-rdfa', git: 'git://github.com/ruby-rdf/rdf-rdfa.git' 12 | gem 'rdf-rdfxml', git: 'git://github.com/ruby-rdf/rdf-rdfxml.git' 13 | gem 'rdf-spec', git: 'git://github.com/ruby-rdf/rdf-spec.git' 14 | gem 'rdf-trix', git: 'git://github.com/ruby-rdf/rdf-trix.git' 15 | 16 | gem 'rdf-arq', git: 'git://github.com/bendiken/rdf-arq.git' 17 | gem 'rdf-bert', git: 'git://github.com/bendiken/rdf-bert.git' 18 | gem 'rdf-cassandra', git: 'git://github.com/bendiken/rdf-cassandra.git' 19 | gem 'rdf-raptor', git: 'git://github.com/bendiken/rdf-raptor.git' 20 | gem 'rdf-rasqal', git: 'git://github.com/bendiken/rdf-rasqal.git' 21 | gem 'rdf-sesame', git: 'git://github.com/bendiken/rdf-sesame.git' 22 | 23 | gem 'rdf-talis', git: 'git://github.com/bhuga/rdf-talis.git' 24 | 25 | gem 'rdf-n3', git: 'git://github.com/gkellogg/json-ld.git' 26 | 27 | gem 'rdf-4store', git: 'git://github.com/fumi/rdf-4store.git' 28 | gem 'rdf-redstore', git: 'git://github.com/njh/rdf-redstore.git' 29 | -------------------------------------------------------------------------------- /spec/vocab_strict_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rdf' 2 | require 'rdf/vocabulary' 3 | 4 | describe RDF::StrictVocabulary do 5 | subject :test_vocab do 6 | Class.new(RDF::StrictVocabulary.create("http://example.com/test#")) do 7 | property :Class 8 | property :prop 9 | property :prop2, label: "Test property label", comment: "Test property comment" 10 | end 11 | end 12 | 13 | it "should not have Vocabulary::method_missing" do 14 | expect do 15 | test_vocab.a_missing_method 16 | end.to raise_error(NoMethodError) 17 | end 18 | 19 | it "should not have Vocabulary#method_missing" do 20 | expect do 21 | test_vocab.new.a_missing_method 22 | end.to raise_error(NoMethodError) 23 | end 24 | 25 | it "should respond to [] with properties that have been defined" do 26 | expect(test_vocab[:prop]).to be_a(RDF::URI) 27 | expect(test_vocab["prop2"]).to be_a(RDF::URI) 28 | end 29 | 30 | it "should list properties that have been defined" do 31 | expect([test_vocab.prop, test_vocab.Class, test_vocab.prop2] - test_vocab.properties).to be_empty 32 | end 33 | 34 | it "should not respond to [] with properties that have not been defined" do 35 | expect{ test_vocab["not_a_prop"] }.to raise_error(KeyError) 36 | expect{ test_vocab[:not_a_prop] }.to raise_error(KeyError) 37 | end 38 | 39 | it "should respond to methods for which a property has been defined explicitly" do 40 | expect(test_vocab.prop).to be_a(RDF::URI) 41 | end 42 | 43 | it "should respond to methods for which a property has been defined by a graph" do 44 | expect(test_vocab.prop2).to be_a(RDF::URI) 45 | end 46 | 47 | it "should respond to methods for which a class has been defined by a graph" do 48 | expect(test_vocab.Class).to be_a(RDF::URI) 49 | end 50 | 51 | it "should respond to label from base RDFS" do 52 | expect(test_vocab["prop2"].label).to eql "Test property label" 53 | end 54 | 55 | it "should respond to comment from base RDFS" do 56 | expect(test_vocab[:prop2].comment).to eql "Test property comment" 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/rdf/ntriples/format.rb: -------------------------------------------------------------------------------- 1 | module RDF::NTriples 2 | ## 3 | # N-Triples format specification. 4 | # 5 | # Note: Latest standards activities treat N-Triples as a subset 6 | # of Turtle. This includes application/n-triples mime type and a 7 | # new default encoding of utf-8. 8 | # 9 | # @example Obtaining an NTriples format class 10 | # RDF::Format.for(:ntriples) #=> RDF::NTriples::Format 11 | # RDF::Format.for("etc/doap.nt") 12 | # RDF::Format.for(file_name: "etc/doap.nt") 13 | # RDF::Format.for(file_extension: "nt") 14 | # RDF::Format.for(content_type: "application/n-triples") 15 | # 16 | # @see http://www.w3.org/TR/rdf-testcases/#ntriples 17 | # @see http://www.w3.org/TR/n-triples/ 18 | class Format < RDF::Format 19 | content_type 'application/n-triples', extension: :nt, alias: ['text/plain'] 20 | content_encoding 'utf-8' 21 | 22 | reader { RDF::NTriples::Reader } 23 | writer { RDF::NTriples::Writer } 24 | 25 | ## 26 | # Sample detection to see if it matches N-Triples 27 | # 28 | # Use a text sample to detect the format of an input file. Sub-classes implement 29 | # a matcher sufficient to detect probably format matches, including disambiguating 30 | # between other similar formats. 31 | # 32 | # @param [String] sample Beginning several bytes (about 1K) of input. 33 | # @return [Boolean] 34 | def self.detect(sample) 35 | !!sample.match(%r( 36 | (?:(?:<[^>]*>) | (?:_:\w+)) # Subject 37 | \s* 38 | (?:<[^>]*>) # Predicate 39 | \s* 40 | (?:(?:<[^>]*>) | (?:_:\w+) | (?:"[^"\n]*"(?:^^|@\S+)?)) # Object 41 | \s*\. 42 | )x) && !( 43 | sample.match(%r(@(base|prefix|keywords)|\{)) || # Not Turtle/N3/TriG 44 | sample.match(%r(<(html|rdf))i) # Not HTML or XML 45 | ) && !RDF::NQuads::Format.detect(sample) 46 | end 47 | 48 | # Human readable name for this format 49 | def self.name; "N-Triples"; end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec/query_hash_pattern_normalizer_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), 'spec_helper') 2 | 3 | describe RDF::Query::HashPatternNormalizer do 4 | context "new" do 5 | it "is instantiable" do 6 | expect { RDF::Query::HashPatternNormalizer.new }.not_to raise_error 7 | end 8 | end 9 | 10 | context ".normalize!" do 11 | subject {RDF::Query::HashPatternNormalizer.new} 12 | 13 | it "should raise an error if outer-most object is not a hash-pattern" do 14 | expect { subject.normalize!(42) }.to raise_error(ArgumentError) 15 | expect { subject.normalize!({}) }.to_not raise_error 16 | end 17 | 18 | it "should be idempotent" do 19 | hash_pattern = { 20 | foo: { 21 | bar: :baz 22 | } 23 | } 24 | 25 | expect(subject.normalize!(hash_pattern)).to eq hash_pattern 26 | expect(subject.normalize!(subject.normalize!(hash_pattern))).to eq hash_pattern 27 | end 28 | 29 | it "should normalize nested hash-patterns" do 30 | hash_pattern = { 31 | foo: { 32 | bar: { 33 | baz: :qux 34 | } 35 | } 36 | } 37 | 38 | expected_hash_pattern = { 39 | foo: { 40 | bar: :__1__ 41 | }, 42 | __1__: { 43 | baz: :qux 44 | } 45 | } 46 | 47 | expect(subject.normalize!(hash_pattern)).to eq expected_hash_pattern 48 | end 49 | 50 | it "should normalize nested array-patterns" do 51 | hash_pattern = { 52 | foo: { 53 | bar: [ 54 | { 55 | baz: :qux 56 | }, 57 | { 58 | quux: :corge 59 | } 60 | ] 61 | } 62 | } 63 | 64 | expected_hash_pattern = { 65 | foo: { 66 | bar: [:__1__, :__2__] 67 | }, 68 | __1__: { 69 | baz: :qux 70 | }, 71 | __2__: { 72 | quux: :corge 73 | } 74 | } 75 | 76 | expect(subject.normalize!(hash_pattern)).to eq expected_hash_pattern 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /rdf.gemspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby -rubygems 2 | # -*- encoding: utf-8 -*- 3 | 4 | Gem::Specification.new do |gem| 5 | gem.version = File.read('VERSION').chomp 6 | gem.date = File.mtime('VERSION').strftime('%Y-%m-%d') 7 | 8 | gem.name = 'rdf' 9 | gem.homepage = 'http://ruby-rdf.github.com/' 10 | gem.license = 'Unlicense' 11 | gem.summary = 'A Ruby library for working with Resource Description Framework (RDF) data.' 12 | gem.description = 'RDF.rb is a pure-Ruby library for working with Resource Description Framework (RDF) data.' 13 | gem.rubyforge_project = 'rdf' 14 | 15 | gem.authors = ['Arto Bendiken', 'Ben Lavender', 'Gregg Kellogg'] 16 | gem.email = 'public-rdf-ruby@w3.org' 17 | 18 | gem.platform = Gem::Platform::RUBY 19 | gem.files = %w(AUTHORS CREDITS README.md UNLICENSE VERSION bin/rdf etc/doap.nt) + Dir.glob('lib/**/*.rb') 20 | gem.bindir = %q(bin) 21 | gem.executables = %w(rdf) 22 | gem.default_executable = gem.executables.first 23 | gem.require_paths = %w(lib) 24 | gem.extensions = %w() 25 | gem.test_files = %w() 26 | gem.has_rdoc = false 27 | 28 | gem.required_ruby_version = '>= 2.0' 29 | gem.requirements = [] 30 | gem.add_runtime_dependency 'link_header', '~> 0.0', '>= 0.0.8' 31 | gem.add_runtime_dependency 'hamster', '~> 3.0' 32 | gem.add_development_dependency 'rdf-spec', '~> 2.0' 33 | gem.add_development_dependency 'rdf-vocab', '~> 2.0' 34 | gem.add_development_dependency 'rdf-xsd', '~> 2.0' 35 | gem.add_development_dependency 'rest-client', '~> 1.7' 36 | gem.add_development_dependency 'rspec', '~> 3.0' 37 | gem.add_development_dependency 'rspec-its', '~> 1.0' 38 | gem.add_development_dependency 'webmock', '~> 1.17' 39 | gem.add_development_dependency 'yard', '~> 0.8' 40 | gem.add_development_dependency 'faraday', '~> 0.9' 41 | gem.add_development_dependency 'faraday_middleware', '~> 0.9' 42 | 43 | gem.post_install_message = nil 44 | end 45 | -------------------------------------------------------------------------------- /lib/rdf/util/aliasing.rb: -------------------------------------------------------------------------------- 1 | module RDF; module Util 2 | module Aliasing 3 | ## 4 | # Helpers for late-bound instance method aliasing. 5 | # 6 | # Anything that extends this module will obtain an `alias_method` class 7 | # method that creates late-bound instance method aliases instead of the 8 | # default early-bound aliases created by Ruby's `Module#alias_method`. 9 | # 10 | # This is useful because RDF.rb mixins typically alias a number of 11 | # overridable methods. For example, `RDF::Enumerable#count` has the 12 | # aliases `#size` and `#length`. Normally if implementing classes were 13 | # to override the default method, the aliased methods would still be 14 | # bound to the mixin's original reference implementation rather than the 15 | # new overridden method. Mixing in this module into the implementing 16 | # class fixes this problem. 17 | # 18 | # @example Using late-bound aliasing in a module 19 | # module MyModule 20 | # extend RDF::Util::Aliasing::LateBound 21 | # end 22 | # 23 | # @example Using late-bound aliasing in a class 24 | # class MyClass 25 | # extend RDF::Util::Aliasing::LateBound 26 | # end 27 | # 28 | # @see http://en.wikipedia.org/wiki/Name_binding 29 | # @since 0.2.0 30 | module LateBound 31 | ## 32 | # Makes `new_name` a late-bound alias of the method `old_name`. 33 | # 34 | # @example Aliasing the `#count` method to `#size` and `#length` 35 | # alias_method :size, :count 36 | # alias_method :length, :count 37 | # 38 | # @param [Symbol, #to_sym] new_name 39 | # @param [Symbol, #to_sym] old_name 40 | # @return [void] 41 | # @see http://ruby-doc.org/core/classes/Module.html#M001653 42 | def alias_method(new_name, old_name) 43 | new_name, old_name = new_name.to_sym, old_name.to_sym 44 | 45 | self.__send__(:define_method, new_name) do |*args, &block| 46 | __send__(old_name, *args, &block) 47 | end 48 | 49 | return self 50 | end 51 | end # LateBound 52 | end # Aliasing 53 | end; end # RDF::Util 54 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | Community contributions are essential for keeping Ruby RDF great. We want to keep it as easy as possible to contribute changes that get things working in your environment. There are a few guidelines that we need contributors to follow so that we can have a chance of keeping on top of things. 4 | 5 | ## Development 6 | 7 | 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. 8 | 9 | * create or respond to an issue on the [Github Repository](http://github.com/ruby-rdf/rdf/issues) 10 | * Fork and clone the repo: 11 | `git clone git@github.com:your-username/rdf.git` 12 | * Install bundle: 13 | `bundle install` 14 | * Create tests in RSpec and make sure you achieve at least 90% code coverage for the feature your adding or behavior being modified. 15 | * Push to your fork and [submit a pull request][pr]. 16 | 17 | ## Do's and Dont's 18 | * Do your best to adhere to the existing coding conventions and idioms. 19 | * Don't use hard tabs, and don't leave trailing whitespace on any line. 20 | Before committing, run `git diff --check` to make sure of this. 21 | * Do document every method you add using [YARD][] annotations. Read the 22 | [tutorial][YARD-GS] or just look at the existing code for examples. 23 | * Don't touch the `.gemspec` or `VERSION` files. If you need to change them, 24 | do so on your private branch only. 25 | * Do feel free to add yourself to the `CREDITS` file and the 26 | corresponding list in the the `README`. Alphabetical order applies. 27 | * Don't touch the `AUTHORS` file. If your contributions are significant 28 | enough, be assured we will eventually add you in there. 29 | * Do note that in order for us to merge any non-trivial changes (as a rule 30 | of thumb, additions larger than about 15 lines of code), we need an 31 | explicit [public domain dedication][PDD] on record from you. 32 | 33 | [YARD]: http://yardoc.org/ 34 | [YARD-GS]: http://rubydoc.info/docs/yard/file/docs/GettingStarted.md 35 | [PDD]: http://lists.w3.org/Archives/Public/public-rdf-ruby/2010May/0013.html 36 | [pr]: https://github.com/ruby-rdf/rdf/compare/ 37 | -------------------------------------------------------------------------------- /spec/mixin_queryable_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), 'spec_helper') 2 | require 'rdf/spec/queryable' 3 | 4 | describe RDF::Queryable do 5 | # @see lib/rdf/spec/queryable.rb in rdf-spec 6 | it_behaves_like 'an RDF::Queryable' do 7 | # The available reference implementations are `RDF::Repository` and 8 | # `RDF::Graph`, but a subclass of Ruby Array implementing 9 | # `query_pattern` and `query_execute` should do as well 10 | # FIXME 11 | let(:queryable) { RDF::Repository.new } 12 | end 13 | 14 | context "Examples" do 15 | subject { RDF::Repository.new.insert(RDF::Spec.quads.extend(RDF::Enumerable)) } 16 | 17 | context "Querying for statements having a given predicate" do 18 | it "calls #query_pattern" do 19 | is_expected.to receive(:query_pattern) 20 | is_expected.not_to receive(:query_execute) 21 | subject.query([:s, :p, :o]) {} 22 | end 23 | it "with array" do 24 | expect(subject.query([nil, RDF::Vocab::DOAP.developer, nil]).to_a).not_to be_empty 25 | subject.query([nil, RDF::Vocab::DOAP.developer, nil]) {|s| expect(s).to be_a_statement} 26 | expect{|b| subject.query([nil, RDF::Vocab::DOAP.developer, nil], &b)}.to yield_control.at_least(1).times 27 | end 28 | it "with hash" do 29 | expect(subject.query(predicate: RDF::Vocab::DOAP.developer).to_a).not_to be_empty 30 | subject.query(predicate: RDF::Vocab::DOAP.developer) {|s| expect(s).to be_a_statement} 31 | expect{|b| subject.query(predicate: RDF::Vocab::DOAP.developer, &b)}.to yield_control.at_least(1).times 32 | end 33 | end 34 | 35 | context "Querying for solutions from a BGP" do 36 | let(:query) { query = RDF::Query.new {pattern [:s, :p, :o]} } 37 | it "calls #query_execute" do 38 | is_expected.to receive(:query_execute) 39 | is_expected.not_to receive(:query_pattern) 40 | subject.query(query) {} 41 | end 42 | 43 | it "returns solutions" do 44 | expect(subject.query(query).to_a).not_to be_empty 45 | subject.query(query) {|s| expect(s).to be_a RDF::Query::Solution} 46 | expect{|b| subject.query(query, &b)}.to yield_control.exactly(subject.count).times 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/util_aliasing_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), 'spec_helper') 2 | 3 | describe RDF::Util::Aliasing do 4 | before :all do 5 | module ::RDF::Util::Aliasing 6 | class Aliased 7 | def original(*args, &block) 8 | "original return value: #{args.join(',')} and #{block.call if block_given?}" 9 | end 10 | 11 | alias_method :rebound, :original # early-bound alias 12 | 13 | extend RDF::Util::Aliasing::LateBound # magic happens 14 | alias_method :aliased, :original # late-bound alias 15 | end unless defined?(Aliased) 16 | end 17 | end 18 | 19 | subject {RDF::Util::Aliasing::Aliased.new} 20 | 21 | context "when aliasing a method" do 22 | it "should create a new instance method with the given name" do 23 | expect(subject).to respond_to(:aliased) 24 | end 25 | end 26 | 27 | context "the aliased method" do 28 | it "should accept any arguments" do 29 | expect { subject.aliased }.not_to raise_error 30 | expect { subject.aliased(1) }.not_to raise_error 31 | expect { subject.aliased(1, 2) }.not_to raise_error 32 | end 33 | 34 | it "should accept a block" do 35 | expect { subject.aliased(1, 2) do 3 end }.not_to raise_error 36 | end 37 | 38 | it "should invoke the original method with the given arguments" do 39 | expect(subject.aliased).to eq subject.original 40 | expect(subject.aliased(1, 2)).to eq subject.original(1, 2) 41 | expect(subject.aliased(1, 2, 3)).to eq subject.original(1, 2, 3) 42 | end 43 | 44 | it "should invoke the original method with the given block" do 45 | expect(subject.aliased { 1 }).to eq subject.original { 1 } 46 | expect(subject.aliased(1) { 2 }).to eq subject.original(1) { 2 } 47 | expect(subject.aliased(1, 2) { 3 }).to eq subject.original(1, 2) { 3 } 48 | end 49 | 50 | it "should update if the original method is redefined" do 51 | module ::RDF::Util::Aliasing 52 | class Aliased 53 | def original(*args, &block) 54 | "redefined return value: #{args.join('::')} and #{block.call if block_given?}" 55 | end 56 | end 57 | end 58 | 59 | expect(subject.rebound(1, 2)).not_to eq subject.original(1, 2) 60 | expect(subject.aliased(1, 2)).to eq subject.original(1, 2) 61 | 62 | expect(subject.rebound(1, 2) { 3 }).not_to eq subject.original(1, 2) { 3 } 63 | expect(subject.aliased(1, 2) { 3 }).to eq subject.original(1, 2) { 3 } 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /spec/data/test.nq: -------------------------------------------------------------------------------- 1 | . 2 | "Alice" . 3 | _:bnode1 . 4 | _:bnode1 . 5 | _:bnode1 "Bob" . 6 | _:bnode1 . 7 | _:bnode1 . 8 | 9 | . 10 | "Bob" . # comment 11 | .#comment 12 | 13 | # Same but without embedded whitespace 14 | . 15 | "Alice". 16 | _:bnode1. 17 | _:b . 18 | _:b"Bob". 19 | _:b. 20 | _:b. 21 | _:b_:bnode2. 22 | _:b_:bnode2 _:bnode3. 23 | 24 | -------------------------------------------------------------------------------- /spec/transaction_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), 'spec_helper') 2 | require 'rdf/spec/transaction' 3 | 4 | describe RDF::Transaction do 5 | let(:repository) { RDF::Repository.new } 6 | 7 | # @see lib/rdf/spec/transaction.rb in rdf-spec 8 | it_behaves_like "an RDF::Transaction", RDF::Transaction 9 | 10 | describe 'base implementation' do 11 | subject { described_class.new(repository, mutable: true) } 12 | 13 | describe "#insert" do 14 | let(:st) { RDF::Statement(:s, RDF::URI('p'), 'o') } 15 | 16 | it 'adds to inserts' do 17 | expect { subject.insert(st) } 18 | .to change { subject.changes.inserts }.to contain_exactly(st) 19 | end 20 | 21 | it 'adds multiple to inserts' do 22 | sts = [st] << RDF::Statement(:x, RDF::URI('y'), 'z') 23 | 24 | expect { subject.insert(*sts) } 25 | .to change { subject.changes.inserts }.to contain_exactly(*sts) 26 | end 27 | 28 | it 'adds enumerable to inserts' do 29 | sts = [st] << RDF::Statement(:x, RDF::URI('y'), 'z') 30 | sts.extend(RDF::Enumerable) 31 | 32 | expect { subject.insert(sts) } 33 | .to change { subject.changes.inserts }.to contain_exactly(*sts) 34 | end 35 | end 36 | 37 | describe '#delete' do 38 | let(:st) { RDF::Statement(:s, RDF::URI('p'), 'o') } 39 | 40 | it 'adds to deletes' do 41 | expect { subject.delete(st) } 42 | .to change { subject.changes.deletes }.to contain_exactly(st) 43 | end 44 | 45 | it 'adds multiple to deletes' do 46 | sts = [st] << RDF::Statement(:x, RDF::URI('y'), 'z') 47 | 48 | expect { subject.delete(*sts) } 49 | .to change { subject.changes.deletes }.to contain_exactly(*sts) 50 | end 51 | 52 | it 'adds enumerable to deletes' do 53 | sts = [st] << RDF::Statement(:x, RDF::URI('y'), 'z') 54 | sts.extend(RDF::Enumerable) 55 | 56 | expect { subject.delete(sts) } 57 | .to change { subject.changes.deletes }.to contain_exactly(*sts) 58 | end 59 | end 60 | 61 | describe '#execute' do 62 | it 'calls `changes#apply` with repository' do 63 | expect(subject.changes).to receive(:apply).with(subject.repository) 64 | subject.execute 65 | end 66 | end 67 | end 68 | end 69 | 70 | describe RDF::Repository::Implementation::SerializedTransaction do 71 | let(:repository) { RDF::Repository.new } 72 | 73 | # @see lib/rdf/spec/transaction.rb in rdf-spec 74 | it_behaves_like "an RDF::Transaction", 75 | RDF::Repository::Implementation::SerializedTransaction 76 | end 77 | -------------------------------------------------------------------------------- /lib/rdf/model/term.rb: -------------------------------------------------------------------------------- 1 | module RDF 2 | ## 3 | # An RDF term. 4 | # 5 | # Terms can be used as subjects, predicates, objects, and graph names of 6 | # statements. 7 | # 8 | # @since 0.3.0 9 | module Term 10 | include RDF::Value 11 | include Comparable 12 | 13 | ## 14 | # Compares `self` to `other` for sorting purposes. 15 | # 16 | # Subclasses should override this to provide a more meaningful 17 | # implementation than the default which simply performs a string 18 | # comparison based on `#to_s`. 19 | # 20 | # @abstract 21 | # @param [Object] other 22 | # @return [Integer] `-1`, `0`, or `1` 23 | def <=>(other) 24 | self.to_s <=> other.to_s 25 | end 26 | 27 | ## 28 | # Compares `self` to `other` to implement RDFterm-equal. 29 | # 30 | # Subclasses should override this to provide a more meaningful 31 | # implementation than the default which simply performs a string 32 | # comparison based on `#to_s`. 33 | # 34 | # @abstract 35 | # @param [Object] other 36 | # @return [Integer] `-1`, `0`, or `1` 37 | # 38 | # @see http://www.w3.org/TR/rdf-sparql-query/#func-RDFterm-equal 39 | def ==(other) 40 | super 41 | end 42 | 43 | ## 44 | # Determins if `self` is the same term as `other`. 45 | # 46 | # Subclasses should override this to provide a more meaningful 47 | # implementation than the default which simply performs a string 48 | # comparison based on `#to_s`. 49 | # 50 | # @abstract 51 | # @param [Object] other 52 | # @return [Integer] `-1`, `0`, or `1` 53 | # 54 | # @see http://www.w3.org/TR/rdf-sparql-query/#func-sameTerm 55 | def eql?(other) 56 | super 57 | end 58 | 59 | ## 60 | # Returns `true` if `self` is a {RDF::Term}. 61 | # 62 | # @return [Boolean] 63 | def term? 64 | true 65 | end 66 | 67 | ## 68 | # Returns itself. 69 | # 70 | # @return [RDF::Value] 71 | def to_term 72 | self 73 | end 74 | 75 | ## 76 | # Returns the base representation of this term. 77 | # 78 | # @return [Sring] 79 | def to_base 80 | RDF::NTriples.serialize(self).freeze 81 | end 82 | 83 | ## 84 | # Term compatibility according to SPARQL 85 | # 86 | # @see http://www.w3.org/TR/sparql11-query/#func-arg-compatibility 87 | # @since 2.0 88 | def compatible?(other) 89 | false 90 | end 91 | 92 | protected 93 | ## 94 | # Escape a term using escapes. This should be implemented as appropriate for the given type of term. 95 | # 96 | # @param [String] string 97 | # @return [String] 98 | def escape(string) 99 | raise NotImplementedError, "#{self.class}#escape" 100 | end 101 | 102 | end # Term 103 | end # RDF 104 | -------------------------------------------------------------------------------- /lib/rdf/ntriples.rb: -------------------------------------------------------------------------------- 1 | module RDF 2 | ## 3 | # **`RDF::NTriples`** provides support for the N-Triples serialization 4 | # format. 5 | # 6 | # N-Triples is a line-based plain-text format for encoding an RDF graph. 7 | # It is a very restricted, explicit and well-defined subset of both 8 | # [Turtle](http://www.w3.org/TeamSubmission/turtle/) and 9 | # [Notation3](http://www.w3.org/TeamSubmission/n3/) (N3). 10 | # 11 | # The MIME content type for N-Triples files is `text/plain` and the 12 | # recommended file extension is `.nt`. 13 | # 14 | # An example of an RDF statement in N-Triples format: 15 | # 16 | # "RubyForge" . 17 | # 18 | # Installation 19 | # ------------ 20 | # 21 | # This is the only RDF serialization format that is directly supported by 22 | # RDF.rb. Support for other formats is available in the form of add-on 23 | # gems, e.g. 'rdf-xml' or 'rdf-json'. 24 | # 25 | # Documentation 26 | # ------------- 27 | # 28 | # * {RDF::NTriples::Format} 29 | # * {RDF::NTriples::Reader} 30 | # * {RDF::NTriples::Writer} 31 | # 32 | # @example Requiring the `RDF::NTriples` module explicitly 33 | # require 'rdf/ntriples' 34 | # 35 | # @see http://www.w3.org/TR/n-triples/ 36 | # @see http://en.wikipedia.org/wiki/N-Triples 37 | # 38 | # @author [Arto Bendiken](http://ar.to/) 39 | module NTriples 40 | require 'rdf/ntriples/format' 41 | autoload :Reader, 'rdf/ntriples/reader' 42 | autoload :Writer, 'rdf/ntriples/writer' 43 | 44 | ## 45 | # Reconstructs an RDF value from its serialized N-Triples 46 | # representation. 47 | # 48 | # @param [String] data 49 | # @return [RDF::Value] 50 | # @see RDF::NTriples::Reader.unserialize 51 | # @since 0.1.5 52 | def self.unserialize(data) 53 | Reader.unserialize(data) 54 | end 55 | 56 | ## 57 | # Returns the serialized N-Triples representation of the given RDF 58 | # value. 59 | # 60 | # @param [RDF::Value] value 61 | # @return [String] 62 | # @see RDF::NTriples::Writer.serialize 63 | # @since 0.1.5 64 | def self.serialize(value) 65 | Writer.for(:ntriples).serialize(value) 66 | end 67 | 68 | ## 69 | # @param [String] string 70 | # @return [String] 71 | # @see RDF::NTriples::Reader.unescape 72 | # @since 0.2.2 73 | def self.unescape(string) 74 | Reader.unescape(string) 75 | end 76 | 77 | ## 78 | # @param [String] string 79 | # @return [String] 80 | # @see RDF::NTriples::Writer.escape 81 | # @since 0.2.2 82 | def self.escape(string) 83 | Writer.escape(string) 84 | end 85 | end # NTriples 86 | 87 | ## 88 | # Extensions for `RDF::Value`. 89 | module Value 90 | ## 91 | # Returns the N-Triples representation of this value. 92 | # 93 | # This method is only available when the 'rdf/ntriples' serializer has 94 | # been explicitly required. 95 | # 96 | # @return [String] 97 | # @since 0.2.1 98 | def to_ntriples 99 | RDF::NTriples.serialize(self) 100 | end 101 | end # Value 102 | end # RDF 103 | -------------------------------------------------------------------------------- /spec/mixin_enumerable_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), 'spec_helper') 2 | require 'rdf/spec/enumerable' 3 | 4 | describe RDF::Enumerable do 5 | # @see lib/rdf/spec/enumerable.rb in rdf-spec 6 | it_behaves_like 'an RDF::Enumerable' do 7 | # The available reference implementations are `RDF::Repository` and 8 | # `RDF::Graph`, but a plain Ruby array will do fine as well: 9 | let(:enumerable) { RDF::Spec.quads.extend(described_class) } 10 | end 11 | 12 | context "Examples" do 13 | subject { RDF::Spec.quads.extend(described_class) } 14 | 15 | context "Checking whether any statements exist" do 16 | subject {[].extend(RDF::Enumerable)} 17 | it {is_expected.to be_empty} 18 | end 19 | 20 | context "Checking how many statements exist" do 21 | its(:count) {is_expected.to eq subject.size} 22 | end 23 | 24 | context "Checking whether a specific statement exists" do 25 | let(:statement) {subject.detect {|s| s.to_a.none?(&:node?)}} 26 | it {is_expected.to have_statement(statement)} 27 | it {is_expected.to have_triple(statement.to_a)} 28 | it do 29 | quad = statement.to_a 30 | quad[3] ||= nil 31 | is_expected.to have_quad(quad) 32 | end 33 | end 34 | 35 | context "Checking whether a specific value exists" do 36 | it {is_expected.to have_subject(RDF::URI("http://rubygems.org/gems/rdf"))} 37 | it {is_expected.to have_predicate(RDF.type)} 38 | it {is_expected.to have_object(RDF::Literal("A Ruby library for working with Resource Description Framework (RDF) data.", language: :en))} 39 | it {is_expected.to have_graph(RDF::URI("http://ar.to/#self"))} 40 | end 41 | 42 | it "Enumerating all statements" do 43 | expect { 44 | subject.each_statement {|statement| $stdout.puts statement.inspect} 45 | }.to write(:something) 46 | end 47 | 48 | it "Enumerating all statements in the form of triples" do 49 | expect { 50 | subject.each_triple do |subject, predicate, object| 51 | $stdout.puts [subject, predicate, object].inspect 52 | end 53 | }.to write(:something) 54 | end 55 | 56 | it "Enumerating all statements in the form of quads" do 57 | expect { 58 | subject.each_quad do |subject, predicate, object, graph_name| 59 | $stdout.puts [subject, predicate, object, graph_name].inspect 60 | end 61 | }.to write(:something) 62 | end 63 | 64 | context "Enumerating all terms" do 65 | %w(each_subject each_predicate each_object each_graph).each do |method| 66 | it "##{method}" do 67 | expect { 68 | subject.send(method.to_sym) {|term| $stdout.puts term.inspect} 69 | }.to write(:something) 70 | end 71 | end 72 | end 73 | 74 | context "Obtaining all statements" do 75 | %w(statements triples quads).each do |method| 76 | it "##{method}" do 77 | expect(subject.send(method.to_sym).to_a).not_to be_empty 78 | end 79 | end 80 | end 81 | 82 | context "Obtaining all unique values" do 83 | %w(subjects predicates objects).each do |method| 84 | it "##{method}" do 85 | expect(subject.send(method.to_sym).to_a).not_to be_empty 86 | end 87 | end 88 | end 89 | 90 | describe "#dump" do 91 | it "Serializing into N-Triples format" do 92 | expect(subject.dump(:ntriples)).not_to be_empty 93 | end 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), 'lib'))) 3 | require 'rubygems' 4 | require 'rdf' 5 | 6 | namespace :gem do 7 | desc "Build the rdf-#{File.read('VERSION').chomp}.gem file" 8 | task :build do 9 | sh "gem build rdf.gemspec && mv rdf-#{File.read('VERSION').chomp}.gem pkg/" 10 | end 11 | 12 | desc "Release the rdf-#{File.read('VERSION').chomp}.gem file" 13 | task :release do 14 | sh "gem push pkg/rdf-#{File.read('VERSION').chomp}.gem" 15 | end 16 | end 17 | 18 | desc 'Default: run specs.' 19 | task default: :spec 20 | task specs: :spec 21 | 22 | require 'rspec/core/rake_task' 23 | desc 'Run specifications' 24 | RSpec::Core::RakeTask.new do |spec| 25 | spec.rspec_opts = %w(--options spec/spec.opts) if File.exists?('spec/spec.opts') 26 | end 27 | 28 | desc "Run specifications for continuous integration" 29 | RSpec::Core::RakeTask.new("spec:ci") do |spec| 30 | spec.rspec_opts = %w(--options spec/spec.opts) if File.exists?('spec/spec.opts') 31 | end 32 | 33 | desc "Generate etc/doap.nt from etc/doap.ttl." 34 | task :doap do 35 | sh "bin/rdf serialize etc/doap.ttl --output etc/doap.nt" 36 | end 37 | 38 | require 'rdf/vocab/writer' 39 | 40 | desc "Generate Vocabularies" 41 | vocab_sources = { 42 | owl: {uri: "http://www.w3.org/2002/07/owl#"}, 43 | rdfs: {uri: "http://www.w3.org/2000/01/rdf-schema#"}, 44 | # Result requires manual editing 45 | #rdfv: {uri: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", 46 | # extra: { 47 | # about: {comment: "RDF/XML attribute declaring subject"}, 48 | # Alt: {comment: "RDF/XML Alt container"}, 49 | # Bag: {comment: "RDF/XML Bag container"}, 50 | # datatype: {comment: "RDF/XML literal datatype"}, 51 | # Description: {comment: "RDF/XML node element"}, 52 | # ID: {comment: "RDF/XML attribute creating a Reification"}, 53 | # li: {comment: "RDF/XML container membership list element"}, 54 | # nil: {comment: "The empty list, with no items in it. If the rest of a list is nil then the list has no more items in it."}, 55 | # nodeID: {comment: "RDF/XML Blank Node identifier"}, 56 | # object: {comment: "RDF/XML reification object"}, 57 | # parseType: {comment: "Parse type for RDF/XML, either Collection, Literal or Resource"}, 58 | # predicate: {comment: "RDF/XML reification predicate"}, 59 | # resource: {comment: "RDF/XML attribute declaring object"}, 60 | # Seq: {comment: "RDF/XML Seq container"}, 61 | # Statement: {comment: "RDF/XML reification Statement"}, 62 | # subject: {comment: "RDF/XML reification subject"}, 63 | # }}, 64 | xsd: {uri: "http://www.w3.org/2001/XMLSchema#", 65 | source: "etc/xsd.ttl", 66 | strict: false}, 67 | } 68 | 69 | task gen_vocabs: vocab_sources.keys.map {|v| "lib/rdf/vocab/#{v}.rb"} 70 | 71 | vocab_sources.each do |id, v| 72 | file "lib/rdf/vocab/#{id}.rb" => :do_build do 73 | puts "Generate lib/rdf/vocab/#{id}.rb" 74 | cmd = "bin/rdf serialize --uri '#{v[:uri]}' --output-format vocabulary" 75 | cmd += " --class-name #{id.to_s.upcase}" 76 | cmd += " -o lib/rdf/vocab/#{id}.rb_t" 77 | cmd += " --strict" if v.fetch(:strict, true) 78 | cmd += " '" + v.fetch(:source, v[:uri]) + "'" 79 | puts " #{cmd}" 80 | begin 81 | %x{#{cmd} && mv lib/rdf/vocab/#{id}.rb_t lib/rdf/vocab/#{id}.rb} 82 | rescue 83 | %x{rm -f lib/rdf/vocab/#{id}.rb_t} 84 | puts "Failed to load #{id}: #{$!.message}" 85 | end 86 | end 87 | end 88 | 89 | task :do_build -------------------------------------------------------------------------------- /lib/rdf/model/literal/boolean.rb: -------------------------------------------------------------------------------- 1 | module RDF; class Literal 2 | ## 3 | # A boolean literal. 4 | # 5 | # @see http://www.w3.org/TR/xmlschema11-2/#boolean 6 | # @since 0.2.1 7 | class Boolean < Literal 8 | DATATYPE = RDF::XSD.boolean 9 | GRAMMAR = /^(true|false|1|0)$/i.freeze 10 | TRUES = %w(true 1).freeze 11 | FALSES = %w(false 0).freeze 12 | 13 | ## 14 | # @param [Boolean] value 15 | # @option options [String] :lexical (nil) 16 | def initialize(value, options = {}) 17 | @datatype = RDF::URI(options[:datatype] || self.class.const_get(:DATATYPE)) 18 | @string = options[:lexical] if options.has_key?(:lexical) 19 | @string ||= value if value.is_a?(String) 20 | @object = case 21 | when true.equal?(value) then true 22 | when false.equal?(value) then false 23 | when TRUES.include?(value.to_s.downcase) then true 24 | when FALSES.include?(value.to_s.downcase) then false 25 | else value 26 | end 27 | end 28 | 29 | ## 30 | # Converts this literal into its canonical lexical representation. 31 | # 32 | # @return [RDF::Literal] `self` 33 | # @see http://www.w3.org/TR/xmlschema11-2/#boolean-canonical-representation 34 | def canonicalize! 35 | @string = (@object ? :true : :false).to_s 36 | self 37 | end 38 | 39 | ## 40 | # Compares this literal to `other` for sorting purposes. 41 | # 42 | # @param [Object] other 43 | # @return [Integer] `-1`, `0`, or `1` 44 | # @since 0.3.0 45 | def <=>(other) 46 | case other 47 | when TrueClass, FalseClass 48 | to_i <=> (other ? 1 : 0) 49 | when RDF::Literal::Boolean 50 | to_i <=> other.to_i 51 | else super 52 | end 53 | end 54 | 55 | ## 56 | # Returns `true` if this literal is equivalent to `other`. 57 | # 58 | # @param [Object] other 59 | # @return [Boolean] `true` or `false` 60 | # @since 0.3.0 61 | def ==(other) 62 | # If lexically invalid, use regular literal testing 63 | return super unless self.valid? 64 | 65 | other = Literal::Boolean.new(other) if other.class == TrueClass || other.class == FalseClass 66 | 67 | case other 68 | when Literal::Boolean 69 | return super unless other.valid? 70 | (cmp = (self <=> other)) ? cmp.zero? : false 71 | else 72 | super 73 | end 74 | end 75 | 76 | ## 77 | # Returns the value as a string. 78 | # 79 | # @return [String] 80 | def to_s 81 | @string || @object.to_s 82 | end 83 | 84 | ## 85 | # Returns the value as an integer. 86 | # 87 | # @return [Integer] `0` or `1` 88 | # @since 0.3.0 89 | def to_i 90 | @object ? 1 : 0 91 | end 92 | 93 | ## 94 | # Returns `true` if this value is `true`. 95 | # 96 | # @return [Boolean] 97 | def true? 98 | @object.equal?(true) 99 | end 100 | 101 | ## 102 | # Returns `true` if this value is `false`. 103 | # 104 | # @return [Boolean] 105 | def false? 106 | @object.equal?(false) 107 | end 108 | 109 | ## 110 | # Returns a developer-friendly representation of `self`. 111 | # 112 | # @return [String] 113 | def inspect 114 | case 115 | when self.equal?(RDF::Literal::TRUE) then 'RDF::Literal::TRUE' 116 | when self.equal?(RDF::Literal::FALSE) then 'RDF::Literal::FALSE' 117 | else super 118 | end 119 | end 120 | end # Boolean 121 | end; end # RDF::Literal 122 | -------------------------------------------------------------------------------- /lib/rdf/model/literal/date.rb: -------------------------------------------------------------------------------- 1 | module RDF; class Literal 2 | ## 3 | # A date literal. 4 | # 5 | # @see http://www.w3.org/TR/xmlschema11-2/#date 6 | # @since 0.2.1 7 | class Date < Literal 8 | DATATYPE = RDF::XSD.date 9 | GRAMMAR = %r(\A(-?\d{4}-\d{2}-\d{2})((?:[\+\-]\d{2}:\d{2})|UTC|GMT|Z)?\Z).freeze 10 | FORMAT = '%Y-%m-%d'.freeze 11 | 12 | ## 13 | # @param [Date] value 14 | # @option options [String] :lexical (nil) 15 | def initialize(value, options = {}) 16 | @datatype = RDF::URI(options[:datatype] || self.class.const_get(:DATATYPE)) 17 | @string = options[:lexical] if options.has_key?(:lexical) 18 | @string ||= value if value.is_a?(String) 19 | @object = case 20 | when value.is_a?(::Date) then value 21 | when value.respond_to?(:to_date) then value.to_date 22 | else ::Date.parse(value.to_s) 23 | end rescue nil 24 | end 25 | 26 | ## 27 | # Converts this literal into its canonical lexical representation. 28 | # 29 | # Note that the timezone is recoverable for xsd:date, where it is not for xsd:dateTime and xsd:time, which are both transformed relative to Z, if a timezone is provided. 30 | # 31 | # @return [RDF::Literal] `self` 32 | # @see http://www.w3.org/TR/xmlschema11-2/#date 33 | def canonicalize! 34 | @string = @object.strftime(FORMAT) + self.tz.to_s if self.valid? 35 | self 36 | end 37 | 38 | ## 39 | # Returns `true` if the value adheres to the defined grammar of the 40 | # datatype. 41 | # 42 | # Special case for date and dateTime, for which '0000' is not a valid year 43 | # 44 | # @return [Boolean] 45 | # @since 0.2.1 46 | def valid? 47 | super && object && value !~ %r(\A0000) 48 | end 49 | 50 | ## 51 | # Does the literal representation include a timezone? Note that this is only possible if initialized using a string, or `:lexical` option. 52 | # 53 | # @return [Boolean] 54 | # @since 1.1.6 55 | def has_timezone? 56 | md = self.to_s.match(GRAMMAR) 57 | md && !!md[2] 58 | end 59 | alias_method :has_tz?, :has_timezone? 60 | 61 | ## 62 | # Returns the value as a string. 63 | # 64 | # @return [String] 65 | def to_s 66 | @string || @object.strftime(FORMAT) 67 | end 68 | 69 | ## 70 | # Returns a human-readable value for the literal 71 | # 72 | # @return [String] 73 | # @since 1.1.6 74 | def humanize(lang = :en) 75 | d = object.strftime("%A, %d %B %Y") 76 | if has_timezone? 77 | d += if self.tz == 'Z' 78 | " UTC" 79 | else 80 | " #{self.tz}" 81 | end 82 | end 83 | d 84 | end 85 | 86 | ## 87 | # Returns the timezone part of arg as a simple literal. Returns the empty string if there is no timezone. 88 | # 89 | # @return [RDF::Literal] 90 | # @since 1.1.6 91 | def tz 92 | md = self.to_s.match(GRAMMAR) 93 | zone = md[2].to_s 94 | zone = "Z" if zone == "+00:00" 95 | RDF::Literal(zone) 96 | end 97 | 98 | ## 99 | # Equal compares as Date objects 100 | def ==(other) 101 | # If lexically invalid, use regular literal testing 102 | return super unless self.valid? 103 | 104 | case other 105 | when Literal::Date 106 | return super unless other.valid? 107 | self.object == other.object 108 | when Literal::Time, Literal::DateTime 109 | false 110 | else 111 | super 112 | end 113 | end 114 | end # Date 115 | end; end # RDF::Literal 116 | -------------------------------------------------------------------------------- /lib/rdf/mixin/transactable.rb: -------------------------------------------------------------------------------- 1 | module RDF 2 | ## 3 | # A transaction application mixin. 4 | # 5 | # Classes that include this module must provide a `#begin_transaction` method 6 | # returning an {RDF::Transaction}. 7 | # 8 | # @example running a read/write transaction with block syntax 9 | # repository = RDF::Repository.new # or other transactable 10 | # 11 | # repository.transaction(mutable: true) do |tx| 12 | # tx.insert [:node, RDF.type, RDF::OWL.Thing] 13 | # # ... 14 | # end 15 | # 16 | # @see RDF::Transaction 17 | # @since 2.0.0 18 | module Transactable 19 | ## 20 | # Executes the given block in a transaction. 21 | # 22 | # @example running a transaction 23 | # repository.transaction(mutable: true) do |tx| 24 | # tx.insert [RDF::URI("http://rubygems.org/gems/rdf"), RDF::RDFS.label, "RDF.rb"] 25 | # end 26 | # 27 | # Raising an error within the transaction block causes automatic rollback. 28 | # 29 | # @example manipulating a live transaction 30 | # tx = repository.transaction(mutable: true) 31 | # tx.insert [RDF::URI("http://rubygems.org/gems/rdf"), RDF::RDFS.label, "RDF.rb"] 32 | # tx.execute 33 | # 34 | # @overload transaction(mutable: false) 35 | # @param mutable [Boolean] 36 | # @return [RDF::Transaction] an open transaction; the client is 37 | # responsible for closing the transaction via #execute or #rollback 38 | # 39 | # @overload transaction(mutable: false, &block) 40 | # @param mutable [Boolean] 41 | # allows changes to the transaction, otherwise it is a read-only 42 | # snapshot of the underlying repository. 43 | # @yield [tx] 44 | # @yieldparam [RDF::Transaction] tx 45 | # @yieldreturn [void] ignored 46 | # @return [self] 47 | # 48 | # @see RDF::Transaction 49 | # @since 0.3.0 50 | def transaction(mutable: false, &block) 51 | tx = begin_transaction(mutable: mutable) 52 | return tx unless block_given? 53 | 54 | begin 55 | case block.arity 56 | when 1 then block.call(tx) 57 | else tx.instance_eval(&block) 58 | end 59 | rescue => error 60 | rollback_transaction(tx) 61 | raise error 62 | end 63 | commit_transaction(tx) 64 | self 65 | end 66 | alias_method :transact, :transaction 67 | 68 | protected 69 | 70 | ## 71 | # Begins a new transaction. 72 | # 73 | # Subclasses implementing transaction-capable storage adapters may wish 74 | # to override this method in order to begin a transaction against the 75 | # underlying storage. 76 | # 77 | # @param mutable [Boolean] Create a mutable or immutable transaction. 78 | # @param graph_name [Boolean] A default graph name for statements inserted 79 | # or deleted (default: nil) 80 | # @return [RDF::Transaction] 81 | def begin_transaction(mutable: false, graph_name: nil) 82 | raise NotImplementedError 83 | end 84 | 85 | ## 86 | # Rolls back the given transaction. 87 | # 88 | # @param [RDF::Transaction] tx 89 | # @return [void] ignored 90 | # @since 0.3.0 91 | def rollback_transaction(tx) 92 | tx.rollback 93 | end 94 | 95 | ## 96 | # Commits the given transaction. 97 | # 98 | # Subclasses implementing transaction-capable storage adapters may wish 99 | # to override this method in order to commit the given transaction to 100 | # the underlying storage. 101 | # 102 | # @param [RDF::Transaction] tx 103 | # @return [void] ignored 104 | # @since 0.3.0 105 | def commit_transaction(tx) 106 | tx.execute 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /lib/rdf/model/literal/integer.rb: -------------------------------------------------------------------------------- 1 | module RDF; class Literal 2 | ## 3 | # An integer literal. 4 | # 5 | # @example Arithmetic with integer literals 6 | # RDF::Literal(40) + 2 #=> RDF::Literal(42) 7 | # RDF::Literal(45) - 3 #=> RDF::Literal(42) 8 | # RDF::Literal(6) * 7 #=> RDF::Literal(42) 9 | # RDF::Literal(84) / 2 #=> RDF::Literal(42) 10 | # 11 | # @see http://www.w3.org/TR/xmlschema11-2/#integer 12 | # @see http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#integer 13 | # @since 0.2.1 14 | class Integer < Decimal 15 | DATATYPE = RDF::XSD.integer 16 | GRAMMAR = /^[\+\-]?\d+$/.freeze 17 | 18 | ## 19 | # @param [Integer, #to_i] value 20 | # @option options [String] :lexical (nil) 21 | def initialize(value, options = {}) 22 | @datatype = RDF::URI(options[:datatype] || self.class.const_get(:DATATYPE)) 23 | @string = options[:lexical] if options.has_key?(:lexical) 24 | @string ||= value if value.is_a?(String) 25 | @object = case 26 | when value.is_a?(::String) then Integer(value) rescue nil 27 | when value.is_a?(::Integer) then value 28 | when value.respond_to?(:to_i) then value.to_i 29 | else Integer(value.to_s) rescue nil 30 | end 31 | end 32 | 33 | ## 34 | # Converts this literal into its canonical lexical representation. 35 | # 36 | # @return [RDF::Literal] `self` 37 | # @see http://www.w3.org/TR/xmlschema11-2/#integer 38 | def canonicalize! 39 | @string = @object.to_s if @object 40 | self 41 | end 42 | 43 | ## 44 | # Returns the predecessor value of `self`. 45 | # 46 | # @return [RDF::Literal] 47 | # @since 0.2.3 48 | def pred 49 | RDF::Literal(to_i.pred) 50 | end 51 | 52 | ## 53 | # Returns the successor value of `self`. 54 | # 55 | # @return [RDF::Literal] 56 | # @since 0.2.3 57 | def succ 58 | RDF::Literal(to_i.succ) 59 | end 60 | alias_method :next, :succ 61 | 62 | ## 63 | # Returns `true` if the value is even. 64 | # 65 | # @return [Boolean] 66 | # @since 0.2.3 67 | def even? 68 | to_i.even? 69 | end 70 | 71 | ## 72 | # Returns `true` if the value is odd. 73 | # 74 | # @return [Boolean] 75 | # @since 0.2.3 76 | def odd? 77 | to_i.odd? 78 | end 79 | 80 | ## 81 | # Returns the absolute value of `self`. 82 | # 83 | # @return [RDF::Literal] 84 | # @since 0.2.3 85 | def abs 86 | (n = to_i) && n > 0 ? self : self.class.new(n.abs) 87 | end 88 | 89 | ## 90 | # Returns `self`. 91 | # 92 | # @return [RDF::Literal] 93 | def round 94 | self 95 | end 96 | 97 | ## 98 | # Returns `true` if the value is zero. 99 | # 100 | # @return [Boolean] 101 | # @since 0.2.3 102 | def zero? 103 | to_i.zero? 104 | end 105 | 106 | ## 107 | # Returns `self` if the value is not zero, `nil` otherwise. 108 | # 109 | # @return [Boolean] 110 | # @since 0.2.3 111 | def nonzero? 112 | to_i.nonzero? ? self : nil 113 | end 114 | 115 | ## 116 | # Returns the value as a string. 117 | # 118 | # @return [String] 119 | def to_s 120 | @string || @object.to_s 121 | end 122 | 123 | ## 124 | # Returns the value as an `OpenSSL::BN` instance. 125 | # 126 | # @return [OpenSSL::BN] 127 | # @see http://ruby-doc.org/stdlib/libdoc/openssl/rdoc/classes/OpenSSL/BN.html 128 | # @since 0.2.4 129 | def to_bn 130 | require 'openssl' unless defined?(OpenSSL::BN) 131 | OpenSSL::BN.new(to_s) 132 | end 133 | end # Integer 134 | end; end # RDF::Literal 135 | -------------------------------------------------------------------------------- /lib/rdf/model/literal/decimal.rb: -------------------------------------------------------------------------------- 1 | module RDF; class Literal 2 | ## 3 | # A decimal literal. 4 | # 5 | # @example Arithmetic with decimal literals 6 | # RDF::Literal(BigDecimal('1.0')) + 0.5 #=> RDF::Literal(BigDecimal('1.5')) 7 | # RDF::Literal(BigDecimal('1.0')) - 0.5 #=> RDF::Literal(BigDecimal('0.5')) 8 | # RDF::Literal(BigDecimal('1.0')) * 0.5 #=> RDF::Literal(BigDecimal('0.5')) 9 | # RDF::Literal(BigDecimal('1.0')) / 0.5 #=> RDF::Literal(BigDecimal('2.0')) 10 | # 11 | # @see http://www.w3.org/TR/xmlschema11-2/#decimal 12 | # @since 0.2.1 13 | class Decimal < Numeric 14 | DATATYPE = RDF::XSD.decimal 15 | GRAMMAR = /^[\+\-]?\d+(\.\d*)?$/.freeze 16 | 17 | ## 18 | # @param [BigDecimal] value 19 | # @option options [String] :lexical (nil) 20 | def initialize(value, options = {}) 21 | @datatype = RDF::URI(options[:datatype] || self.class.const_get(:DATATYPE)) 22 | @string = options[:lexical] if options.has_key?(:lexical) 23 | @string ||= value if value.is_a?(String) 24 | @object = case 25 | when value.is_a?(BigDecimal) then value 26 | else BigDecimal(value.to_s) 27 | end 28 | end 29 | 30 | ## 31 | # Converts this literal into its canonical lexical representation. 32 | # 33 | # @return [RDF::Literal] `self` 34 | # @see http://www.w3.org/TR/xmlschema11-2/#decimal 35 | def canonicalize! 36 | # Can't use simple %f transformation due to special requirements from 37 | # N3 tests in representation 38 | @string = begin 39 | i, f = @object.to_s('F').split('.') 40 | i.sub!(/^\+?0+(\d)$/, '\1') # remove the optional leading '+' sign and any extra leading zeroes 41 | f = f[0, 16] # truncate the fractional part after 15 decimal places 42 | f.sub!(/0*$/, '') # remove any trailing zeroes 43 | f = '0' if f.empty? # ...but there must be a digit to the right of the decimal point 44 | "#{i}.#{f}" 45 | end 46 | @object = BigDecimal(@string) unless @object.nil? 47 | self 48 | end 49 | 50 | ## 51 | # Returns the absolute value of `self`. 52 | # 53 | # @return [RDF::Literal] 54 | # @since 0.2.3 55 | def abs 56 | (d = to_d) && d > 0 ? self : RDF::Literal(d.abs) 57 | end 58 | 59 | ## 60 | # Returns the number with no fractional part that is closest to the argument. If there are two such numbers, then the one that is closest to positive infinity is returned. An error is raised if arg is not a numeric value. 61 | # 62 | # @return [RDF::Literal] 63 | def round 64 | self.class.new(to_d.round) 65 | end 66 | 67 | ## 68 | # Returns the smallest integer greater than or equal to `self`. 69 | # 70 | # @example 71 | # RDF::Literal(1).ceil #=> RDF::Literal(1) 72 | # 73 | # @return [RDF::Literal] 74 | def ceil 75 | self.class.new(to_d.ceil) 76 | end 77 | 78 | ## 79 | # Returns the largest integer less than or equal to `self`. 80 | # 81 | # @example 82 | # RDF::Literal(1).floor #=> RDF::Literal(1) 83 | # 84 | # @return [RDF::Literal] 85 | def floor 86 | self.class.new(to_d.floor) 87 | end 88 | 89 | ## 90 | # Returns `true` if the value is zero. 91 | # 92 | # @return [Boolean] 93 | # @since 0.2.3 94 | def zero? 95 | to_d.zero? 96 | end 97 | 98 | ## 99 | # Returns `self` if the value is not zero, `nil` otherwise. 100 | # 101 | # @return [Boolean] 102 | # @since 0.2.3 103 | def nonzero? 104 | to_d.nonzero? ? self : nil 105 | end 106 | 107 | ## 108 | # Returns the value as a string. 109 | # 110 | # @return [String] 111 | # @see BigDecimal#to_s 112 | def to_s 113 | @string || @object.to_s('F') 114 | end 115 | end # Decimal 116 | end; end # RDF::Literal 117 | -------------------------------------------------------------------------------- /spec/changeset_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), 'spec_helper') 2 | 3 | describe RDF::Changeset do 4 | describe "#initialize" do 5 | it "accepts inserts" do 6 | g = double("inserts") 7 | this = described_class.new(insert: g) 8 | expect(this.inserts).to eq g 9 | end 10 | 11 | it "accepts deletes" do 12 | g = double("deletes") 13 | this = described_class.new(delete: g) 14 | expect(this.deletes).to eq g 15 | end 16 | 17 | it "accepts inserts & deletes" do 18 | ins = double("inserts") 19 | del = double("deletes") 20 | 21 | this = described_class.new(delete: del, insert: ins) 22 | 23 | expect(this.inserts).to eq ins 24 | expect(this.deletes).to eq del 25 | end 26 | end 27 | 28 | its(:deletes) { is_expected.to be_a(RDF::Enumerable) } 29 | its(:inserts) { is_expected.to be_a(RDF::Enumerable) } 30 | 31 | it { is_expected.to be_mutable } 32 | it { is_expected.to_not be_readable } 33 | 34 | it "does not respond to #load" do 35 | expect {subject.load("http://example/")}.to raise_error(NoMethodError) 36 | end 37 | 38 | it "does not respond to #update" do 39 | expect { subject.update(RDF::Statement.new) }.to raise_error(NoMethodError) 40 | end 41 | 42 | it "does not respond to #clear" do 43 | expect { subject.clear }.to raise_error(NoMethodError) 44 | end 45 | 46 | describe "#apply" do 47 | let(:st) { RDF::Statement.new(RDF::URI("s"), RDF::URI("p"), RDF::URI("o")) } 48 | 49 | context 'on repository' do 50 | let(:repo) { RDF::Repository.new } 51 | 52 | it "deletes statements" do 53 | repo << st 54 | subject.delete(st) 55 | 56 | expect { subject.apply(repo) }.to change { repo.statements }.to be_empty 57 | end 58 | 59 | it "inserts statements" do 60 | subject.insert(st) 61 | expect { subject.apply(repo) } 62 | .to change { repo.statements }.to contain_exactly(st) 63 | end 64 | 65 | it "inserts & deletes statements" do 66 | repo << st 67 | new_statement = RDF::Statement(RDF::URI('x'), 68 | RDF::URI('y'), 69 | RDF::URI('z')) 70 | subject.delete(st) 71 | subject.insert(new_statement) 72 | 73 | expect { subject.apply(repo) } 74 | .to change { repo.statements }.to contain_exactly(new_statement) 75 | end 76 | end 77 | end 78 | 79 | describe '#empty?' do 80 | let(:s) {RDF::Statement.new(RDF::URI("s"), RDF::URI("p"), RDF::URI("o"))} 81 | 82 | it 'is empty when no deletes/inserts are present' do 83 | expect(subject).to be_empty 84 | end 85 | 86 | it 'is not empty when deletes are present' do 87 | subject.delete(s) 88 | expect(subject).not_to be_empty 89 | end 90 | 91 | it 'is not empty when inserts are present' do 92 | subject.insert(s) 93 | expect(subject).not_to be_empty 94 | end 95 | end 96 | 97 | describe '#delete' do 98 | let(:s) {RDF::Statement.new(RDF::URI("s"), RDF::URI("p"), RDF::URI("o"))} 99 | 100 | it 'adds statement to deletes' do 101 | expect { subject.delete(s) } 102 | .to change { subject.deletes }.to contain_exactly(s) 103 | end 104 | 105 | it 'adds multiple statements to #deletes' do 106 | statements = [].extend(RDF::Enumerable) 107 | statements << [s, RDF::Statement(:node, RDF.type, RDF::URI('x'))] 108 | 109 | expect { subject.delete(statements) } 110 | .to change { subject.deletes }.to contain_exactly(*statements) 111 | end 112 | end 113 | 114 | describe '#insert' do 115 | let(:s) {RDF::Statement.new(RDF::URI("s"), RDF::URI("p"), RDF::URI("o"))} 116 | 117 | it 'adds statement to inserts' do 118 | expect { subject.insert(s) } 119 | .to change { subject.inserts }.to contain_exactly(s) 120 | end 121 | 122 | it "adds multiple statements to #inserts" do 123 | statements = [].extend(RDF::Enumerable) 124 | statements << [s, RDF::Statement(:node, RDF.type, RDF::URI('x'))] 125 | 126 | expect { subject.insert(statements) } 127 | .to change { subject.inserts }.to contain_exactly(*statements) 128 | end 129 | end 130 | end 131 | -------------------------------------------------------------------------------- /spec/data/test.nt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright World Wide Web Consortium, (Massachusetts Institute of 3 | # Technology, Institut National de Recherche en Informatique et en 4 | # Automatique, Keio University). 5 | # 6 | # All Rights Reserved. 7 | # 8 | # Please see the full Copyright clause at 9 | # 10 | # 11 | # Test file with a variety of legal N-Triples 12 | # 13 | # Dave Beckett - http://purl.org/net/dajobe/ 14 | # 15 | # $Id: test.nt,v 1.7 2003/10/06 15:52:19 dbeckett2 Exp $ 16 | # 17 | ##################################################################### 18 | 19 | # comment lines 20 | # comment line after whitespace 21 | # empty blank line, then one with spaces and tabs 22 | 23 | 24 | . 25 | _:anon . 26 | _:anon . 27 | # spaces and tabs throughout: 28 | . 29 | 30 | # line ending with CR NL (ASCII 13, ASCII 10) 31 | . 32 | 33 | # 2 statement lines separated by single CR (ASCII 10) 34 | . . 35 | 36 | # All literal escapes 37 | "simple literal" . 38 | "backslash:\\" . 39 | "dquote:\"" . 40 | "newline:\n" . 41 | "return\r" . 42 | "tab:\t" . 43 | 44 | # Space is optional before final . 45 | . 46 | "x". 47 | _:anon. 48 | 49 | # \u and \U escapes 50 | # latin small letter e with acute symbol \u00E9 - 3 UTF-8 bytes #xC3 #A9 51 | "\u00E9" . 52 | # Euro symbol \u20ac - 3 UTF-8 bytes #xE2 #x82 #xAC 53 | "\u20AC" . 54 | # resource18 test removed 55 | # resource19 test removed 56 | # resource20 test removed 57 | 58 | # XML Literals as Datatyped Literals 59 | ""^^ . 60 | " "^^ . 61 | "x"^^ . 62 | "\""^^ . 63 | ""^^ . 64 | "a "^^ . 65 | "a c"^^ . 66 | "a\n\nc"^^ . 67 | "chat"^^ . 68 | # resource28 test removed 2003-08-03 69 | # resource29 test removed 2003-08-03 70 | 71 | # Plain literals with languages 72 | "chat"@fr . 73 | "chat"@en . 74 | 75 | # Typed Literals 76 | "abc"^^ . 77 | # resource33 test removed 2003-08-03 78 | 79 | # line ending with a comment 80 | . # Comment 81 | 82 | -------------------------------------------------------------------------------- /etc/doap.ttl: -------------------------------------------------------------------------------- 1 | @base . 2 | @prefix rdf: . 3 | @prefix rdfs: . 4 | @prefix dc: . 5 | @prefix foaf: . 6 | @prefix doap: . 7 | 8 | <> a doap:Project ; 9 | doap:name "RDF.rb" ; 10 | doap:homepage ; 11 | doap:license ; 12 | doap:shortdesc "A Ruby library for working with Resource Description Framework (RDF) data."@en ; 13 | doap:description "RDF.rb is a pure-Ruby library for working with Resource Description Framework (RDF) data."@en ; 14 | doap:created "2007-10-23" ; 15 | doap:platform "Ruby" ; 16 | doap:category , 17 | ; 18 | doap:implements , 19 | , 20 | ; 21 | doap:download-page ; 22 | doap:bug-database ; 23 | doap:blog , ; 24 | doap:vendor ; 25 | doap:developer , , ; 26 | doap:maintainer , , ; 27 | doap:documenter , , ; 28 | doap:helper [a foaf:Person ; 29 | foaf:name "Călin Ardelean" ; 30 | foaf:mbox_sha1sum "274bd18402fc773ffc0606996aa1fb90b603aa29"] ; 31 | doap:helper [a foaf:Person ; 32 | foaf:name "Danny Gagne" ; 33 | foaf:mbox_sha1sum "6de43e9cf7de53427fea9765706703e4d957c17b"] ; 34 | doap:helper [a foaf:Person ; 35 | foaf:name "Joey Geiger" ; 36 | foaf:mbox_sha1sum "f412d743150d7b27b8468d56e69ca147917ea6fc"] ; 37 | doap:helper [a foaf:Person ; 38 | foaf:name "Fumihiro Kato" ; 39 | foaf:mbox_sha1sum "d31fdd6af7a279a89bf09fdc9f7c44d9d08bb930"] ; 40 | doap:helper [a foaf:Person ; 41 | foaf:name "Naoki Kawamukai" ; 42 | foaf:mbox_sha1sum "5bdcd8e2af4f5952aaeeffbdd371c41525ec761d"] ; 43 | doap:helper [a foaf:Person ; 44 | foaf:name "Hellekin O. Wolf" ; 45 | foaf:mbox_sha1sum "c69f3255ff0639543cc5edfd8116eac8df16fab8"] ; 46 | doap:helper [a foaf:Person ; 47 | foaf:name "John Fieber" ; 48 | foaf:mbox_sha1sum "f7653fc1ac0e82ebb32f092389bd5fc728eaae12"] ; 49 | doap:helper [a foaf:Person ; 50 | foaf:name "Keita Urashima" ; 51 | foaf:mbox_sha1sum "2b4247b6fd5bb4a1383378f325784318680d5ff9"] ; 52 | doap:helper [a foaf:Person ; 53 | foaf:name "Pius Uzamere" ; 54 | foaf:mbox_sha1sum "bedbbf2451e5beb38d59687c0460032aff92cd3c"] ; 55 | foaf:maker ; 56 | dc:creator . 57 | 58 | a foaf:Person ; 59 | foaf:name "Arto Bendiken" ; 60 | foaf:mbox ; 61 | foaf:mbox_sha1sum "a033f652c84a4d73b8c26d318c2395699dd2bdfb", 62 | "d0737cceb55eb7d740578d2db1bc0727e3ed49ce" ; 63 | foaf:homepage ; 64 | foaf:made <> ; 65 | rdfs:isDefinedBy . 66 | 67 | a foaf:Person ; 68 | foaf:name "Ben Lavender" ; 69 | foaf:mbox ; 70 | foaf:mbox_sha1sum "dbf45f4ffbd27b67aa84f02a6a31c144727d10af" ; 71 | foaf:homepage ; 72 | rdfs:isDefinedBy . 73 | 74 | a foaf:Person ; 75 | foaf:name "Gregg Kellogg" ; 76 | foaf:mbox ; 77 | foaf:mbox_sha1sum "35bc44e6d0070e5ad50ccbe0d24403c96af2b9bd" ; 78 | foaf:homepage ; 79 | rdfs:isDefinedBy . -------------------------------------------------------------------------------- /spec/model_node_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), 'spec_helper') 2 | 3 | describe RDF::Node do 4 | let(:new) {Proc.new { |*args| RDF::Node.new(*args) }} 5 | 6 | it "should be instantiable" do 7 | expect { new.call }.not_to raise_error 8 | end 9 | 10 | describe ".intern" do 11 | before(:each) {RDF::URI.instance_variable_set(:@cache, nil)} 12 | it "caches Node instance" do 13 | RDF::Node.intern("a") 14 | expect(RDF::Node.instance_variable_get(:@cache)[:a]).to eq RDF::Node.intern("a") 15 | end 16 | 17 | it "freezes instance" do 18 | expect(RDF::Node.intern("a")).to be_frozen 19 | end 20 | end 21 | 22 | context "as method" do 23 | it "with no args" do 24 | expect(described_class).to receive(:new).with(no_args) 25 | RDF::Node() 26 | end 27 | 28 | it "with positional arg" do 29 | expect(described_class).to receive(:new).with("a") 30 | RDF::Node("a") 31 | end 32 | end 33 | 34 | describe "#==" do 35 | specify {expect(new.call("a")).to eq new.call("a")} 36 | specify {expect(new.call(:a)).to eq new.call("a")} 37 | specify {expect(new.call("a")).not_to eq new.call("b")} 38 | specify {expect(new.call("a")).not_to eq Object.new} 39 | 40 | it 'does not equal non-nodes' do 41 | other = new.call(:a) 42 | allow(other).to receive(:node?).and_return(false) 43 | expect(new.call(:a)).not_to eq other 44 | end 45 | 46 | it 'does not equal nodes with a different term hash' do 47 | other = new.call(:a) 48 | allow(other.to_term).to receive(:hash).and_return('fake-hash') 49 | expect(new.call(:a)).not_to eq other 50 | end 51 | end 52 | 53 | describe "#eql" do 54 | specify {expect(new.call("a")).not_to eql new.call("a")} 55 | specify {expect(RDF::Node.intern("a")).to eql RDF::Node.intern("a")} 56 | specify {expect(RDF::Node.intern(:a)).to eql RDF::Node.intern("a")} 57 | end 58 | 59 | subject {new.call("foo")} 60 | 61 | describe "#dup" do 62 | specify {expect(subject.dup).to eql subject} 63 | end 64 | 65 | its(:to_base) {is_expected.to eq "_:foo"} 66 | its(:to_unique_base) {is_expected.to match /^_:g/} 67 | 68 | describe "#to_unique_base" do 69 | it "uses the to_unique_base of original if duped" do 70 | dup = subject.dup 71 | expect(dup.to_unique_base).to eql subject.to_unique_base 72 | end 73 | end 74 | 75 | { 76 | "" => true, 77 | "foo" => true, 78 | foo: true, 79 | "1bc" => true, 80 | }.each do |l, valid| 81 | context "given '#{l}'" do 82 | if valid 83 | specify {expect(new.call(l)).to be_valid} 84 | specify {expect(new.call(l)).not_to be_invalid} 85 | describe "#validate!" do 86 | specify {expect {new.call(l).validate!}.not_to raise_error} 87 | end 88 | else 89 | specify {expect(new.call(l)).not_to be_valid} 90 | specify {expect(new.call(l)).to be_invalid} 91 | describe "#validate!" do 92 | specify {expect {new.call(l).validate!}.to raise_error(ArgumentError)} 93 | end 94 | end 95 | describe "#canonicalize!" do 96 | specify { 97 | n = new.call(l) 98 | expect(n.canonicalize!).to eq(n) 99 | } 100 | end 101 | end 102 | end 103 | 104 | describe "#make_unique!" do 105 | it "changes the id if assigned" do 106 | ub = subject.to_unique_base 107 | subject.make_unique! 108 | expect(subject.id).to eql ub[2..-1] 109 | end 110 | 111 | it "changes the id of a duplicated node based on the original node" do 112 | subject.freeze 113 | dup = subject.dup.make_unique! 114 | expect(dup.id).to eql subject.to_unique_base[2..-1] 115 | end 116 | end 117 | 118 | describe "#compatible?" do 119 | { 120 | %(_:abc) => { 121 | %("b") => false, 122 | %("b^^") => false, 123 | %("b"@ja) => false, 124 | %() => false, 125 | %(_:a) => false, 126 | }, 127 | }.each do |l1, props| 128 | props.each do |l2, res| 129 | it "#{l1} should #{'not ' unless res}be compatible with #{l2}" do 130 | if res 131 | expect(RDF::NTriples::Reader.parse_object l1).to be_compatible(RDF::NTriples::Reader.parse_object l2) 132 | else 133 | expect(RDF::NTriples::Reader.parse_object l1).not_to be_compatible(RDF::NTriples::Reader.parse_object l2) 134 | end 135 | end 136 | end 137 | end 138 | end 139 | end 140 | -------------------------------------------------------------------------------- /lib/rdf/model/literal/time.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | module RDF; class Literal 3 | ## 4 | # A time literal. 5 | # 6 | # The lexical representation for time is the left truncated lexical 7 | # representation for `xsd:dateTime`: "hh:mm:ss.sss" with an optional 8 | # following time zone indicator. 9 | # 10 | # @see http://www.w3.org/TR/xmlschema11-2/#time 11 | # @since 0.2.1 12 | class Time < Literal 13 | DATATYPE = RDF::XSD.time 14 | GRAMMAR = %r(\A(\d{2}:\d{2}:\d{2}(?:\.\d+)?)((?:[\+\-]\d{2}:\d{2})|UTC|GMT|Z)?\Z).freeze 15 | FORMAT = '%H:%M:%S%:z'.freeze 16 | 17 | ## 18 | # @param [Time] value 19 | # @option options [String] :lexical (nil) 20 | def initialize(value, options = {}) 21 | @datatype = RDF::URI(options[:datatype] || self.class.const_get(:DATATYPE)) 22 | @string = options[:lexical] if options.has_key?(:lexical) 23 | @string ||= value if value.is_a?(String) 24 | @object = case 25 | when value.is_a?(::DateTime) then value 26 | when value.respond_to?(:to_datetime) then value.to_datetime rescue ::DateTime.parse(value.to_s) 27 | else ::DateTime.parse(value.to_s) 28 | end rescue nil 29 | end 30 | 31 | ## 32 | # Converts this literal into its canonical lexical representation. 33 | # 34 | # §3.2.8.2 Canonical representation 35 | # 36 | # The canonical representation for time is defined by prohibiting 37 | # certain options from the Lexical representation (§3.2.8.1). 38 | # Specifically, either the time zone must be omitted or, if present, the 39 | # time zone must be Coordinated Universal Time (UTC) indicated by a "Z". 40 | # Additionally, the canonical representation for midnight is 00:00:00. 41 | # 42 | # @return [RDF::Literal] `self` 43 | # @see http://www.w3.org/TR/xmlschema11-2/#time 44 | def canonicalize! 45 | if self.valid? 46 | @string = if has_timezone? 47 | @object.new_offset.new_offset.strftime(FORMAT[0..-4] + 'Z') 48 | else 49 | @object.strftime(FORMAT[0..-4]) 50 | end 51 | end 52 | self 53 | end 54 | 55 | ## 56 | # Returns the timezone part of arg as a simple literal. Returns the empty string if there is no timezone. 57 | # 58 | # @return [RDF::Literal] 59 | # @see http://www.w3.org/TR/sparql11-query/#func-tz 60 | def tz 61 | zone = has_timezone? ? object.zone : "" 62 | zone = "Z" if zone == "+00:00" 63 | RDF::Literal(zone) 64 | end 65 | 66 | ## 67 | # Returns `true` if the value adheres to the defined grammar of the 68 | # datatype. 69 | # 70 | # Special case for date and dateTime, for which '0000' is not a valid year 71 | # 72 | # @return [Boolean] 73 | # @since 0.2.1 74 | def valid? 75 | super && !object.nil? 76 | end 77 | 78 | ## 79 | # Does the literal representation include a timezone? Note that this is only possible if initialized using a string, or `:lexical` option. 80 | # 81 | # @return [Boolean] 82 | # @since 1.1.6 83 | def has_timezone? 84 | md = self.to_s.match(GRAMMAR) 85 | md && !!md[2] 86 | end 87 | alias_method :has_tz?, :has_timezone? 88 | 89 | ## 90 | # Returns the value as a string. 91 | # Does not normalize timezone 92 | # 93 | # @return [String] 94 | def to_s 95 | @string || @object.strftime(FORMAT).sub("+00:00", 'Z') 96 | end 97 | 98 | ## 99 | # Returns a human-readable value for the literal 100 | # 101 | # @return [String] 102 | # @since 1.1.6 103 | def humanize(lang = :en) 104 | t = object.strftime("%r") 105 | if has_timezone? 106 | t += if self.tz == 'Z' 107 | " UTC" 108 | else 109 | " #{self.tz}" 110 | end 111 | end 112 | t 113 | end 114 | 115 | ## 116 | # Equal compares as Time objects 117 | def ==(other) 118 | # If lexically invalid, use regular literal testing 119 | return super unless self.valid? 120 | 121 | case other 122 | when Literal::Time 123 | return super unless other.valid? 124 | # Compare as strings, as time includes a date portion, and adjusting for UTC 125 | # can create a mismatch in the date portion. 126 | self.object.new_offset.strftime('%H%M%S') == other.object.new_offset.strftime('%H%M%S') 127 | when Literal::DateTime, Literal::Date 128 | false 129 | else 130 | super 131 | end 132 | end 133 | end # Time 134 | end; end # RDF::Literal 135 | -------------------------------------------------------------------------------- /lib/rdf/model/literal/datetime.rb: -------------------------------------------------------------------------------- 1 | module RDF; class Literal 2 | ## 3 | # A date/time literal. 4 | # 5 | # @see http://www.w3.org/TR/xmlschema11-2/#dateTime#boolean 6 | # @since 0.2.1 7 | class DateTime < Literal 8 | DATATYPE = RDF::XSD.dateTime 9 | GRAMMAR = %r(\A(-?\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?)((?:[\+\-]\d{2}:\d{2})|UTC|GMT|Z)?\Z).freeze 10 | FORMAT = '%Y-%m-%dT%H:%M:%S%:z'.freeze 11 | 12 | ## 13 | # @param [DateTime] value 14 | # @option options [String] :lexical (nil) 15 | def initialize(value, options = {}) 16 | @datatype = RDF::URI(options[:datatype] || self.class.const_get(:DATATYPE)) 17 | @string = options[:lexical] if options.has_key?(:lexical) 18 | @string ||= value if value.is_a?(String) 19 | @object = case 20 | when value.is_a?(::DateTime) then value 21 | when value.respond_to?(:to_datetime) then value.to_datetime 22 | else ::DateTime.parse(value.to_s) 23 | end rescue nil 24 | end 25 | 26 | ## 27 | # Converts this literal into its canonical lexical representation. 28 | # with date and time normalized to UTC. 29 | # 30 | # @return [RDF::Literal] `self` 31 | # @see http://www.w3.org/TR/xmlschema11-2/#dateTime 32 | def canonicalize! 33 | if self.valid? 34 | @string = if has_timezone? 35 | @object.new_offset.new_offset.strftime(FORMAT[0..-4] + 'Z') 36 | else 37 | @object.strftime(FORMAT[0..-4]) 38 | end 39 | end 40 | self 41 | end 42 | 43 | ## 44 | # Returns the timezone part of arg as a simple literal. Returns the empty string if there is no timezone. 45 | # 46 | # @return [RDF::Literal] 47 | # @see http://www.w3.org/TR/sparql11-query/#func-tz 48 | def tz 49 | zone = has_timezone? ? object.zone : "" 50 | zone = "Z" if zone == "+00:00" 51 | RDF::Literal(zone) 52 | end 53 | 54 | ## 55 | # Returns the timezone part of arg as an xsd:dayTimeDuration, or `nil` 56 | # if lexical form of literal does not include a timezone. 57 | # 58 | # @return [RDF::Literal] 59 | def timezone 60 | if tz == 'Z' 61 | RDF::Literal("PT0S", datatype: RDF::XSD.dayTimeDuration) 62 | elsif md = tz.to_s.match(/^([+-])?(\d+):(\d+)?$/) 63 | plus_minus, hour, min = md[1,3] 64 | plus_minus = nil unless plus_minus == "-" 65 | hour = hour.to_i 66 | min = min.to_i 67 | res = "#{plus_minus}PT#{hour}H#{"#{min}M" if min > 0}" 68 | RDF::Literal(res, datatype: RDF::XSD.dayTimeDuration) 69 | end 70 | end 71 | 72 | ## 73 | # Returns `true` if the value adheres to the defined grammar of the 74 | # datatype. 75 | # 76 | # Special case for date and dateTime, for which '0000' is not a valid year 77 | # 78 | # @return [Boolean] 79 | # @since 0.2.1 80 | def valid? 81 | super && object && value !~ %r(\A0000) 82 | end 83 | 84 | ## 85 | # Does the literal representation include a timezone? Note that this is only possible if initialized using a string, or `:lexical` option. 86 | # 87 | # @return [Boolean] 88 | # @since 1.1.6 89 | def has_timezone? 90 | md = self.to_s.match(GRAMMAR) 91 | md && !!md[2] 92 | end 93 | alias_method :has_tz?, :has_timezone? 94 | 95 | ## 96 | # Returns the `timezone` of the literal. If the 97 | ## 98 | # Returns the value as a string. 99 | # 100 | # @return [String] 101 | def to_s 102 | @string || @object.strftime(FORMAT).sub("+00:00", 'Z') 103 | end 104 | 105 | ## 106 | # Returns a human-readable value for the literal 107 | # 108 | # @return [String] 109 | # @since 1.1.6 110 | def humanize(lang = :en) 111 | d = object.strftime("%r on %A, %d %B %Y") 112 | if has_timezone? 113 | zone = if self.tz == 'Z' 114 | "UTC" 115 | else 116 | self.tz 117 | end 118 | d.sub!(" on ", " #{zone} on ") 119 | end 120 | d 121 | end 122 | 123 | ## 124 | # Equal compares as DateTime objects 125 | def ==(other) 126 | # If lexically invalid, use regular literal testing 127 | return super unless self.valid? 128 | 129 | case other 130 | when Literal::DateTime 131 | return super unless other.valid? 132 | self.object == other.object 133 | when Literal::Time, Literal::Date 134 | false 135 | else 136 | super 137 | end 138 | end 139 | end # DateTime 140 | end; end # RDF::Literal 141 | -------------------------------------------------------------------------------- /lib/rdf/util/cache.rb: -------------------------------------------------------------------------------- 1 | module RDF; module Util 2 | ## 3 | # A `Hash`-like cache that holds only weak references to the values it 4 | # caches, meaning that values contained in the cache can be garbage 5 | # collected. This allows the cache to dynamically adjust to changing 6 | # memory conditions, caching more objects when memory is plentiful, but 7 | # evicting most objects if memory pressure increases to the point of 8 | # scarcity. 9 | # 10 | # While this cache is something of an internal implementation detail of 11 | # RDF.rb, some external libraries do currently make use of it as well, 12 | # including [SPARQL](http://ruby-rdf/sparql/) and 13 | # [Spira](http://spira.rubyforge.org/). Do be sure to include any changes 14 | # here in the RDF.rb changelog. 15 | # 16 | # @see RDF::URI.intern 17 | # @see http://en.wikipedia.org/wiki/Weak_reference 18 | # @since 0.2.0 19 | class Cache 20 | ## 21 | # @private 22 | def self.new(*args) 23 | # JRuby doesn't support `ObjectSpace#_id2ref` unless the `-X+O` 24 | # startup option is given. In addition, ObjectSpaceCache is very slow 25 | # on Rubinius. On those platforms we'll default to using 26 | # the WeakRef-based cache: 27 | if RUBY_PLATFORM == 'java' || (defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx') 28 | klass = WeakRefCache 29 | else 30 | klass = ObjectSpaceCache 31 | end 32 | cache = klass.allocate 33 | cache.send(:initialize, *args) 34 | cache 35 | end 36 | 37 | ## 38 | # @param [Integer] capacity 39 | def initialize(capacity = -1) 40 | @capacity = capacity 41 | @cache ||= {} 42 | @index ||= {} 43 | end 44 | 45 | ## 46 | # @return [Integer] 47 | def size 48 | @cache.size 49 | end 50 | 51 | ## 52 | # @return [Boolean] 53 | def has_capacity? 54 | @capacity.equal?(-1) || @capacity > @cache.size 55 | end 56 | 57 | ## 58 | # This implementation relies on `ObjectSpace#_id2ref` and performs 59 | # optimally on Ruby >= 2.x; however, it does not work on JRuby 60 | # by default since much `ObjectSpace` functionality on that platform is 61 | # disabled unless the `-X+O` startup option is given. 62 | # 63 | # @see http://ruby-doc.org/core-2.2.2/ObjectSpace.html 64 | # @see http://ruby-doc.org/stdlib-2.2.0/libdoc/weakref/rdoc/WeakRef.html 65 | class ObjectSpaceCache < Cache 66 | ## 67 | # @param [Object] key 68 | # @return [Object] 69 | def [](key) 70 | if value_id = @cache[key] 71 | ObjectSpace._id2ref(value_id) rescue nil 72 | end 73 | end 74 | 75 | ## 76 | # @param [Object] key 77 | # @param [Object] value 78 | # @return [Object] 79 | def []=(key, value) 80 | if has_capacity? 81 | id = value.__id__ 82 | @cache[key] = id 83 | @index[id] = key 84 | ObjectSpace.define_finalizer(value, proc {|id| @cache.delete(@index.delete(id))}) 85 | end 86 | value 87 | end 88 | 89 | ## 90 | # Remove cache entry for key 91 | # 92 | # @param [Object] key 93 | # @return [Object] the previously referenced object 94 | def delete(key) 95 | id = @cache[key] 96 | @cache.delete(key) 97 | @index.delete(id) if id 98 | end 99 | end # ObjectSpaceCache 100 | 101 | ## 102 | # This implementation uses the `WeakRef` class from Ruby's standard 103 | # library, and provides adequate performance on JRuby and on Ruby 2.x. 104 | # 105 | # @see http://ruby-doc.org/stdlib-2.2.0/libdoc/weakref/rdoc/WeakRef.html 106 | class WeakRefCache < Cache 107 | ## 108 | # @param [Integer] capacity 109 | def initialize(capacity = -1) 110 | require 'weakref' unless defined?(::WeakRef) 111 | super 112 | end 113 | 114 | ## 115 | # @param [Object] key 116 | # @return [Object] 117 | def [](key) 118 | if (ref = @cache[key]) 119 | if ref.weakref_alive? 120 | value = ref.__getobj__ rescue nil 121 | else 122 | @cache.delete(key) 123 | nil 124 | end 125 | end 126 | end 127 | 128 | ## 129 | # @param [Object] key 130 | # @param [Object] value 131 | # @return [Object] 132 | def []=(key, value) 133 | if has_capacity? 134 | @cache[key] = WeakRef.new(value) 135 | end 136 | value 137 | end 138 | 139 | ## 140 | # Remove cache entry for key 141 | # 142 | # @param [Object] key 143 | # @return [Object] the previously referenced object 144 | def delete(key) 145 | ref = @cache.delete(key) 146 | ref.__getobj__ rescue nil 147 | end 148 | end # WeakRefCache 149 | end # Cache 150 | end; end # RDF::Util 151 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | Release 2.0.0 2 | ============= 3 | 4 | * A new class `RDF::Changeset` has been added. This is meant to replace any 5 | previous use of `RDF::Transaction`, which in fact used to in RDF.rb 1.x 6 | represent more of a buffered changeset than a genuine transaction scope. 7 | 8 | - Instead of `RDF::Transaction.execute`, use `RDF::Changeset.apply`. 9 | - Instead of `RDF::Transaction#execute`, use `RDF::Changeset#apply`. 10 | 11 | * The `RDF::Transaction` class has been substantially revamped, including 12 | some minor backwards-incompatible changes. These changes will mostly 13 | affect repository implementors, not so much general RDF.rb users. 14 | 15 | The changes reflect the expanded purpose of the class: instead of being a 16 | mere buffered changeset (for which, see `RDF::Changeset`), transactions 17 | are now intended to provide a proper ACID scope for repository queries and 18 | mutations. 19 | 20 | We always now also carefully distinguish between read-only and read/write 21 | transactions, in order to enable repository implementations to take out the 22 | appropriate locks for concurrency control. Note as well that transactions 23 | are now read-only by default; mutability must be explicitly requested on 24 | construction in order to obtain a read/write transaction. 25 | 26 | In case repository implementations should be unable to provide actual ACID 27 | guarantees for transactions, that must be clearly indicated in their 28 | documentation. Similarly, implementations should throw an exception when 29 | appropriate in case they don't provide write transaction support. 30 | 31 | - `RDF::Transaction#initialize` now takes the target repository as its 32 | first argument. Transactions are now always tied to a specific 33 | repository instance, instead of being free-floating objects as they used 34 | to be (for that, see `RDF::Changeset`). 35 | 36 | - `RDF::Transaction` now mixes in `RDF::Queryable` and `RDF::Enumerable`, 37 | enabling quad-pattern matches and BGP queries to execute in a proper 38 | transaction scope. 39 | 40 | - The `RDF::Transaction#context` accessor, and its aliases, have been 41 | removed. Transactions aren't necessarily scoped to a single graph only. 42 | 43 | - There is a new `RDF::Transaction#repository` accessor for retrieving the 44 | target repository object that the transaction operates upon. 45 | 46 | - There is a new `RDF::Transaction#buffered?` predicate for testing 47 | whether the changeset that constitutes a transaction is available for 48 | introspection. Particular repository implementations may support both 49 | options and permit the user the choice on transaction construction. 50 | 51 | - The `RDF::Transaction#inserts` and `#deletes` methods are deprecated. 52 | Instead, there is a new `RDF::Transaction#changes` accessor to retrieve 53 | an `RDF::Changeset` instance, which contains corresponding methods. 54 | For unbuffered transactions, `#changes` returns `nil`. 55 | 56 | * Enumerables vs. Enumerators 57 | 58 | - `RDF::Queryable#query` and `RDF::Query#execute` not return an enumerable, which may be an enumerator. Most internal uses return an Array now, which aides performance for small result sets, but potentially causes problems for large result sets. Implementations may still return an Enumerator, and Enumerators may be passed as arguments. 59 | - `RDF::Enumerable#statements`, `#quads`, `#triples`, `#subjects`, `#predicates`, `#objects`, and `#contexts` now return an array rather than an Enumerator. 60 | 61 | * The following vocabularies are deprecated and have been moved to the rdf-vocab gem. 62 | 63 | - `RDF::CC` - Creative Commons (CC) 64 | - `RDF::CERT` - W3 Authentication Certificate (CERT) 65 | - `RDF::DC` - Dublin Core (DC) 66 | - `RDF::DC11` - Dublin Core 1.1 (DC11) _deprecated_ 67 | - `RDF::DOAP` - Description of a Project (DOAP) 68 | - `RDF::EXIF` - Exchangeable Image File Format (EXIF) 69 | - `RDF::FOAF` - Friend of a Friend (FOAF) 70 | - `RDF::GEO` - WGS84 Geo Positioning (GEO) 71 | - `RDF::GR` - GoodRelations (GR) 72 | - `RDF::HT` - Hypertext Transfer Protocol (HT) 73 | - `RDF::ICAL` - RDF Calendar Workspace (ICAL) 74 | - `RDF::MA` - Media Resources (MA) 75 | - `RDF::MO` - Music Ontology (MO) 76 | - `RDF::OG` - Open Graph protocol (OG) 77 | - `RDF::PROV` - Provenance on the web (PROV) 78 | - `RDF::RSA` - W3 RSA Keys (RSA) 79 | - `RDF::RSS` - RDF Site Summary (RSS) 80 | - `RDF::SCHEMA` - Schema.org (SCHEMA) 81 | - `RDF::SIOC` - Semantically-Interlinked Online Communities (SIOC) 82 | - `RDF::SKOS` - Simple Knowledge Organization System (SKOS) 83 | - `RDF::SKOSXL` - SKOS eXtension for Labels (SKOSXL) 84 | - `RDF::V` - RDF data vocabulary (V) 85 | - `RDF::VCARD` - Ontology for vCards (VCARD) 86 | - `RDF::VMD` - Data-Vocabulary.org (VMD) 87 | - `RDF::VOID` - Vocabulary of Interlinked Datasets (VOID) 88 | - `RDF::VS` - SemWeb Vocab Status ontology (VS) 89 | - `RDF::WDRS` - Protocol for Web Description Resources (WDRS) 90 | - `RDF::WOT` - Web of Trust (WOT) 91 | - `RDF::XHTML` - Extensible HyperText Markup Language (XHTML) 92 | - `RDF::XHV` - XHTML Vocabulary (XHV) 93 | -------------------------------------------------------------------------------- /lib/rdf/vocab/rdfs.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | # frozen_string_literal: true 3 | # This file generated automatically using vocab-fetch from http://www.w3.org/2000/01/rdf-schema# 4 | require 'rdf' 5 | module RDF 6 | # @!parse 7 | # # Vocabulary for 8 | # class RDFS < RDF::StrictVocabulary 9 | # end 10 | class RDFS < RDF::StrictVocabulary("http://www.w3.org/2000/01/rdf-schema#") 11 | 12 | # Class definitions 13 | term :Class, 14 | comment: %(The class of classes.).freeze, 15 | label: "Class".freeze, 16 | :"rdfs:isDefinedBy" => %(rdfs:).freeze, 17 | subClassOf: "rdfs:Resource".freeze, 18 | type: "rdfs:Class".freeze 19 | term :Container, 20 | comment: %(The class of RDF containers.).freeze, 21 | label: "Container".freeze, 22 | :"rdfs:isDefinedBy" => %(rdfs:).freeze, 23 | subClassOf: "rdfs:Resource".freeze, 24 | type: "rdfs:Class".freeze 25 | term :ContainerMembershipProperty, 26 | comment: %(The class of container membership properties, rdf:_1, rdf:_2, ..., 27 | all of which are sub-properties of 'member'.).freeze, 28 | label: "ContainerMembershipProperty".freeze, 29 | :"rdfs:isDefinedBy" => %(rdfs:).freeze, 30 | subClassOf: "rdf:Property".freeze, 31 | type: "rdfs:Class".freeze 32 | term :Datatype, 33 | comment: %(The class of RDF datatypes.).freeze, 34 | label: "Datatype".freeze, 35 | :"rdfs:isDefinedBy" => %(rdfs:).freeze, 36 | subClassOf: "rdfs:Class".freeze, 37 | type: "rdfs:Class".freeze 38 | term :Literal, 39 | comment: %(The class of literal values, eg. textual strings and integers.).freeze, 40 | label: "Literal".freeze, 41 | :"rdfs:isDefinedBy" => %(rdfs:).freeze, 42 | subClassOf: "rdfs:Resource".freeze, 43 | type: "rdfs:Class".freeze 44 | term :Resource, 45 | comment: %(The class resource, everything.).freeze, 46 | label: "Resource".freeze, 47 | :"rdfs:isDefinedBy" => %(rdfs:).freeze, 48 | type: "rdfs:Class".freeze 49 | 50 | # Property definitions 51 | property :comment, 52 | comment: %(A description of the subject resource.).freeze, 53 | domain: "rdfs:Resource".freeze, 54 | label: "comment".freeze, 55 | range: "rdfs:Literal".freeze, 56 | :"rdfs:isDefinedBy" => %(rdfs:).freeze, 57 | type: "rdf:Property".freeze 58 | property :domain, 59 | comment: %(A domain of the subject property.).freeze, 60 | domain: "rdf:Property".freeze, 61 | label: "domain".freeze, 62 | range: "rdfs:Class".freeze, 63 | :"rdfs:isDefinedBy" => %(rdfs:).freeze, 64 | type: "rdf:Property".freeze 65 | property :isDefinedBy, 66 | comment: %(The defininition of the subject resource.).freeze, 67 | domain: "rdfs:Resource".freeze, 68 | label: "isDefinedBy".freeze, 69 | range: "rdfs:Resource".freeze, 70 | :"rdfs:isDefinedBy" => %(rdfs:).freeze, 71 | subPropertyOf: "rdfs:seeAlso".freeze, 72 | type: "rdf:Property".freeze 73 | property :label, 74 | comment: %(A human-readable name for the subject.).freeze, 75 | domain: "rdfs:Resource".freeze, 76 | label: "label".freeze, 77 | range: "rdfs:Literal".freeze, 78 | :"rdfs:isDefinedBy" => %(rdfs:).freeze, 79 | type: "rdf:Property".freeze 80 | property :member, 81 | comment: %(A member of the subject resource.).freeze, 82 | domain: "rdfs:Resource".freeze, 83 | label: "member".freeze, 84 | range: "rdfs:Resource".freeze, 85 | :"rdfs:isDefinedBy" => %(rdfs:).freeze, 86 | type: "rdf:Property".freeze 87 | property :range, 88 | comment: %(A range of the subject property.).freeze, 89 | domain: "rdf:Property".freeze, 90 | label: "range".freeze, 91 | range: "rdfs:Class".freeze, 92 | :"rdfs:isDefinedBy" => %(rdfs:).freeze, 93 | type: "rdf:Property".freeze 94 | property :seeAlso, 95 | comment: %(Further information about the subject resource.).freeze, 96 | domain: "rdfs:Resource".freeze, 97 | label: "seeAlso".freeze, 98 | range: "rdfs:Resource".freeze, 99 | :"rdfs:isDefinedBy" => %(rdfs:).freeze, 100 | type: "rdf:Property".freeze 101 | property :subClassOf, 102 | comment: %(The subject is a subclass of a class.).freeze, 103 | domain: "rdfs:Class".freeze, 104 | label: "subClassOf".freeze, 105 | range: "rdfs:Class".freeze, 106 | :"rdfs:isDefinedBy" => %(rdfs:).freeze, 107 | type: "rdf:Property".freeze 108 | property :subPropertyOf, 109 | comment: %(The subject is a subproperty of a property.).freeze, 110 | domain: "rdf:Property".freeze, 111 | label: "subPropertyOf".freeze, 112 | range: "rdf:Property".freeze, 113 | :"rdfs:isDefinedBy" => %(rdfs:).freeze, 114 | type: "rdf:Property".freeze 115 | 116 | # Extra definitions 117 | term :"", 118 | :"dc11:title" => %(The RDF Schema vocabulary \(RDFS\)).freeze, 119 | label: "".freeze, 120 | :"rdfs:seeAlso" => %(http://www.w3.org/2000/01/rdf-schema-more).freeze, 121 | type: "owl:Ontology".freeze 122 | end 123 | end 124 | -------------------------------------------------------------------------------- /lib/rdf/changeset.rb: -------------------------------------------------------------------------------- 1 | module RDF 2 | ## 3 | # An RDF changeset that can be applied to an {RDF::Mutable}. 4 | # 5 | # Changesets consist of a sequence of RDF statements to delete from and a 6 | # sequence of RDF statements to insert into a target dataset. 7 | # 8 | # @example Applying a Changeset with block syntax 9 | # graph = RDF::Graph.new 10 | # graph << [RDF::URI('s_del'), RDF::URI('p_del'), RDF::URI('o_del')] 11 | # 12 | # RDF::Changeset.apply(graph) do |c| 13 | # c.insert [RDF::URI('s1'), RDF::URI('p1'), RDF::URI('o1')] 14 | # c.insert [RDF::URI('s2'), RDF::URI('p2'), RDF::URI('o2')] 15 | # c.delete [RDF::URI('s_del'), RDF::URI('p_del'), RDF::URI('o_del')] 16 | # end 17 | # 18 | # @example Defining a changeset for later application to a Mutable 19 | # changes = RDF::Changeset.new do |c| 20 | # c.insert [RDF::URI('s1'), RDF::URI('p1'), RDF::URI('o1')] 21 | # c.insert [RDF::URI('s2'), RDF::URI('p2'), RDF::URI('o2')] 22 | # c.delete [RDF::URI('s_del'), RDF::URI('p_del'), RDF::URI('o_del')] 23 | # end 24 | # 25 | # graph = RDF::Graph.new 26 | # graph << [RDF::URI('s_del'), RDF::URI('p_del'), RDF::URI('o_del')] 27 | # 28 | # changes.apply(graph) # or graph.apply_changeset(changes) 29 | # 30 | # @note When applying a Changeset, deletes are resolved before inserts. 31 | # 32 | # @since 2.0.0 33 | class Changeset 34 | include RDF::Mutable 35 | 36 | ## 37 | # Applies a changeset to the given mutable RDF::Enumerable . 38 | # 39 | # @param [RDF::Mutable] mutable 40 | # @param [Hash{Symbol => Object}] options 41 | # @yield [changes] 42 | # @yieldparam [RDF::Changeset] changes 43 | # @return [void] 44 | def self.apply(mutable, options = {}, &block) 45 | self.new(&block).apply(mutable, options) 46 | end 47 | 48 | ## 49 | # RDF statements to delete when applied. 50 | # 51 | # @return [RDF::Enumerable] 52 | attr_reader :deletes 53 | 54 | ## 55 | # RDF statements to insert when applied. 56 | # 57 | # @return [RDF::Enumerable] 58 | attr_reader :inserts 59 | 60 | ## 61 | # Any additional options for this changeset. 62 | # 63 | # @return [Hash{Symbol => Object}] 64 | attr_reader :options 65 | 66 | ## 67 | # Initializes this changeset. 68 | # 69 | # @param [RDF::Enumerable] insert (RDF::Graph.new) 70 | # @param [RDF::Enumerable] delete (RDF::Graph.new) 71 | # @yield [changes] 72 | # @yieldparam [RDF::Changeset] changes 73 | def initialize(insert: [], delete: [], &block) 74 | @inserts = insert 75 | @deletes = delete 76 | 77 | @inserts.extend(RDF::Enumerable) unless @inserts.kind_of?(RDF::Enumerable) 78 | @deletes.extend(RDF::Enumerable) unless @deletes.kind_of?(RDF::Enumerable) 79 | 80 | if block_given? 81 | case block.arity 82 | when 1 then block.call(self) 83 | else self.instance_eval(&block) 84 | end 85 | end 86 | end 87 | 88 | ## 89 | # Returns `false` to indicate that this changeset is append-only. 90 | # 91 | # Changesets do not support the `RDF::Enumerable` protocol directly. 92 | # To enumerate the RDF statements to be inserted or deleted, use the 93 | # {RDF::Changeset#inserts} and {RDF::Changeset#deletes} accessors. 94 | # 95 | # @return [Boolean] 96 | # @see RDF::Readable#readable? 97 | def readable? 98 | false 99 | end 100 | 101 | ## 102 | # Applies this changeset to the given mutable RDF::Enumerable. 103 | # 104 | # This operation executes as a single write transaction. 105 | # 106 | # @param [RDF::Mutable] mutable 107 | # @param [Hash{Symbol => Object}] options 108 | # @return [void] 109 | def apply(mutable, options = {}) 110 | mutable.apply_changeset(self) 111 | end 112 | 113 | ## 114 | # @return [Boolean] `true` iff inserts and deletes are both empty 115 | def empty? 116 | deletes.empty? && inserts.empty? 117 | end 118 | 119 | ## 120 | # Returns a developer-friendly representation of this changeset. 121 | # 122 | # @return [String] 123 | def inspect 124 | sprintf("#<%s:%#0x(deletes: %d, inserts: %d)>", self.class.name, 125 | self.__id__, self.deletes.count, self.inserts.count) 126 | end 127 | 128 | ## 129 | # Outputs a developer-friendly representation of this changeset to 130 | # `stderr`. 131 | # 132 | # @return [void] 133 | def inspect! 134 | $stderr.puts(self.inspect) 135 | end 136 | 137 | protected 138 | 139 | ## 140 | # Appends an RDF statement to the sequence to insert when applied. 141 | # 142 | # @param [RDF::Statement] statement 143 | # @return [void] 144 | # @see RDF::Writable#insert_statement 145 | def insert_statement(statement) 146 | self.inserts << statement 147 | end 148 | 149 | ## 150 | # Appends an RDF statement to the sequence to delete when applied. 151 | # 152 | # @param [RDF::Statement] statement 153 | # @return [void] 154 | # @see RDF::Mutable#delete_statement 155 | def delete_statement(statement) 156 | self.deletes << statement 157 | end 158 | 159 | undef_method :load, :update, :clear 160 | end # Changeset 161 | end # RDF 162 | -------------------------------------------------------------------------------- /lib/rdf/mixin/writable.rb: -------------------------------------------------------------------------------- 1 | module RDF 2 | ## 3 | # Classes that include this module must implement the methods 4 | # `#insert_statement`. 5 | # 6 | # @see RDF::Graph 7 | # @see RDF::Repository 8 | module Writable 9 | extend RDF::Util::Aliasing::LateBound 10 | 11 | ## 12 | # Returns `true` if `self` is writable. 13 | # 14 | # @return [Boolean] `true` or `false` 15 | # @see RDF::Readable#readable? 16 | def writable? 17 | !frozen? 18 | end 19 | 20 | ## 21 | # Inserts RDF data into `self`. 22 | # 23 | # @param [RDF::Enumerable, RDF::Statement, #to_rdf] data 24 | # @return [self] 25 | def <<(data) 26 | case data 27 | when RDF::Reader 28 | insert_reader(data) 29 | when RDF::Graph 30 | insert_graph(data) 31 | when RDF::Enumerable 32 | insert_statements(data) 33 | when RDF::Statement 34 | insert_statement(data) 35 | else case 36 | when data.respond_to?(:to_rdf) && !data.equal?(rdf = data.to_rdf) 37 | self << rdf 38 | else 39 | insert_statement(Statement.from(data)) 40 | end 41 | end 42 | 43 | return self 44 | end 45 | 46 | ## 47 | # Inserts RDF statements into `self`. 48 | # 49 | # @note using splat argument syntax with excessive arguments provided 50 | # significantly affects performance. Use Enumerator form for large 51 | # numbers of statements. 52 | # 53 | # @overload insert(*statements) 54 | # @param [Array] statements 55 | # @return [self] 56 | # 57 | # @overload insert(statements) 58 | # @param [Enumerable] statements 59 | # @return [self] 60 | def insert(*statements) 61 | statements.map! do |value| 62 | case 63 | when value.respond_to?(:each_statement) 64 | insert_statements(value) 65 | nil 66 | when (statement = Statement.from(value)) 67 | statement 68 | else 69 | raise ArgumentError.new("not a valid statement: #{value.inspect}") 70 | end 71 | end 72 | statements.compact! 73 | insert_statements(statements) unless statements.empty? 74 | 75 | return self 76 | end 77 | alias_method :insert!, :insert 78 | 79 | protected 80 | 81 | ## 82 | # Inserts statements from the given RDF reader into the underlying 83 | # storage or output stream. 84 | # 85 | # Defaults to passing the reader to the {RDF::Writable#insert_statements} method. 86 | # 87 | # Subclasses of {RDF::Repository} may wish to override this method in 88 | # case their underlying storage can efficiently import RDF data directly 89 | # in particular serialization formats, thus avoiding the intermediate 90 | # parsing overhead. 91 | # 92 | # @param [RDF::Reader] reader 93 | # @return [void] 94 | # @since 0.2.3 95 | def insert_reader(reader) 96 | insert_statements(reader) 97 | end 98 | 99 | ## 100 | # Inserts the given RDF graph into the underlying storage or output 101 | # stream. 102 | # 103 | # Defaults to passing the graph to the {RDF::Writable#insert_statements} method. 104 | # 105 | # Subclasses of {RDF::Repository} may wish to override this method in 106 | # case their underlying storage architecture is graph-centric rather 107 | # than statement-oriented. 108 | # 109 | # Subclasses of {RDF::Writer} may wish to override this method if the 110 | # output format they implement supports named graphs, in which case 111 | # implementing this method may help in producing prettier and more 112 | # concise output. 113 | # 114 | # @param [RDF::Graph] graph 115 | # @return [void] 116 | def insert_graph(graph) 117 | insert_statements(graph) 118 | end 119 | 120 | ## 121 | # Inserts the given RDF statements into the underlying storage or output 122 | # stream. 123 | # 124 | # Defaults to invoking {RDF::Writable#insert_statement} for each given statement. 125 | # 126 | # Subclasses of {RDF::Repository} may wish to override this method if 127 | # they are capable of more efficiently inserting multiple statements at 128 | # once. 129 | # 130 | # Subclasses of {RDF::Writer} don't generally need to implement this 131 | # method. 132 | # 133 | # @param [RDF::Enumerable] statements 134 | # @return [void] 135 | # @since 0.1.6 136 | def insert_statements(statements) 137 | each = statements.respond_to?(:each_statement) ? :each_statement : :each 138 | statements.__send__(each) do |statement| 139 | insert_statement(statement) 140 | end 141 | end 142 | 143 | ## 144 | # Inserts an RDF statement into the underlying storage or output stream. 145 | # 146 | # Subclasses of {RDF::Repository} must implement this method, except if 147 | # they are immutable. 148 | # 149 | # Subclasses of {RDF::Writer} must implement this method. 150 | # 151 | # @param [RDF::Statement] statement 152 | # @return [void] 153 | # @abstract 154 | def insert_statement(statement) 155 | raise NotImplementedError.new("#{self.class}#insert_statement") 156 | end 157 | 158 | protected :insert_statements 159 | protected :insert_statement 160 | end # Writable 161 | end # RDF 162 | -------------------------------------------------------------------------------- /spec/model_value_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), 'spec_helper') 2 | 3 | describe RDF::Value do 4 | subject {"".extend(RDF::Value)} 5 | let(:uri) {RDF::URI("http://example/")} 6 | let(:node) {RDF::Node.new} 7 | let(:literal) {RDF::Literal("")} 8 | let(:graph) {RDF::Graph.new} 9 | let(:statement) {RDF::Statement(RDF::URI("http::/a"), RDF::URI("http::/b"), "c")} 10 | let(:variable) {RDF::Query::Variable.new(:v)} 11 | let(:list) {RDF::List[]} 12 | 13 | it "should not be instantiable" do 14 | expect { described_class.new }.to raise_error(NoMethodError) 15 | end 16 | 17 | it "#graph?" do 18 | expect(graph).to be_graph 19 | [subject, uri, node, literal, statement, variable, list].each do |v| 20 | expect(v).not_to be_graph 21 | end 22 | end 23 | 24 | it "#statement?" do 25 | expect(statement).to be_statement 26 | [subject, uri, node, literal, graph, variable, list].each do |v| 27 | expect(v).not_to be_statement 28 | end 29 | end 30 | 31 | it "#list?" do 32 | expect(list).to be_list 33 | [subject, statement, uri, node, literal, graph, statement, variable].each do |v| 34 | expect(v).not_to be_list 35 | end 36 | end 37 | 38 | it "#term?" do 39 | [uri, node, literal, variable].each do |v| 40 | expect(v).to be_term 41 | end 42 | [subject, statement, graph, statement, list].each do |v| 43 | expect(v).not_to be_term 44 | end 45 | end 46 | 47 | it "#resource?" do 48 | [uri, node].each do |v| 49 | expect(v).to be_resource 50 | end 51 | [subject, statement, graph, statement, list, literal, variable].each do |v| 52 | expect(v).not_to be_resource 53 | end 54 | end 55 | 56 | it "#literal?" do 57 | expect(literal).to be_literal 58 | [subject, statement, uri, node, graph, statement, variable, list].each do |v| 59 | expect(v).not_to be_literal 60 | end 61 | end 62 | 63 | it "#node?" do 64 | expect(node).to be_node 65 | [subject, uri, literal, graph, statement, variable, list].each do |v| 66 | expect(v).not_to be_node 67 | end 68 | expect(RDF::Statement(:a, :b, :c)).to be_node 69 | end 70 | 71 | it "#iri?" do 72 | expect(uri).to be_iri 73 | [subject, node, literal, graph, statement, variable, list].each do |v| 74 | expect(v).not_to be_iri 75 | end 76 | end 77 | 78 | it "#uri?" do 79 | expect(uri).to be_uri 80 | [subject, node, literal, graph, statement, variable, list].each do |v| 81 | expect(v).not_to be_uri 82 | end 83 | end 84 | 85 | it "#variable?" do 86 | expect(variable).to be_variable 87 | [subject, uri, node, literal, graph, statement, list].each do |v| 88 | expect(v).not_to be_variable 89 | end 90 | expect(RDF::Statement(:a, :b, RDF::Query::Variable.new(:c))).to be_variable 91 | end 92 | 93 | it "#constant?" do 94 | expect(variable).not_to be_constant 95 | [subject, uri, node, literal, graph, statement, list].each do |v| 96 | expect(v).to be_constant 97 | end 98 | expect(RDF::Statement(:a, :b, RDF::Query::Variable.new(:c))).not_to be_constant 99 | end 100 | 101 | it "#anonymous?" do 102 | is_expected.not_to be_anonymous 103 | expect(node).to be_anonymous 104 | [subject, uri, literal, graph, statement, variable, list].each do |v| 105 | expect(v).not_to be_anonymous 106 | end 107 | expect(RDF::Statement(:a, :b, :c)).not_to be_anonymous 108 | end 109 | 110 | it "#valid?" do 111 | [statement, uri, node, literal, graph, statement, variable, list].each do |v| 112 | expect(v).to be_valid 113 | end 114 | expect(RDF::URI("invalid")).not_to be_valid 115 | end 116 | 117 | it "#invalid?" do 118 | [statement, uri, node, literal, graph, statement, variable, list].each do |v| 119 | expect(v).not_to be_invalid 120 | end 121 | expect(RDF::URI("invalid")).to be_invalid 122 | end 123 | 124 | it "#validate!" do 125 | [statement, uri, node, literal, graph, statement, variable, list].each do |v| 126 | expect {v.validate!}.not_to raise_error 127 | end 128 | expect {RDF::URI("invalid").validate!}.to raise_error(ArgumentError) 129 | end 130 | 131 | it "#canonicalize" do 132 | [statement, uri, node, literal, graph, statement, variable, list].each do |v| 133 | expect(v.canonicalize).not_to equal v 134 | end 135 | end 136 | 137 | it "#canonicalize!" do 138 | [statement, uri, node, literal, graph, statement, variable, list].each do |v| 139 | expect(v.canonicalize!).to equal v 140 | end 141 | end 142 | 143 | it "#to_rdf" do 144 | [statement, uri, node, literal, graph, statement, variable, list].each do |v| 145 | expect {v.to_rdf}.not_to raise_error 146 | end 147 | end 148 | 149 | it "#to_term" do 150 | [uri, node, literal, variable, list].each do |v| 151 | expect {v.to_term}.not_to raise_error 152 | end 153 | [statement, graph, statement].each do |v| 154 | expect {v.to_term}.to raise_error(NotImplementedError) 155 | end 156 | end 157 | 158 | context "Examples" do 159 | it "Checking if a value is a resource (blank node or URI reference)" do 160 | end 161 | 162 | it "Checking if a value is a blank node" do 163 | end 164 | 165 | it "Checking if a value is a URI reference" do 166 | end 167 | 168 | it "Checking if a value is a literal" do 169 | end 170 | end 171 | end 172 | -------------------------------------------------------------------------------- /lib/rdf/model/value.rb: -------------------------------------------------------------------------------- 1 | module RDF 2 | ## 3 | # An RDF value. 4 | # 5 | # This is the basis for the RDF.rb class hierarchy. Anything that can be a 6 | # term of {RDF::Statement RDF statements} should directly or indirectly 7 | # include this module, but it does not define classes that can be included 8 | # within a {RDF::Statement}, for this see {RDF::Term}. 9 | # 10 | # @example Checking if a value is a resource (blank node or URI reference) 11 | # value.resource? 12 | # 13 | # @example Checking if a value is a blank node 14 | # value.node? 15 | # 16 | # @example Checking if a value is a URI reference 17 | # value.uri? 18 | # value.iri? 19 | # 20 | # @example Checking if a value is a literal 21 | # value.literal? 22 | # 23 | # @see RDF::Literal 24 | # @see RDF::Node 25 | # @see RDF::Resource 26 | # @see RDF::URI 27 | # @see RDF::Graph 28 | # @see RDF::List 29 | # @see RDF::Statement 30 | module Value 31 | ## 32 | # Returns `true` if `self` is a {RDF::Graph}. 33 | # 34 | # @return [Boolean] 35 | def graph? 36 | false 37 | end 38 | 39 | ## 40 | # Is this a {RDF::Statement}? 41 | # 42 | # @return [Boolean] 43 | def statement? 44 | false 45 | end 46 | 47 | ## 48 | # Is this a {RDF::List}? 49 | # 50 | # @return [Boolean] 51 | def list? 52 | false 53 | end 54 | 55 | ## 56 | # Is this a {RDF::Term}? 57 | # 58 | # @return [Boolean] 59 | def term? 60 | false 61 | end 62 | 63 | ## 64 | # Is this a {RDF::Resource}? 65 | # 66 | # @return [Boolean] 67 | def resource? 68 | false 69 | end 70 | 71 | ## 72 | # Is this a {RDF::Literal}? 73 | # 74 | # @return [Boolean] 75 | def literal? 76 | false 77 | end 78 | 79 | ## 80 | # Is this a {RDF::Node}, or does it contain a node? 81 | # 82 | # @return [Boolean] 83 | def node? 84 | false 85 | end 86 | 87 | ## 88 | # Is this an {RDF::IRI}? 89 | # 90 | # By default this is simply an alias for {RDF::Value#uri?}. 91 | # 92 | # @return [Boolean] 93 | def iri? 94 | uri? 95 | end 96 | 97 | ## 98 | # Is this an {RDF::URI}? 99 | # 100 | # @return [Boolean] 101 | def uri? 102 | false 103 | end 104 | 105 | ## 106 | # Is this a {RDF::Query::Variable}, or does it contain a variable? 107 | # 108 | # @return [Boolean] 109 | # @since 0.1.7 110 | def variable? 111 | false 112 | end 113 | 114 | ## 115 | # Is this constant, or are all of its components constant? 116 | # 117 | # Same as `!variable?` 118 | # 119 | # @return [Boolean] `true` or `false` 120 | # @see #variable? 121 | def constant? 122 | !(variable?) 123 | end 124 | 125 | ## 126 | # Is this value named? 127 | # 128 | # @return [Boolean] `true` or `false` 129 | def anonymous? 130 | false 131 | end 132 | 133 | ## 134 | # Is this value valid, and composed only of valid components? 135 | # 136 | # @return [Boolean] `true` or `false` 137 | # @since 0.3.9 138 | def valid? 139 | true 140 | end 141 | 142 | ## 143 | # Is this value invalid, or is it composed of any invalid components? 144 | # 145 | # @return [Boolean] `true` or `false` 146 | # @since 0.2.1 147 | def invalid? 148 | !valid? 149 | end 150 | 151 | ## 152 | # Default validate! implementation, overridden in concrete classes 153 | # @return [RDF::Value] `self` 154 | # @raise [ArgumentError] if the value is invalid 155 | # @since 0.3.9 156 | def validate! 157 | raise ArgumentError, "#{self.inspect} is not valid" if invalid? 158 | self 159 | end 160 | alias_method :validate, :validate! 161 | 162 | ## 163 | # Returns a copy of this value converted into its canonical 164 | # representation. 165 | # 166 | # @return [RDF::Value] 167 | # @since 1.0.8 168 | def canonicalize 169 | self.dup.canonicalize! 170 | end 171 | 172 | ## 173 | # Converts this value into its canonical representation. 174 | # 175 | # Should be overridden by concrete classes. 176 | # 177 | # @return [RDF::Value] `self` 178 | # @since 1.0.8 179 | def canonicalize! 180 | self 181 | end 182 | 183 | ## 184 | # Returns an `RDF::Value` representation of `self`. 185 | # 186 | # @return [RDF::Value] 187 | def to_rdf 188 | self 189 | end 190 | 191 | ## 192 | # Returns an `RDF::Term` representation of `self`. 193 | # 194 | # @return [RDF::Value] 195 | def to_term 196 | raise NotImplementedError, "#{self.class}#read_triple" # override in subclasses 197 | end 198 | 199 | ## 200 | # Returns a developer-friendly representation of `self`. 201 | # 202 | # The result will be of the format `#`, 203 | # where `...` is the string returned by `#to_s`. 204 | # 205 | # @return [String] 206 | def inspect 207 | sprintf("#<%s:%#0x(%s)>", self.class.name, __id__, to_s) 208 | end 209 | 210 | ## 211 | # Outputs a developer-friendly representation of `self` to `stderr`. 212 | # 213 | # @return [void] 214 | def inspect! 215 | warn(inspect) 216 | end 217 | 218 | ## 219 | # Default implementation of `type_error`, which returns false. 220 | # Classes including RDF::TypeCheck will raise TypeError 221 | # instead. 222 | # 223 | # @return [false] 224 | def type_error(message) 225 | false 226 | end 227 | end # Value 228 | end # RDF 229 | -------------------------------------------------------------------------------- /lib/rdf/model/node.rb: -------------------------------------------------------------------------------- 1 | module RDF 2 | ## 3 | # An RDF blank node, also known as an anonymous or unlabeled node. 4 | # 5 | # @example Creating a blank node with an implicit identifier 6 | # bnode = RDF::Node.new 7 | # 8 | # @example Creating a blank node with an UUID identifier 9 | # bnode = RDF::Node.uuid 10 | # bnode.to_s #=> "_:504c0a30-0d11-012d-3f50-001b63cac539" 11 | # 12 | class Node 13 | include RDF::Resource 14 | 15 | ## 16 | # Defines the maximum number of interned Node references that can be held 17 | # cached in memory at any one time. 18 | # 19 | # @note caching interned nodes means that two different invocations using the same symbol will result in the same node, which may not be appropriate depending on the graph from which it is used. RDF requires that bnodes with the same label are, in fact, different bnodes, unless they are used within the same document. 20 | CACHE_SIZE = -1 # unlimited by default 21 | 22 | ## 23 | # @return [RDF::Util::Cache] 24 | # @private 25 | def self.cache 26 | require 'rdf/util/cache' unless defined?(::RDF::Util::Cache) 27 | @cache ||= RDF::Util::Cache.new(CACHE_SIZE) 28 | end 29 | 30 | ## 31 | # Returns a blank node with a random UUID-based identifier. 32 | # 33 | # @param [Regexp] grammar (nil) 34 | # a grammar specification that the generated UUID must match 35 | # @return [RDF::Node] 36 | def self.uuid(grammar: nil) 37 | case 38 | when grammar 39 | # The UUID is generated such that its initial part is guaranteed 40 | # to match the given `grammar`, e.g. `/^[A-Za-z][A-Za-z0-9]*/`. 41 | # Some RDF storage systems (e.g. AllegroGraph) require this. 42 | # @see http://github.com/bendiken/rdf/pull/43 43 | uuid = RDF::Util::UUID.generate(options) until uuid =~ grammar 44 | else 45 | uuid = RDF::Util::UUID.generate(options) 46 | end 47 | self.new(uuid) 48 | end 49 | 50 | ## 51 | # Alias for `RDF::Node.new`, at the moment. 52 | # 53 | # @private 54 | # @param [#to_s] id 55 | # @return [RDF::Node] 56 | # @since 0.2.0 57 | def self.intern(id) 58 | (cache[(id = id.to_s).to_sym] ||= self.new(id)).freeze 59 | end 60 | 61 | ## 62 | # Override #dup to remember original object. 63 | # This allows .eql? to determine that two nodes 64 | # are the same thing, and not different nodes 65 | # instantiated with the same identifier. 66 | # @return [RDF::Node] 67 | def dup 68 | node = super 69 | node.original = self.original || self 70 | node 71 | end 72 | 73 | ## 74 | # Originally instantiated node, if any 75 | # @return [RDF::Node] 76 | attr_accessor :original 77 | 78 | # @return [String] 79 | attr_accessor :id 80 | 81 | ## 82 | # @param [#to_s] id 83 | def initialize(id = nil) 84 | id = nil if id.to_s.empty? 85 | @id = (id || "g#{__id__.to_i.abs}").to_s.freeze 86 | end 87 | 88 | ## 89 | # Returns `true`. 90 | # 91 | # @return [Boolean] 92 | def node? 93 | true 94 | end 95 | 96 | ## 97 | # Returns `true`. 98 | # 99 | # @return [Boolean] 100 | def anonymous? 101 | true 102 | end 103 | 104 | alias_method :unlabeled?, :anonymous? 105 | 106 | ## 107 | # Returns `false`. 108 | # 109 | # @return [Boolean] 110 | def labeled? 111 | !unlabeled? 112 | end 113 | 114 | ## 115 | # Returns a hash code for this blank node. 116 | # 117 | # @return [Fixnum] 118 | def hash 119 | @id.hash 120 | end 121 | 122 | ## 123 | # Determines if `self` is the same term as `other`. 124 | # 125 | # In this case, nodes must be the same object 126 | # 127 | # @param [Node] other 128 | # @return [Boolean] 129 | def eql?(other) 130 | other.is_a?(RDF::Node) && (self.original || self).equal?(other.original || other) 131 | end 132 | 133 | ## 134 | # Checks whether this blank node is equal to `other` (type checking). 135 | # 136 | # In this case, different nodes having the same id are considered the same. 137 | # 138 | # Per SPARQL data-r2/expr-equal/eq-2-2, numeric can't be compared with other types 139 | # 140 | # @param [Object] other 141 | # @return [Boolean] 142 | # @see http://www.w3.org/TR/rdf-sparql-query/#func-RDFterm-equal 143 | def ==(other) 144 | if other.is_a?(Literal) 145 | # If other is a Literal, reverse test to consolodate complex type checking logic 146 | other == self 147 | else 148 | other.respond_to?(:node?) && other.node? && 149 | self.hash == other.to_term.hash && 150 | other.respond_to?(:id) && @id == other.to_term.id 151 | end 152 | end 153 | alias_method :===, :== 154 | 155 | ## 156 | # Returns a representation of this node independent of any identifier used to initialize it 157 | # 158 | # @return [String] 159 | def to_unique_base 160 | original ? original.to_unique_base : "_:g#{__id__.to_i.abs}" 161 | end 162 | 163 | ## 164 | # Make this term identifier unique, if it is found to be shared with another node having the same identifier 165 | # @return [self] 166 | def make_unique! 167 | @id = to_unique_base[2..-1] 168 | self 169 | end 170 | 171 | ## 172 | # Returns a string representation of this blank node. 173 | # 174 | # @return [String] 175 | def to_s 176 | "_:%s" % @id.to_s 177 | end 178 | 179 | ## 180 | # Returns a symbol representation of this blank node. 181 | # 182 | # @return [Symbol] 183 | # @since 0.2.0 184 | def to_sym 185 | @id.to_s.to_sym 186 | end 187 | end 188 | end 189 | -------------------------------------------------------------------------------- /spec/util_file_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), 'spec_helper') 2 | require 'webmock/rspec' 3 | require 'rdf/ntriples' 4 | 5 | describe RDF::Util::File do 6 | before(:each) {WebMock.disable_net_connect!} 7 | after(:each) {WebMock.allow_net_connect!} 8 | 9 | describe ".http_adapter" do 10 | after do 11 | RDF::Util::File.http_adapter = nil 12 | end 13 | 14 | it "returns Net::HTTP if rest-client is not available" do 15 | hide_const("RestClient") 16 | expect(RDF::Util::File.http_adapter).to eq RDF::Util::File::NetHttpAdapter 17 | end 18 | 19 | it "returns RestClient if rest-client is available" do 20 | require 'rest-client' 21 | expect(RDF::Util::File.http_adapter).to eq RDF::Util::File::RestClientAdapter 22 | end 23 | 24 | it "return Net::HTTP if explicitly requested" do 25 | require 'rest-client' 26 | expect(RDF::Util::File.http_adapter(true)).to eq RDF::Util::File::NetHttpAdapter 27 | end 28 | end 29 | 30 | describe RDF::Util::File::HttpAdapter do 31 | describe ".default_accept_header" do 32 | subject { RDF::Util::File::HttpAdapter.default_accept_header.split(", ") } 33 | before do 34 | allow(RDF::Format).to receive(:reader_types).and_return(["text/html", "text/plain", "application/xhtml+xml"]) 35 | end 36 | it "should demote text/html to q=0.5" do 37 | expect(subject).to include "text/html;q=0.5" 38 | end 39 | it "should demote text/plain to q=0.5" do 40 | expect(subject).to include "text/plain;q=0.5" 41 | end 42 | it "should demote application/xhtml+xml to q=0.7" do 43 | expect(subject).to include "application/xhtml+xml;q=0.7" 44 | end 45 | end 46 | end 47 | 48 | describe RDF::Util::File::FaradayAdapter do 49 | let(:http_adapter) { RDF::Util::File::FaradayAdapter } 50 | require 'faraday' 51 | require 'faraday_middleware' 52 | 53 | describe ".conn=" do 54 | after do 55 | http_adapter.conn = nil 56 | end 57 | 58 | it "should set the Faraday connection" do 59 | custom_connection = Faraday.new 60 | http_adapter.conn = custom_connection 61 | expect(http_adapter.conn).to eq custom_connection 62 | end 63 | end 64 | 65 | describe ".conn" do 66 | it "should return a Faraday connection that follows redirects" do 67 | expect(http_adapter.conn.builder.handlers).to include FaradayMiddleware::FollowRedirects 68 | end 69 | end 70 | end 71 | 72 | describe ".open_file" do 73 | let(:uri) {"http://ruby-rdf.github.com/rdf/etc/doap.nt"} 74 | let(:opened) {double("opened")} 75 | before(:each) do 76 | expect(opened).to receive(:opened) 77 | end 78 | 79 | it "yields a local file" do 80 | r = RDF::Util::File.open_file(fixture_path("test.nt")) do |f| 81 | expect(f).to respond_to(:read) 82 | opened.opened 83 | RDF::Reader.new 84 | end 85 | expect(r).to be_a(RDF::Reader) 86 | end 87 | 88 | it "raises IOError on missing local file" do 89 | expect {RDF::Util::File.open_file(fixture_path("not-here"))}.to raise_error IOError 90 | opened.opened 91 | end 92 | 93 | it "yields an RemoteDocument and returns yieldreturn" do 94 | WebMock.stub_request(:get, uri). 95 | to_return(body: File.read(File.expand_path("../../etc/doap.nt", __FILE__)), 96 | status: 200, 97 | headers: { 'Content-Type' => RDF::NTriples::Format.content_type.first}) 98 | r = RDF::Util::File.open_file(uri) do |f| 99 | expect(f).to respond_to(:read) 100 | expect(f.content_type).to eq RDF::NTriples::Format.content_type.first 101 | expect(f.code).to eq 200 102 | opened.opened 103 | RDF::Reader.new 104 | end 105 | expect(r).to be_a(RDF::Reader) 106 | end unless ENV["CI"] 107 | 108 | it "yields a file URL" do 109 | r = RDF::Util::File.open_file("file:" + fixture_path("test.nt")) do |f| 110 | expect(f).to respond_to(:read) 111 | opened.opened 112 | RDF::Reader.new 113 | end 114 | expect(r).to be_a(RDF::Reader) 115 | end 116 | 117 | it "returns a local file" do 118 | f = RDF::Util::File.open_file(fixture_path("test.nt")) 119 | expect(f).to respond_to(:read) 120 | opened.opened 121 | end 122 | 123 | it "returns a local file with fragment identifier" do 124 | f = RDF::Util::File.open_file(fixture_path("test.nt#fragment")) 125 | expect(f).to respond_to(:read) 126 | opened.opened 127 | end 128 | 129 | it "returns a local file with query" do 130 | f = RDF::Util::File.open_file(fixture_path("test.nt?query")) 131 | expect(f).to respond_to(:read) 132 | opened.opened 133 | end 134 | 135 | it "returns a file URL" do 136 | f = RDF::Util::File.open_file("file:" + fixture_path("test.nt")) 137 | expect(f).to respond_to(:read) 138 | opened.opened 139 | end 140 | 141 | it "raises IOError on missing file URL" do 142 | expect {RDF::Util::File.open_file("file:" + fixture_path("not-here"))}.to raise_error IOError 143 | opened.opened 144 | end 145 | end 146 | 147 | context "HTTP Adapters" do 148 | require 'rdf/spec/http_adapter' 149 | 150 | context "using Net::HTTP" do 151 | it_behaves_like 'an RDF::HttpAdapter' do 152 | let(:http_adapter) { RDF::Util::File::NetHttpAdapter } 153 | end 154 | end 155 | 156 | context "using RestClient" do 157 | require 'rest_client' 158 | 159 | it_behaves_like 'an RDF::HttpAdapter' do 160 | let(:http_adapter) { RDF::Util::File::RestClientAdapter } 161 | end 162 | end 163 | 164 | context "using Faraday" do 165 | require 'faraday' 166 | require 'faraday_middleware' 167 | 168 | it_behaves_like 'an RDF::HttpAdapter' do 169 | let(:http_adapter) { RDF::Util::File::FaradayAdapter } 170 | end 171 | end 172 | end unless ENV["CI"] 173 | end 174 | -------------------------------------------------------------------------------- /spec/cli_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), 'spec_helper') 2 | require 'rdf/cli' 3 | require 'rdf/ntriples' 4 | require 'rdf/nquads' 5 | 6 | describe RDF::CLI do 7 | before(:all) {ARGV.replace %w(help)} 8 | 9 | TEST_FILES = { 10 | nt: fixture_path('test.nt'), 11 | nq: fixture_path('test.nq'), 12 | } 13 | 14 | describe "options" do 15 | it "calls a block" do 16 | expect {|b| RDF::CLI.options(&b)}.to yield_control 17 | end 18 | 19 | it "sets debug logging with --debug" do 20 | ARGV.replace %w(help --debug) 21 | options = RDF::CLI.options 22 | expect(options.options[:logger].level).to eql Logger::DEBUG 23 | end 24 | 25 | it "sets :evaluate with --evaluate" do 26 | ARGV.replace %w(help --evaluate foo) 27 | options = RDF::CLI.options 28 | expect(options.options[:evaluate]).to eql "foo" 29 | end 30 | 31 | describe "add_command" do 32 | around(:each) do |example| 33 | orig_commands = RDF::CLI::COMMANDS.dup 34 | example.run 35 | RDF::CLI::COMMANDS.clear 36 | RDF::CLI::COMMANDS.merge!(orig_commands) 37 | end 38 | 39 | it "adds a command" do 40 | RDF::CLI.add_command(:foo) do |argv, opts| 41 | $stdout.puts "Hello, World!" 42 | end 43 | expect {RDF::CLI.exec(["foo"])}.to write("Hello, World!").to(:output) 44 | end 45 | end 46 | 47 | describe "--input-format" do 48 | it "sets :format given a legitimate format" do 49 | ARGV.replace %w(help --format ntriples) 50 | options = RDF::CLI.options 51 | expect(options.options[:format]).to eql :ntriples 52 | end 53 | 54 | it "Calls options on selected reader" do 55 | ARGV.replace %w(help --format ntriples) 56 | expect(RDF::NTriples::Reader).to receive(:options).and_return({}) 57 | options = RDF::CLI.options 58 | end 59 | 60 | #it "sets aborts given an illegitimate format" do 61 | # ARGV.replace %w(help --format foo) 62 | # options = RDF::CLI.options 63 | # expect(RDF::CLI).to receive(:abort).and_return(nil) 64 | # expect(options.options[:format]).not_to eql :foo 65 | #end 66 | end 67 | 68 | it "sets :output with --output" do 69 | ARGV.replace %w(help --output foo) 70 | mock = double("open") 71 | expect(File).to receive(:open).with("foo", "w").and_return(mock) 72 | options = RDF::CLI.options 73 | expect(options.options[:output]).to eql mock 74 | end 75 | 76 | describe "--output-format" do 77 | it "sets :output_format given a legitimate format" do 78 | ARGV.replace %w(help --output-format ntriples) 79 | options = RDF::CLI.options 80 | expect(options.options[:output_format]).to eql :ntriples 81 | end 82 | 83 | it "Calls options on selected writer" do 84 | ARGV.replace %w(help --output-format ntriples) 85 | expect(RDF::NTriples::Writer).to receive(:options).and_return({}) 86 | options = RDF::CLI.options 87 | end 88 | 89 | #it "sets aborts given an illegitimate format" do 90 | # ARGV.replace %w(help --output-format foo) 91 | # options = RDF::CLI.options 92 | # expect(RDF::CLI).to receive(:abort).and_return(nil) 93 | # expect(options.options[::output_format]).not_to eql :foo 94 | #end 95 | end 96 | end 97 | 98 | describe "#serialize" do 99 | after(:each) do 100 | $stdin = STDIN 101 | end 102 | 103 | it "serializes to NTriples" do 104 | expect {RDF::CLI.exec(["serialize", TEST_FILES[:nt]])}.to write.to(:output) 105 | end 106 | 107 | it "serializes to NTriples from $stdin" do 108 | $stdin = File.open(TEST_FILES[:nt]) 109 | expect {RDF::CLI.exec(["serialize"])}.to write.to(:output) 110 | end 111 | 112 | it "serializes to NTriples from evaluate" do 113 | expect {RDF::CLI.exec(["serialize"], evaluate: File.read(TEST_FILES[:nt]))}.to write.to(:output) 114 | end 115 | 116 | it "serializes to NQuads" do 117 | expect { 118 | RDF::CLI.exec(["serialize", TEST_FILES[:nt]], output_format: :nquads) 119 | }.to write.to(:output) 120 | end 121 | end 122 | 123 | it "#help" do 124 | expect {RDF::CLI.exec(["help", TEST_FILES[:nt]])}.to write(:anything) 125 | end 126 | 127 | describe "#count" do 128 | TEST_FILES.each do |fmt, file| 129 | it "counts #{fmt}" do 130 | g = RDF::Repository.load(file) 131 | expect {RDF::CLI.exec(["count", file])}.to write(/Parsed #{g.count} statements/) 132 | end 133 | end 134 | end 135 | 136 | describe "#subjects" do 137 | TEST_FILES.each do |fmt, file| 138 | it "gets subjects #{fmt}" do 139 | expect {RDF::CLI.exec(["subjects", file])}.to write(:something) 140 | end 141 | end 142 | end 143 | 144 | describe "#objects" do 145 | TEST_FILES.each do |fmt, file| 146 | it "gets objects #{fmt}" do 147 | expect {RDF::CLI.exec(["objects", file])}.to write(:anything) 148 | end 149 | end 150 | end 151 | 152 | describe "#predicates" do 153 | TEST_FILES.each do |fmt, file| 154 | it "gets predicates #{fmt}" do 155 | expect {RDF::CLI.exec(["predicates", file])}.to write(:something) 156 | end 157 | end 158 | end 159 | 160 | describe "#lengths" do 161 | TEST_FILES.each do |fmt, file| 162 | it "gets lengths #{fmt}" do 163 | expect {RDF::CLI.exec(["lengths", file])}.to write(:something) 164 | end 165 | end 166 | end 167 | 168 | describe "#validate" do 169 | TEST_FILES.each do |fmt, file| 170 | it "validates #{fmt}" do 171 | expect {RDF::CLI.exec(["validate", file])}.to write(/Input is valid/) 172 | end 173 | end 174 | end 175 | 176 | context "chaining" do 177 | TEST_FILES.each do |fmt, file| 178 | it "chains subjects and objects #{fmt}" do 179 | expect {RDF::CLI.exec(["subjects", "objects", file])}.to write(:something) 180 | end 181 | end 182 | end 183 | end -------------------------------------------------------------------------------- /spec/format_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), 'spec_helper') 2 | require 'rdf/spec/format' 3 | require 'rdf/ntriples' 4 | require 'rdf/nquads' 5 | 6 | class RDF::Format::FooFormat < RDF::Format 7 | content_type 'application/test', extension: :test 8 | reader { RDF::NTriples::Reader } 9 | def self.detect(sample) 10 | sample == "foo" 11 | end 12 | def self.to_sym; :foo_bar; end 13 | end 14 | 15 | class RDF::Format::BarFormat < RDF::Format 16 | content_type 'application/test', extension: :test 17 | reader { RDF::NTriples::Reader } 18 | def self.detect(sample) 19 | sample == "bar" 20 | end 21 | def self.to_sym; :foo_bar; end 22 | end 23 | 24 | describe RDF::Format do 25 | 26 | # @see lib/rdf/spec/format.rb in rdf-spec 27 | it_behaves_like 'an RDF::Format' do 28 | let(:format_class) { described_class } 29 | end 30 | 31 | # If there are multiple formats that assert the same type or extension, 32 | # Format.for should yield to return a sample used for detection 33 | describe ".for" do 34 | { 35 | "symbol" => :foo_bar, 36 | "path with extension" => "filename.test", 37 | "file_name" => {file_name: "filename.test"}, 38 | "file_extension" => {file_extension: "test"}, 39 | "content_type" => {content_type: "application/test"}, 40 | "URI" => RDF::URI("filename.test"), 41 | "fragment" => "filename.test#fragment", 42 | "query" => "filename.test?query", 43 | }.each do |condition, arg| 44 | context condition do 45 | it "yields given duplicates" do 46 | expect {|b| RDF::Format.for(arg, &b)}.to yield_control 47 | end 48 | 49 | it "returns last defined format for duplicates" do 50 | expect(RDF::Format.for(arg)).to eq RDF::Format::BarFormat 51 | end 52 | 53 | it "returns detected format for duplicates" do 54 | expect(RDF::Format.for(arg) { "foo" }).to eq RDF::Format::FooFormat 55 | expect(RDF::Format.for(arg) { "bar" }).to eq RDF::Format::BarFormat 56 | end 57 | end 58 | end 59 | end 60 | 61 | describe ".reader_symbols" do 62 | it "returns symbols of available readers" do 63 | [:ntriples, :nquads, :foo_bar].each do |sym| 64 | expect(RDF::Format.reader_symbols).to include(sym) 65 | end 66 | end 67 | end 68 | 69 | describe ".reader_types" do 70 | it "returns content-types of available readers" do 71 | %w( 72 | application/n-triples text/plain 73 | application/n-quads text/x-nquads 74 | application/test 75 | ).each do |ct| 76 | expect(RDF::Format.reader_types).to include(ct) 77 | end 78 | end 79 | end 80 | 81 | describe ".writer_symbols" do 82 | it "returns symbols of available writers" do 83 | [:ntriples, :nquads].each do |sym| 84 | expect(RDF::Format.writer_symbols).to include(sym) 85 | end 86 | [:fooformat, :barformat].each do |sym| 87 | expect(RDF::Format.writer_symbols).not_to include(sym) 88 | end 89 | end 90 | end 91 | 92 | describe ".writer_types" do 93 | it "returns content-types of available writers" do 94 | %w( 95 | application/n-triples text/plain 96 | application/n-quads text/x-nquads 97 | ).each do |ct| 98 | expect(RDF::Format.writer_types).to include(ct) 99 | end 100 | expect(RDF::Format.writer_types).not_to include("application/test") 101 | end 102 | end 103 | 104 | RDF::Format.each do |format| 105 | context format.name do 106 | subject {format} 107 | let(:content_types) { 108 | RDF::Format.content_types.map {|ct, f| ct if f.include?(format)}.compact 109 | } 110 | let(:file_extensions) { 111 | RDF::Format.file_extensions.map {|ext, f| ext if f.include?(format)}.compact 112 | } 113 | its(:content_type) {is_expected.to match content_types} 114 | its(:file_extension) {is_expected.to match file_extensions} 115 | end 116 | end 117 | 118 | context "Examples" do 119 | require 'rdf/ntriples' 120 | subject {RDF::Format} 121 | 122 | its(:each) do 123 | expect {RDF::Format.each {|klass| $stdout.puts klass.name}}.to write(:something) 124 | end 125 | 126 | its(:"for") do 127 | [ 128 | :ntriples, 129 | "etc/doap.nt", 130 | {file_name: "etc/doap.nt"}, 131 | {file_extension: "nt"}, 132 | {content_type: "application/n-triples"} 133 | ].each do |arg| 134 | expect(subject.for(arg)).to eq RDF::NTriples::Format 135 | end 136 | end 137 | 138 | it "has content-type application/n-triples" do 139 | expect(subject.content_types["application/n-triples"]).to include(RDF::NTriples::Format) 140 | end 141 | it "has file extension .nt" do 142 | expect(subject.file_extensions[:nt]).to include(RDF::NTriples::Format) 143 | end 144 | 145 | it "Defining a new RDF serialization format class" do 146 | expect { 147 | class RDF::NTriples::Format < RDF::Format 148 | content_type 'application/n-triples', extension: :nt 149 | content_encoding 'utf-8' 150 | 151 | reader RDF::NTriples::Reader 152 | writer RDF::NTriples::Writer 153 | end 154 | }.not_to raise_error 155 | end 156 | 157 | it "Instantiating an RDF reader or writer class (1)" do 158 | expect { 159 | subject.for(:ntriples).reader.new($stdin) { |reader|} 160 | subject.for(:ntriples).writer.new($stdout) { |writer|} 161 | }.not_to raise_error 162 | end 163 | 164 | it "Instantiating an RDF reader or writer class (2)" do 165 | expect { 166 | RDF::Reader.for(:ntriples).new($stdin) { |reader|} 167 | RDF::Writer.for(:ntriples).new($stdout) { |writer|} 168 | }.not_to raise_error 169 | end 170 | 171 | describe ".name" do 172 | specify {expect(RDF::NTriples::Format.name).to eq "N-Triples"} 173 | end 174 | 175 | describe ".detect" do 176 | specify {expect(RDF::NTriples::Format.detect(" .")).to be_truthy} 177 | end 178 | end 179 | end 180 | -------------------------------------------------------------------------------- /lib/rdf/query/variable.rb: -------------------------------------------------------------------------------- 1 | class RDF::Query 2 | ## 3 | # An RDF query variable. 4 | # 5 | # @example Creating a named unbound variable 6 | # var = RDF::Query::Variable.new(:x) 7 | # var.unbound? #=> true 8 | # var.value #=> nil 9 | # 10 | # @example Creating an anonymous unbound variable 11 | # var = RDF::Query::Variable.new 12 | # var.name #=> :g2166151240 13 | # 14 | # @example Unbound variables match any value 15 | # var === RDF::Literal(42) #=> true 16 | # 17 | # @example Creating a bound variable 18 | # var = RDF::Query::Variable.new(:y, 123) 19 | # var.bound? #=> true 20 | # var.value #=> 123 21 | # 22 | # @example Bound variables match only their actual value 23 | # var = RDF::Query::Variable.new(:y, 123) 24 | # var === 42 #=> false 25 | # var === 123 #=> true 26 | # 27 | # @example Getting the variable name 28 | # var = RDF::Query::Variable.new(:y, 123) 29 | # var.named? #=> true 30 | # var.name #=> :y 31 | # var.to_sym #=> :y 32 | # 33 | # @example Rebinding a variable returns the previous value 34 | # var.bind!(456) #=> 123 35 | # var.value #=> 456 36 | # 37 | # @example Unbinding a previously bound variable 38 | # var.unbind! 39 | # var.unbound? #=> true 40 | # 41 | # @example Getting the string representation of a variable 42 | # var = RDF::Query::Variable.new(:x) 43 | # var.to_s #=> "?x" 44 | # var = RDF::Query::Variable.new(:y, 123) 45 | # var.to_s #=> "?y=123" 46 | # 47 | class Variable 48 | include RDF::Term 49 | 50 | ## 51 | # The variable's name. 52 | # 53 | # @return [Symbol] 54 | attr_accessor :name 55 | alias_method :to_sym, :name 56 | 57 | ## 58 | # The variable's value. 59 | # 60 | # @return [RDF::Term] 61 | attr_accessor :value 62 | 63 | ## 64 | # @param [Symbol, #to_sym] name 65 | # the variable name 66 | # @param [RDF::Term] value 67 | # an optional variable value 68 | def initialize(name = nil, value = nil) 69 | @name = (name || "g#{__id__.to_i.abs}").to_sym 70 | @value = value 71 | end 72 | 73 | ## 74 | # Returns `true`. 75 | # 76 | # @return [Boolean] 77 | # @see RDF::Term#variable? 78 | # @since 0.1.7 79 | def variable? 80 | true 81 | end 82 | 83 | ## 84 | # Returns `true` if this variable has a name. 85 | # 86 | # @return [Boolean] 87 | def named? 88 | true 89 | end 90 | 91 | ## 92 | # Returns `true` if this variable is bound. 93 | # 94 | # @return [Boolean] 95 | def bound? 96 | !unbound? 97 | end 98 | 99 | ## 100 | # Returns `true` if this variable is unbound. 101 | # 102 | # @return [Boolean] 103 | def unbound? 104 | value.nil? 105 | end 106 | 107 | ## 108 | # Returns `true` if this variable is distinguished. 109 | # 110 | # @return [Boolean] 111 | def distinguished? 112 | @distinguished.nil? || @distinguished 113 | end 114 | 115 | ## 116 | # Sets if variable is distinguished or non-distinguished. 117 | # By default, variables are distinguished 118 | # 119 | # @return [Boolean] 120 | def distinguished=(value) 121 | @distinguished = value 122 | end 123 | 124 | ## 125 | # Rebinds this variable to the given `value`. 126 | # 127 | # @param [RDF::Term] value 128 | # @return [RDF::Term] the previous value, if any. 129 | def bind(value) 130 | old_value = self.value 131 | self.value = value 132 | old_value 133 | end 134 | alias_method :bind!, :bind 135 | 136 | ## 137 | # Unbinds this variable, discarding any currently bound value. 138 | # 139 | # @return [RDF::Term] the previous value, if any. 140 | def unbind 141 | old_value = self.value 142 | self.value = nil 143 | old_value 144 | end 145 | alias_method :unbind!, :unbind 146 | 147 | ## 148 | # Returns this variable as `Hash`. 149 | # 150 | # @return [Hash{Symbol => RDF::Query::Variable}] 151 | def variables 152 | {name => self} 153 | end 154 | alias_method :to_hash, :variables 155 | 156 | ## 157 | # Returns this variable's bindings (if any) as a `Hash`. 158 | # 159 | # @return [Hash{Symbol => RDF::Term}] 160 | def bindings 161 | unbound? ? {} : {name => value} 162 | end 163 | 164 | ## 165 | # Returns a hash code for this variable. 166 | # 167 | # @return [Fixnum] 168 | # @since 0.3.0 169 | def hash 170 | @name.hash 171 | end 172 | 173 | ## 174 | # Returns `true` if this variable is equivalent to a given `other` 175 | # variable. Or, to another Term if bound, or to any other Term 176 | # 177 | # @param [Object] other 178 | # @return [Boolean] `true` or `false` 179 | # @since 0.3.0 180 | def eql?(other) 181 | if unbound? 182 | other.is_a?(RDF::Term) # match any Term when unbound 183 | elsif other.is_a?(RDF::Query::Variable) 184 | @name.eql?(other.name) 185 | else 186 | value.eql?(other) 187 | end 188 | end 189 | alias_method :==, :eql? 190 | 191 | ## 192 | # Compares this variable with the given value. 193 | # 194 | # @param [RDF::Term] other 195 | # @return [Boolean] 196 | def ===(other) 197 | if unbound? 198 | other.is_a?(RDF::Term) # match any Term when unbound 199 | else 200 | value === other 201 | end 202 | end 203 | 204 | ## 205 | # Returns a string representation of this variable. 206 | # 207 | # Distinguished variables are indicated with a single `?`. 208 | # 209 | # Non-distinguished variables are indicated with a double `??` 210 | # 211 | # @example 212 | # v = Variable.new("a") 213 | # v.to_s => '?a' 214 | # v.distinguished = false 215 | # v.to_s => '??a' 216 | # 217 | # @return [String] 218 | def to_s 219 | prefix = distinguished? ? '?' : "??" 220 | unbound? ? "#{prefix}#{name}" : "#{prefix}#{name}=#{value}" 221 | end 222 | end # Variable 223 | end # RDF::Query 224 | -------------------------------------------------------------------------------- /lib/rdf/nquads.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | module RDF 3 | ## 4 | # **`RDF::NQuads`** provides support for the N-Quads serialization format. 5 | # 6 | # This has not yet been implemented as of RDF.rb 0.3.x. 7 | module NQuads 8 | include RDF::NTriples 9 | 10 | ## 11 | # N-Quads format specification. 12 | # 13 | # @example Obtaining an NQuads format class 14 | # RDF::Format.for(:nquads) #=> RDF::NQuads::Format 15 | # RDF::Format.for("etc/doap.nq") 16 | # RDF::Format.for(file_name: "etc/doap.nq") 17 | # RDF::Format.for(file_extension: "nq") 18 | # RDF::Format.for(content_type: "application/n-quads") 19 | # 20 | # @see http://www.w3.org/TR/n-quads/ 21 | # @since 0.4.0 22 | class Format < RDF::Format 23 | content_type 'application/n-quads', extension: :nq, alias: ['text/x-nquads'] 24 | content_encoding 'utf-8' 25 | 26 | reader { RDF::NQuads::Reader } 27 | writer { RDF::NQuads::Writer } 28 | 29 | ## 30 | # Sample detection to see if it matches N-Quads (or N-Triples) 31 | # 32 | # Use a text sample to detect the format of an input file. Sub-classes implement 33 | # a matcher sufficient to detect probably format matches, including disambiguating 34 | # between other similar formats. 35 | # 36 | # @param [String] sample Beginning several bytes (about 1K) of input. 37 | # @return [Boolean] 38 | def self.detect(sample) 39 | !!sample.match(%r( 40 | (?:\s*(?:<[^>]*>) | (?:_:\w+)) # Subject 41 | \s* 42 | (?:\s*<[^>]*>) # Predicate 43 | \s* 44 | (?:(?:<[^>]*>) | (?:_:\w+) | (?:"[^"\n]*"(?:^^|@\S+)?)) # Object 45 | \s* 46 | (?:\s*(?:<[^>]*>) | (?:_:\w+)) # Graph Name 47 | \s*\. 48 | )x) && !( 49 | sample.match(%r(@(base|prefix|keywords)|\{)) || # Not Turtle/N3/TriG 50 | sample.match(%r(<(html|rdf))i) # Not HTML or XML 51 | ) 52 | end 53 | 54 | # Human readable name for this format 55 | def self.name; "N-Quads"; end 56 | end 57 | 58 | class Reader < NTriples::Reader 59 | ## 60 | # Read a Quad, where the graph_name is optional 61 | # 62 | # @return [Array] 63 | # @see http://sw.deri.org/2008/07/n-quads/#grammar 64 | # @since 0.4.0 65 | def read_triple 66 | loop do 67 | readline.strip! # EOFError thrown on end of input 68 | line = @line # for backtracking input in case of parse error 69 | 70 | begin 71 | unless blank? || read_comment 72 | subject = read_uriref || read_node || fail_subject 73 | predicate = read_uriref(intern: true) || fail_predicate 74 | object = read_uriref || read_node || read_literal || fail_object 75 | graph_name = read_uriref || read_node 76 | if validate? && !read_eos 77 | log_error("Expected end of statement (found: #{current_line.inspect})", lineno: lineno, exception: RDF::ReaderError) 78 | end 79 | return [subject, predicate, object, {graph_name: graph_name}] 80 | end 81 | rescue RDF::ReaderError => e 82 | @line = line # this allows #read_value to work 83 | raise e 84 | end 85 | end 86 | end 87 | 88 | end # Reader 89 | 90 | class Writer < NTriples::Writer 91 | ## 92 | # Outputs the N-Quads representation of a statement. 93 | # 94 | # @param [RDF::Resource] subject 95 | # @param [RDF::URI] predicate 96 | # @param [RDF::Term] object 97 | # @return [void] 98 | def write_quad(subject, predicate, object, graph_name) 99 | puts format_quad(subject, predicate, object, graph_name, @options) 100 | end 101 | 102 | ## 103 | # Returns the N-Quads representation of a statement. 104 | # 105 | # @param [RDF::Statement] statement 106 | # @param [Hash{Symbol => Object}] options = ({}) 107 | # @return [String] 108 | # @since 0.4.0 109 | def format_statement(statement, options = {}) 110 | format_quad(*statement.to_quad, options) 111 | end 112 | 113 | ## 114 | # Returns the N-Triples representation of a triple. 115 | # 116 | # @param [RDF::Resource] subject 117 | # @param [RDF::URI] predicate 118 | # @param [RDF::Term] object 119 | # @param [RDF::Term] graph_name 120 | # @param [Hash{Symbol => Object}] options = ({}) 121 | # @return [String] 122 | def format_quad(subject, predicate, object, graph_name, options = {}) 123 | s = "%s %s %s " % [subject, predicate, object].map { |value| format_term(value, options) } 124 | s += format_term(graph_name, options) + " " if graph_name 125 | s + "." 126 | end 127 | end # Writer 128 | 129 | ## 130 | # Reconstructs an RDF value from its serialized N-Triples 131 | # representation. 132 | # 133 | # @param [String] data 134 | # @return [RDF::Value] 135 | # @see RDF::NTriples::Reader.unserialize 136 | # @since 0.1.5 137 | def self.unserialize(data) 138 | Reader.unserialize(data) 139 | end 140 | 141 | ## 142 | # Returns the serialized N-Triples representation of the given RDF 143 | # value. 144 | # 145 | # @param [RDF::Value] value 146 | # @return [String] 147 | # @see RDF::NTriples::Writer.serialize 148 | # @since 0.1.5 149 | def self.serialize(value) 150 | Writer.serialize(value) 151 | end 152 | end # NQuads 153 | 154 | 155 | ## 156 | # Extensions for `RDF::Value`. 157 | module Value 158 | ## 159 | # Returns the N-Quads representation of this value. 160 | # 161 | # This method is only available when the 'rdf/nquads' serializer has 162 | # been explicitly required. 163 | # 164 | # @return [String] 165 | # @since 0.4.0 166 | def to_nquads 167 | RDF::NQuads.serialize(self) 168 | end 169 | end # Value 170 | end # RDF 171 | -------------------------------------------------------------------------------- /lib/rdf/query/hash_pattern_normalizer.rb: -------------------------------------------------------------------------------- 1 | module RDF; class Query 2 | ## 3 | # An RDF query pattern normalizer. 4 | class HashPatternNormalizer 5 | ## 6 | # A counter that can be incremented and decremented. 7 | class Counter 8 | ## 9 | # The offset (or initial value) for this counter. 10 | # 11 | # @return [Numeric] 12 | attr_reader :offset 13 | 14 | ## 15 | # The increment for this counter. 16 | # 17 | # @return [Numeric] 18 | attr_reader :increment 19 | 20 | ## 21 | # @param [Numeric] offset 22 | # the offset (or initial value) for this counter. 23 | # @param [Numeric] increment 24 | # the increment for this counter. 25 | def initialize(offset = 0, increment = 1) 26 | @offset = offset 27 | @increment = increment 28 | 29 | @value = @offset 30 | end 31 | 32 | ## 33 | # Decrements this counter, and returns the new value. 34 | # 35 | # @return [RDF::Query::HashPatternNormalizer::Counter] 36 | def decrement! 37 | @value -= @increment 38 | 39 | self 40 | end 41 | 42 | ## 43 | # Increments this counter, and returns the new value. 44 | # 45 | # @return [RDF::Query::HashPatternNormalizer::Counter] 46 | def increment! 47 | @value += @increment 48 | 49 | self 50 | end 51 | 52 | ## 53 | # Returns a floating point representation of this counter. 54 | # 55 | # @return [Float] 56 | def to_f 57 | @value.to_f 58 | end 59 | 60 | ## 61 | # Returns an integer representation of this counter. 62 | # 63 | # @return [Integer] 64 | def to_i 65 | @value.to_i 66 | end 67 | 68 | ## Returns a string representation of this counter. 69 | # 70 | # @return [String] 71 | def to_s 72 | @value.to_s 73 | end 74 | end # RDF::Query::HashPatternNormalizer::Counter 75 | 76 | class << self 77 | ## 78 | # Returns the normalization of the specified `hash_pattern`. 79 | # 80 | # @param [Hash{Symbol => Object}] hash_pattern (Hash.new) 81 | # the query pattern as a hash. 82 | # @param [Hash{Symbol => Object}] options (Hash.new) 83 | # any additional normalization options. 84 | # @option options [String] :anonymous_subject_format ("__%s__") 85 | # the string format for anonymous subjects. 86 | # @return [Hash{Symbol => Object}] 87 | # the resulting query pattern as a normalized hash. 88 | def normalize!(hash_pattern = {}, options = {}) 89 | raise ArgumentError, "invalid hash pattern: #{hash_pattern.inspect}" unless hash_pattern.is_a?(Hash) 90 | 91 | counter = RDF::Query::HashPatternNormalizer::Counter.new 92 | 93 | anonymous_subject_format = (options[:anonymous_subject_format] || '__%s__').to_s 94 | 95 | hash_pattern.inject({}) { |acc, pair| 96 | subject, predicate_to_object = pair 97 | 98 | ensure_absence_of_duplicate_subjects!(acc, subject) 99 | normalized_predicate_to_object = normalize_hash!(predicate_to_object, acc, counter, anonymous_subject_format) 100 | ensure_absence_of_duplicate_subjects!(acc, subject) 101 | 102 | acc[subject] = normalized_predicate_to_object 103 | acc 104 | } 105 | end 106 | 107 | private 108 | 109 | ## 110 | # @private 111 | def ensure_absence_of_duplicate_subjects!(acc, subject) 112 | raise "duplicate subject #{subject.inspect} in normalized hash pattern: #{acc.inspect}" if acc.key?(subject) 113 | 114 | return 115 | end 116 | 117 | ## 118 | # @private 119 | def normalize_array!(array, *args) 120 | raise ArgumentError, "invalid array pattern: #{array.inspect}" unless array.is_a?(Array) 121 | 122 | array.collect { |object| 123 | normalize_object!(object, *args) 124 | } 125 | end 126 | 127 | ## 128 | # @private 129 | def normalize_hash!(hash, *args) 130 | raise ArgumentError, "invalid hash pattern: #{hash.inspect}" unless hash.is_a?(Hash) 131 | 132 | hash.inject({}) { |acc, pair| 133 | acc[pair.first] = normalize_object!(pair.last, *args) 134 | acc 135 | } 136 | end 137 | 138 | ## 139 | # @private 140 | def normalize_object!(object, *args) 141 | case object 142 | when Array then normalize_array!(object, *args) 143 | when Hash then replace_hash_with_anonymous_subject!(object, *args) 144 | else object 145 | end 146 | end 147 | 148 | ## 149 | # @private 150 | def replace_hash_with_anonymous_subject!(hash, acc, counter, anonymous_subject_format) 151 | raise ArgumentError, "invalid hash pattern: #{hash.inspect}" unless hash.is_a?(Hash) 152 | 153 | subject = (anonymous_subject_format % counter.increment!).to_sym 154 | 155 | ensure_absence_of_duplicate_subjects!(acc, subject) 156 | normalized_hash = normalize_hash!(hash, acc, counter, anonymous_subject_format) 157 | ensure_absence_of_duplicate_subjects!(acc, subject) 158 | 159 | acc[subject] = normalized_hash 160 | 161 | subject 162 | end 163 | end 164 | 165 | ## 166 | # The options for this hash pattern normalizer. 167 | # 168 | # @return [Hash{Symbol => Object}] 169 | attr_reader :options 170 | 171 | ## 172 | # @param [Hash{Symbol => Object}] options (Hash.new) 173 | # any additional normalization options. 174 | # @option options [String] :anonymous_subject_format ("__%s__") 175 | # the string format for anonymous subjects. 176 | def initialize(options = {}) 177 | @options = options.dup 178 | end 179 | 180 | ## 181 | # Equivalent to calling `self.class.normalize!(hash_pattern, self.options)`. 182 | # 183 | # @param [Hash{Symbol => Object}] hash_pattern 184 | # the query pattern as a hash. 185 | # @return [Hash{Symbol => Object}] 186 | # the resulting query pattern as a normalized hash. 187 | def normalize!(hash_pattern = {}) 188 | self.class.normalize!(hash_pattern, @options) 189 | end 190 | end # RDF::Query::HashPatternNormalizer 191 | end; end # RDF::Query 192 | -------------------------------------------------------------------------------- /spec/query_variable_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), 'spec_helper') 2 | 3 | describe RDF::Query::Variable do 4 | context ".new(:x)" do 5 | let(:var) {RDF::Query::Variable.new(:x)} 6 | subject {var} 7 | 8 | it "is named" do 9 | expect(subject.named?).to be_truthy 10 | expect(subject.name).to eq :x 11 | end 12 | 13 | it "has no value" do 14 | expect(subject.value).to be_nil 15 | end 16 | 17 | context "distinguished" do 18 | it "is distinguished" do 19 | expect(subject).to be_distinguished 20 | end 21 | 22 | it "can be made non-distinguished" do 23 | subject.distinguished = false 24 | expect(subject).to_not be_distinguished 25 | end 26 | 27 | it "has a string representation" do 28 | expect(subject.to_s).to eq "?x" 29 | end 30 | end 31 | 32 | context "non-distinguished" do 33 | subject { var.distinguished = false; var } 34 | it "is nondistinguished" do 35 | expect(subject).to_not be_distinguished 36 | end 37 | 38 | it "can be made distinguished" do 39 | subject.distinguished = true 40 | expect(subject).to be_distinguished 41 | end 42 | 43 | it "has a string representation" do 44 | expect(subject.to_s).to eq "??x" 45 | end 46 | end 47 | 48 | it "is convertible to a symbol" do 49 | expect(subject.to_sym).to eq :x 50 | end 51 | 52 | 53 | it "has no value" do 54 | expect(subject.value).to be_nil 55 | end 56 | 57 | it "is unbound" do 58 | expect(subject).to be_unbound 59 | expect(subject).not_to be_bound 60 | expect(subject.variables).to eq({x: subject}) 61 | expect(subject.bindings).to eq({}) 62 | end 63 | 64 | describe "#==" do 65 | it "matches any Term" do 66 | [RDF::URI("foo"), RDF::Node.new, RDF::Literal("foo"), subject].each do |value| 67 | expect(subject).to eq value 68 | end 69 | end 70 | 71 | it "does not match non-terms" do 72 | [nil, true, false, 123].each do |value| 73 | expect(subject).to_not eq value 74 | end 75 | end 76 | end 77 | 78 | describe "#eql?" do 79 | it "matches any Term" do 80 | [RDF::URI("foo"), RDF::Node.new, RDF::Literal("foo"), subject].each do |value| 81 | expect(subject).to be_eql value 82 | end 83 | end 84 | 85 | it "does not match non-terms" do 86 | [nil, true, false, 123].each do |value| 87 | expect(subject).to_not be_eql value 88 | end 89 | end 90 | end 91 | 92 | describe "#===" do 93 | it "matches any Term" do 94 | [RDF::URI("foo"), RDF::Node.new, RDF::Literal("foo"), subject].each do |value| 95 | expect((subject === value)).to be_truthy 96 | end 97 | end 98 | 99 | it "does not match non-terms" do 100 | [nil, true, false, 123].each do |value| 101 | expect((subject === value)).to be_falsey 102 | end 103 | end 104 | end 105 | end 106 | 107 | context ".new(:x, 123)" do 108 | subject {RDF::Query::Variable.new(:x, RDF::Literal(123))} 109 | 110 | it "has a value" do 111 | expect(subject.value).to eq RDF::Literal(123) 112 | end 113 | 114 | it "is bound" do 115 | expect(subject).not_to be_unbound 116 | expect(subject).to be_bound 117 | expect(subject.variables).to eq({x: subject}) 118 | expect(subject.bindings).to eq({x: RDF::Literal(123)}) 119 | end 120 | 121 | it "matches only its value" do 122 | [nil, true, false, RDF::Literal(456)].each do |value| 123 | expect((subject === value)).to be_falsey 124 | end 125 | expect((subject === RDF::Literal(123))).to be_truthy 126 | end 127 | 128 | it "has a string representation" do 129 | expect(subject.to_s).to eq "?x=123" 130 | end 131 | end 132 | 133 | context "when rebound" do 134 | subject {RDF::Query::Variable.new(:x, RDF::Literal(123))} 135 | 136 | it "returns the previous value" do 137 | expect(subject.bind(RDF::Literal(456))).to eq RDF::Literal(123) 138 | expect(subject.bind(RDF::Literal(789))).to eq RDF::Literal(456) 139 | end 140 | 141 | it "is still bound" do 142 | subject.bind!(RDF::Literal(456)) 143 | expect(subject).not_to be_unbound 144 | expect(subject).to be_bound 145 | end 146 | end 147 | 148 | context "when unbound" do 149 | subject {RDF::Query::Variable.new(:x, RDF::Literal(123))} 150 | 151 | it "returns the previous value" do 152 | expect(subject.unbind).to eq RDF::Literal(123) 153 | expect(subject.unbind).to eq nil 154 | end 155 | 156 | it "is not bound" do 157 | subject.unbind! 158 | expect(subject).to be_unbound 159 | expect(subject).not_to be_bound 160 | end 161 | end 162 | 163 | context "Examples" do 164 | subject {RDF::Query::Variable.new(:y, 123)} 165 | it "Creating a named unbound variable" do 166 | var = RDF::Query::Variable.new(:x) 167 | expect(var).to be_unbound 168 | expect(var.value).to be_nil 169 | end 170 | 171 | it "Creating an anonymous unbound variable" do 172 | expect(RDF::Query::Variable.new.name).to be_a(Symbol) 173 | end 174 | 175 | it "Unbound variables match any value" do 176 | expect(RDF::Query::Variable.new).to eq RDF::Literal(42) 177 | end 178 | 179 | it "Creating a bound variable" do 180 | expect(subject).to be_bound 181 | expect(subject.value).to eq 123 182 | end 183 | 184 | it "Bound variables match only their actual value" do 185 | expect(subject).to_not eq 42 186 | expect(subject).to eq 123 187 | end 188 | 189 | it "Getting the variable name" do 190 | expect(subject).to be_named 191 | expect(subject.name).to eq :y 192 | expect(subject.to_sym).to eq :y 193 | end 194 | 195 | it "Rebinding a variable returns the previous value" do 196 | expect(subject.bind!(456)).to eq 123 197 | expect(subject.value).to eq 456 198 | end 199 | 200 | it "Unbinding a previously bound variable" do 201 | subject.unbind! 202 | expect(subject).to be_unbound 203 | end 204 | 205 | it "Getting the string representation of a variable" do 206 | var = RDF::Query::Variable.new(:x) 207 | expect(var.to_s).to eq "?x" 208 | expect(subject.to_s).to eq "?y=123" 209 | end 210 | 211 | describe "#to_s" do 212 | it "can be undistinguished" do 213 | var = RDF::Query::Variable.new("a") 214 | expect(var.to_s).to eq "?a" 215 | var.distinguished = false 216 | expect(var.to_s).to eq "??a" 217 | end 218 | end 219 | end 220 | end 221 | -------------------------------------------------------------------------------- /spec/util_logger_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), 'spec_helper') 2 | 3 | describe RDF::Util::Logger do 4 | class LogTester 5 | attr_reader :options 6 | include RDF::Util::Logger 7 | def initialize(logger = nil) 8 | @logger = logger 9 | @options = {} 10 | end 11 | end 12 | 13 | context "with Spec Logger" do 14 | subject {LogTester.new(RDF::Spec.logger)} 15 | 16 | describe "#logger" do 17 | it "retrieves logger from @logger" do 18 | expect(subject.logger).to eql subject.instance_variable_get(:@logger) 19 | end 20 | 21 | it "prefers logger from options" do 22 | l = RDF::Spec.logger 23 | expect(subject.logger(logger: l)).to eql l 24 | end 25 | 26 | it "prefers @logger to @options[:logger]" do 27 | subject.options[:logger] = RDF::Spec.logger 28 | expect(subject.logger).to eql subject.instance_variable_get(:@logger) 29 | end 30 | 31 | it "will use @options[:logger]" do 32 | subject.instance_variable_set(:@logger, nil) 33 | subject.options[:logger] = RDF::Spec.logger 34 | expect(subject.logger).to eql subject.options[:logger] 35 | end 36 | 37 | it "applies LoggerBehavior to logger" do 38 | expect(subject.logger).to be_a(RDF::Util::Logger::LoggerBehavior) 39 | end 40 | end 41 | 42 | describe "#log_fatal" do 43 | specify {expect {subject.log_fatal("foo", "bar")}.to raise_error(StandardError, "foo")} 44 | 45 | it "raises provided exception" do 46 | expect {subject.log_fatal("foo", exception: RDF::ReaderError)}.to raise_error(RDF::ReaderError, "foo") 47 | end 48 | 49 | it "adds locaton to log message" do 50 | expect {subject.log_fatal("foo")}.to raise_error(StandardError) 51 | expect(subject.logger.to_s).to match /Called from #{File.expand_path("", __FILE__)}:#{__LINE__-1}/ 52 | end 53 | 54 | it "logs to $stderr if logger not configured" do 55 | logger = LogTester.new 56 | expect {logger.log_fatal("foo") rescue nil}.to write(:something).to(:error) 57 | end 58 | 59 | it "increments log_statistics" do 60 | subject.log_fatal("foo") rescue nil 61 | expect(subject.log_statistics[:fatal]).to eql 1 62 | end 63 | end 64 | 65 | describe "#log_error" do 66 | specify {expect {subject.log_error("foo")}.not_to raise_error} 67 | it "raises provided exception" do 68 | expect {subject.log_error("foo", exception: RDF::ReaderError)}.to raise_error(RDF::ReaderError, "foo") 69 | end 70 | 71 | it "Logs messages, and yield return" do 72 | subject.log_error("a", "b") {"c"} 73 | expect(subject.logger.to_s).to eql "ERROR a: b: c\n" 74 | end 75 | 76 | it "Does not log errors while recovering" do 77 | subject.log_error("a") 78 | subject.log_error("b") 79 | subject.log_recover 80 | subject.log_error("c") 81 | expect(subject.logger.to_s).to eql "ERROR a\nERROR c\n" 82 | end 83 | 84 | it "Indicates recovering" do 85 | subject.log_error("a") 86 | expect(subject).to be_log_recovering 87 | end 88 | 89 | it "logs to $stderr if logger not configured" do 90 | logger = LogTester.new 91 | expect {logger.log_error("foo") rescue nil}.to write(:something).to(:error) 92 | end 93 | 94 | it "increments log_statistics" do 95 | subject.log_error("foo") rescue nil 96 | expect(subject.log_statistics[:error]).to eql 1 97 | end 98 | end 99 | 100 | describe "#log_info" do 101 | it "Logs messages, and yield return" do 102 | subject.log_info("a", "b") {"c"} 103 | expect(subject.logger.to_s).to eql "INFO a: b: c\n" 104 | end 105 | 106 | it "adds lineno" do 107 | subject.log_info("a", lineno: 10) 108 | expect(subject.logger.to_s).to eql "INFO [line 10] a\n" 109 | end 110 | 111 | it "adds depth with option" do 112 | subject.log_info("a", depth: 2) 113 | expect(subject.logger.to_s).to eql "INFO a\n" 114 | end 115 | 116 | it "adds depth with option" do 117 | subject.log_info("a", depth: 2) 118 | expect(subject.logger.to_s).to eql "INFO a\n" 119 | end 120 | 121 | it "uses logger from @options" do 122 | logger = LogTester.new 123 | logger.options[:logger] = RDF::Spec.logger 124 | logger.log_info("a") 125 | expect(logger.instance_variable_get(:@logger).to_s).to be_empty 126 | expect(logger.options[:logger].to_s).to eql "INFO a\n" 127 | end 128 | 129 | it "uses logger from options" do 130 | logger = LogTester.new 131 | l = RDF::Spec.logger 132 | logger.log_info("a", logger: l) 133 | expect(logger.instance_variable_get(:@logger).to_s).to be_empty 134 | expect(l.to_s).to eql "INFO a\n" 135 | end 136 | 137 | it "increments log_statistics" do 138 | subject.log_info("foo") rescue nil 139 | expect(subject.log_statistics[:info]).to eql 1 140 | end 141 | end 142 | 143 | describe "#log_depth" do 144 | specify {expect {|b| subject.log_depth(&b)}.to yield_with_no_args} 145 | 146 | it "returns result of block" do 147 | expect(subject.log_depth {"foo"}).to eql "foo" 148 | end 149 | 150 | it "increments log_depth in block" do 151 | subject.log_depth do 152 | expect(subject.log_depth).to eql 1 153 | end 154 | end 155 | end 156 | end 157 | 158 | context "with $stderr" do 159 | subject {LogTester.new} 160 | 161 | it "sets logger to $stderr" do 162 | expect(subject.logger).to equal $stderr 163 | end 164 | 165 | it "forgets log_statistics between instances" do 166 | expect {subject.log_error "An error"}.to write("An error").to(:error) 167 | 168 | new_tester = LogTester.new 169 | expect(new_tester.log_statistics).to be_empty 170 | expect(subject.log_statistics).not_to be_empty 171 | end 172 | end 173 | 174 | context "with StringIO" do 175 | subject {LogTester.new(StringIO.new)} 176 | 177 | it "appends to StringIO" do 178 | subject.log_error("a") 179 | subject.logger.rewind 180 | expect(subject.logger.read).to eql "ERROR a\n" 181 | end 182 | end 183 | 184 | context "with Array" do 185 | subject {LogTester.new([])} 186 | 187 | it "appends to Array" do 188 | subject.log_error("a") 189 | expect(subject.logger).to eql ["ERROR a"] 190 | end 191 | end 192 | 193 | context "with false" do 194 | subject {LogTester.new(false)} 195 | 196 | it "Creates an Object logger" do 197 | subject.log_error("a") 198 | expect(subject.options).to have_key(:logger) 199 | end 200 | end 201 | end 202 | -------------------------------------------------------------------------------- /lib/rdf/vocab/writer.rb: -------------------------------------------------------------------------------- 1 | require 'rdf' 2 | require 'rdf/vocabulary' 3 | 4 | module RDF 5 | ## 6 | # Vocabulary format specification. This can be used to generate a Ruby class definition from a loaded vocabulary. 7 | # 8 | class Vocabulary 9 | class Format < RDF::Format 10 | content_encoding 'utf-8' 11 | writer { RDF::Vocabulary::Writer } 12 | end 13 | 14 | class Writer < RDF::Writer 15 | include RDF::Util::Logger 16 | format RDF::Vocabulary::Format 17 | 18 | attr_accessor :class_name, :module_name 19 | 20 | def self.options 21 | [ 22 | RDF::CLI::Option.new( 23 | symbol: :class_name, 24 | datatype: String, 25 | on: ["--class-name NAME"], 26 | description: "Name of created Ruby class (vocabulary format)."), 27 | RDF::CLI::Option.new( 28 | symbol: :module_name, 29 | datatype: String, 30 | on: ["--module-name NAME"], 31 | description: "Name of Ruby module containing class-name (vocabulary format)."), 32 | RDF::CLI::Option.new( 33 | symbol: :strict, 34 | datatype: TrueClass, 35 | on: ["--strict"], 36 | description: "Make strict vocabulary" 37 | ) {true}, 38 | RDF::CLI::Option.new( 39 | symbol: :extra, 40 | datatype: String, 41 | on: ["--extra URIEncodedJSON"], 42 | description: "URI Encoded JSON representation of extra data" 43 | ) {|arg| ::JSON.parse(::URI.decode(arg))}, 44 | ] 45 | end 46 | 47 | ## 48 | # Initializes the writer. 49 | # 50 | # @param [IO, File] output 51 | # the output stream 52 | # @param [Hash{Symbol => Object}] options = ({}) 53 | # any additional options. See {RDF::Writer#initialize} 54 | # @option options [RDF::URI] :base_uri 55 | # URI of this vocabulary 56 | # @option options [String] :class_name 57 | # Class name for this vocabulary 58 | # @option options [String] :module_name ("RDF") 59 | # Module name for this vocabulary 60 | # @option options [Hash] extra 61 | # Extra properties to add to the output (programatic only) 62 | # @option options [String] patch 63 | # An LD Patch to run against the graph before writing 64 | # @option options [Boolean] strict (false) 65 | # Create an RDF::StrictVocabulary instead of an RDF::Vocabulary 66 | # @yield [writer] `self` 67 | # @yieldparam [RDF::Writer] writer 68 | # @yieldreturn [void] 69 | def initialize(output = $stdout, options = {}, &block) 70 | options.fetch(:base_uri) {raise ArgumentError, "base_uri option required"} 71 | @graph = RDF::Repository.new 72 | super 73 | end 74 | 75 | def write_triple(subject, predicate, object) 76 | @graph << RDF::Statement(subject, predicate, object) 77 | end 78 | 79 | # Generate vocabulary 80 | # 81 | def write_epilogue 82 | class_name = options[:class_name] 83 | module_name = options.fetch(:module_name, "RDF") 84 | source = options.fetch(:location, base_uri) 85 | strict = options.fetch(:strict, false) 86 | 87 | # Passing a graph for the location causes it to serialize the written triples. 88 | vocab = RDF::Vocabulary.from_graph(@graph, 89 | url: base_uri, 90 | class_name: class_name, 91 | extra: options[:extra]) 92 | 93 | @output.print %(# -*- encoding: utf-8 -*- 94 | # frozen_string_literal: true 95 | # This file generated automatically using rdf vocabulary format from #{source} 96 | require 'rdf' 97 | module #{module_name} 98 | # @!parse 99 | # # Vocabulary for <#{base_uri}> 100 | # class #{class_name} < RDF::#{"Strict" if strict}Vocabulary 101 | # end 102 | class #{class_name} < RDF::#{"Strict" if strict}Vocabulary("#{base_uri}") 103 | ).gsub(/^ /, '') 104 | 105 | # Split nodes into Class/Property/Datatype/Other 106 | term_nodes = { 107 | class: {}, 108 | property: {}, 109 | datatype: {}, 110 | other: {} 111 | } 112 | 113 | vocab.each.to_a.sort.each do |term| 114 | name = term.to_s[base_uri.length..-1].to_sym 115 | kind = begin 116 | case term.type.to_s 117 | when /Class/ then :class 118 | when /Property/ then :property 119 | when /Datatype/ then :datatype 120 | else :other 121 | end 122 | rescue KeyError 123 | # This can try to resolve referenced terms against the previous version of this vocabulary, which may be strict, and fail if the referenced term hasn't been created yet. 124 | :other 125 | end 126 | term_nodes[kind][name] = term.attributes 127 | end 128 | 129 | { 130 | class: "Class definitions", 131 | property: "Property definitions", 132 | datatype: "Datatype definitions", 133 | other: "Extra definitions" 134 | }.each do |tt, comment| 135 | next if term_nodes[tt].empty? 136 | @output.puts "\n # #{comment}" 137 | term_nodes[tt].each {|name, attributes| from_node name, attributes, tt} 138 | end 139 | 140 | # Query the vocabulary to extract property and class definitions 141 | @output.puts " end\nend" 142 | end 143 | 144 | private 145 | ## 146 | # Turn a node definition into a property/term expression 147 | def from_node(name, attributes, term_type) 148 | op = term_type == :property ? "property" : "term" 149 | 150 | components = [" #{op} #{name.to_sym.inspect}"] 151 | attributes.keys.sort_by(&:to_s).map(&:to_sym).each do |key| 152 | next if key == :vocab 153 | value = Array(attributes[key]) 154 | component = key.inspect.start_with?(':"') ? "#{key.inspect} => " : "#{key.to_s}: " 155 | value = value.first if value.length == 1 156 | component << if value.is_a?(Array) 157 | '[' + value.map {|v| serialize_value(v, key)}.sort.join(", ") + "]" 158 | else 159 | serialize_value(value, key) 160 | end 161 | components << component 162 | end 163 | @output.puts components.join(",\n ") 164 | end 165 | 166 | def serialize_value(value, key) 167 | case key.to_s 168 | when "comment", /:/ 169 | "%(#{value.gsub('(', '\(').gsub(')', '\)')}).freeze" 170 | else 171 | "#{value.inspect}.freeze" 172 | end 173 | end 174 | end 175 | end 176 | end 177 | -------------------------------------------------------------------------------- /lib/rdf/vocab/rdfv.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | # This file generated automatically using vocab-fetch from http://www.w3.org/1999/02/22-rdf-syntax-ns# 3 | require 'rdf' 4 | module RDF 5 | class RDFV < StrictVocabulary("http://www.w3.org/1999/02/22-rdf-syntax-ns#") 6 | 7 | class << self 8 | def name; "RDF"; end 9 | alias_method :__name__, :name 10 | end 11 | 12 | # Class definitions 13 | term :Alt, 14 | comment: %(The class of containers of alternatives.).freeze, 15 | label: "Alt".freeze, 16 | :"rdfs:isDefinedBy" => %(rdf:).freeze, 17 | subClassOf: "rdfs:Container".freeze, 18 | type: "rdfs:Class".freeze 19 | term :Bag, 20 | comment: %(The class of unordered containers.).freeze, 21 | label: "Bag".freeze, 22 | :"rdfs:isDefinedBy" => %(rdf:).freeze, 23 | subClassOf: "rdfs:Container".freeze, 24 | type: "rdfs:Class".freeze 25 | term :List, 26 | comment: %(The class of RDF Lists.).freeze, 27 | label: "List".freeze, 28 | :"rdfs:isDefinedBy" => %(rdf:).freeze, 29 | subClassOf: "rdfs:Resource".freeze, 30 | type: "rdfs:Class".freeze 31 | term :Property, 32 | comment: %(The class of RDF properties.).freeze, 33 | label: "Property".freeze, 34 | :"rdfs:isDefinedBy" => %(rdf:).freeze, 35 | subClassOf: "rdfs:Resource".freeze, 36 | type: "rdfs:Class".freeze 37 | term :Seq, 38 | comment: %(The class of ordered containers.).freeze, 39 | label: "Seq".freeze, 40 | :"rdfs:isDefinedBy" => %(rdf:).freeze, 41 | subClassOf: "rdfs:Container".freeze, 42 | type: "rdfs:Class".freeze 43 | term :Statement, 44 | comment: %(The class of RDF statements.).freeze, 45 | label: "Statement".freeze, 46 | :"rdfs:isDefinedBy" => %(rdf:).freeze, 47 | subClassOf: "rdfs:Resource".freeze, 48 | type: "rdfs:Class".freeze 49 | 50 | # Property definitions 51 | property :first, 52 | comment: %(The first item in the subject RDF list.).freeze, 53 | domain: "rdf:List".freeze, 54 | label: "first".freeze, 55 | range: "rdfs:Resource".freeze, 56 | :"rdfs:isDefinedBy" => %(rdf:).freeze, 57 | type: "rdf:Property".freeze 58 | property :object, 59 | comment: %(The object of the subject RDF statement.).freeze, 60 | domain: "rdf:Statement".freeze, 61 | label: "object".freeze, 62 | range: "rdfs:Resource".freeze, 63 | :"rdfs:isDefinedBy" => %(rdf:).freeze, 64 | type: "rdf:Property".freeze 65 | property :predicate, 66 | comment: %(The predicate of the subject RDF statement.).freeze, 67 | domain: "rdf:Statement".freeze, 68 | label: "predicate".freeze, 69 | range: "rdfs:Resource".freeze, 70 | :"rdfs:isDefinedBy" => %(rdf:).freeze, 71 | type: "rdf:Property".freeze 72 | property :rest, 73 | comment: %(The rest of the subject RDF list after the first item.).freeze, 74 | domain: "rdf:List".freeze, 75 | label: "rest".freeze, 76 | range: "rdf:List".freeze, 77 | :"rdfs:isDefinedBy" => %(rdf:).freeze, 78 | type: "rdf:Property".freeze 79 | property :subject, 80 | comment: %(The subject of the subject RDF statement.).freeze, 81 | domain: "rdf:Statement".freeze, 82 | label: "subject".freeze, 83 | range: "rdfs:Resource".freeze, 84 | :"rdfs:isDefinedBy" => %(rdf:).freeze, 85 | type: "rdf:Property".freeze 86 | property :type, 87 | comment: %(The subject is an instance of a class.).freeze, 88 | domain: "rdfs:Resource".freeze, 89 | label: "type".freeze, 90 | range: "rdfs:Class".freeze, 91 | :"rdfs:isDefinedBy" => %(rdf:).freeze, 92 | type: "rdf:Property".freeze 93 | property :value, 94 | comment: %(Idiomatic property used for structured values.).freeze, 95 | domain: "rdfs:Resource".freeze, 96 | label: "value".freeze, 97 | range: "rdfs:Resource".freeze, 98 | :"rdfs:isDefinedBy" => %(rdf:).freeze, 99 | type: "rdf:Property".freeze 100 | 101 | # Datatype definitions 102 | term :HTML, 103 | comment: %(The datatype of RDF literals storing fragments of HTML content).freeze, 104 | label: "HTML".freeze, 105 | :"rdfs:isDefinedBy" => %(rdf:).freeze, 106 | :"rdfs:seeAlso" => %(http://www.w3.org/TR/rdf11-concepts/#section-html).freeze, 107 | subClassOf: "rdfs:Literal".freeze, 108 | type: "rdfs:Datatype".freeze 109 | term :PlainLiteral, 110 | comment: %(The class of plain \(i.e. untyped\) literal values, as used in RIF and OWL 2).freeze, 111 | label: "PlainLiteral".freeze, 112 | :"rdfs:isDefinedBy" => %(rdf:).freeze, 113 | :"rdfs:seeAlso" => %(http://www.w3.org/TR/rdf-plain-literal/).freeze, 114 | subClassOf: "rdfs:Literal".freeze, 115 | type: "rdfs:Datatype".freeze 116 | term :XMLLiteral, 117 | comment: %(The datatype of XML literal values.).freeze, 118 | label: "XMLLiteral".freeze, 119 | :"rdfs:isDefinedBy" => %(rdf:).freeze, 120 | subClassOf: "rdfs:Literal".freeze, 121 | type: "rdfs:Datatype".freeze 122 | term :langString, 123 | comment: %(The datatype of language-tagged string values).freeze, 124 | label: "langString".freeze, 125 | :"rdfs:isDefinedBy" => %(rdf:).freeze, 126 | :"rdfs:seeAlso" => %(http://www.w3.org/TR/rdf11-concepts/#section-Graph-Literal).freeze, 127 | subClassOf: "rdfs:Literal".freeze, 128 | type: "rdfs:Datatype".freeze 129 | 130 | # Extra definitions 131 | term :"", 132 | :"dc11:description" => %(This is the RDF Schema for the RDF vocabulary terms in the RDF Namespace, defined in RDF 1.1 Concepts.).freeze, 133 | :"dc11:title" => %(The RDF Concepts Vocabulary \(RDF\)).freeze, 134 | type: "owl:Ontology".freeze 135 | term :Description, 136 | comment: %(RDF/XML node element).freeze, 137 | label: "Description".freeze 138 | term :ID, 139 | comment: %(RDF/XML attribute creating a Reification).freeze, 140 | label: "ID".freeze 141 | term :about, 142 | comment: %(RDF/XML attribute declaring subject).freeze, 143 | label: "about".freeze 144 | term :datatype, 145 | comment: %(RDF/XML literal datatype).freeze, 146 | label: "datatype".freeze 147 | term :li, 148 | comment: %(RDF/XML container membership list element).freeze, 149 | label: "li".freeze 150 | term :nil, 151 | comment: %(The empty list, with no items in it. If the rest of a list is nil then the list has no more items in it.).freeze, 152 | label: "nil".freeze, 153 | :"rdfs:isDefinedBy" => %(rdf:).freeze, 154 | type: "rdf:List".freeze 155 | term :nodeID, 156 | comment: %(RDF/XML Blank Node identifier).freeze, 157 | label: "nodeID".freeze 158 | term :parseType, 159 | comment: %(Parse type for RDF/XML, either Collection, Literal or Resource).freeze, 160 | label: "parseType".freeze 161 | term :resource, 162 | comment: %(RDF/XML attribute declaring object).freeze, 163 | label: "resource".freeze 164 | end 165 | end 166 | -------------------------------------------------------------------------------- /lib/rdf.rb: -------------------------------------------------------------------------------- 1 | require 'stringio' 2 | require 'bigdecimal' 3 | require 'date' 4 | require 'time' 5 | 6 | require 'rdf/version' 7 | 8 | module RDF 9 | # RDF mixins 10 | autoload :Countable, 'rdf/mixin/countable' 11 | autoload :Durable, 'rdf/mixin/durable' 12 | autoload :Enumerable, 'rdf/mixin/enumerable' 13 | autoload :Indexable, 'rdf/mixin/indexable' 14 | autoload :Mutable, 'rdf/mixin/mutable' 15 | autoload :Queryable, 'rdf/mixin/queryable' 16 | autoload :Readable, 'rdf/mixin/readable' 17 | autoload :TypeCheck, 'rdf/mixin/type_check' 18 | autoload :Transactable, 'rdf/mixin/transactable' 19 | autoload :Writable, 'rdf/mixin/writable' 20 | 21 | # RDF objects 22 | autoload :Graph, 'rdf/model/graph' 23 | autoload :IRI, 'rdf/model/uri' 24 | autoload :Literal, 'rdf/model/literal' 25 | autoload :Node, 'rdf/model/node' 26 | autoload :Resource, 'rdf/model/resource' 27 | autoload :Statement, 'rdf/model/statement' 28 | autoload :URI, 'rdf/model/uri' 29 | autoload :Value, 'rdf/model/value' 30 | autoload :Term, 'rdf/model/term' 31 | 32 | # RDF collections 33 | autoload :List, 'rdf/model/list' 34 | 35 | # RDF serialization 36 | autoload :Format, 'rdf/format' 37 | autoload :Reader, 'rdf/reader' 38 | autoload :ReaderError, 'rdf/reader' 39 | autoload :Writer, 'rdf/writer' 40 | autoload :WriterError, 'rdf/writer' 41 | 42 | # RDF serialization formats 43 | autoload :NTriples, 'rdf/ntriples' 44 | autoload :NQuads, 'rdf/nquads' 45 | 46 | # RDF storage 47 | autoload :Changeset, 'rdf/changeset' 48 | autoload :Dataset, 'rdf/model/dataset' 49 | autoload :Repository, 'rdf/repository' 50 | autoload :Transaction, 'rdf/transaction' 51 | 52 | # RDF querying 53 | autoload :Query, 'rdf/query' 54 | 55 | # RDF vocabularies 56 | autoload :Vocabulary, 'rdf/vocabulary' 57 | autoload :StrictVocabulary, 'rdf/vocabulary' 58 | VOCABS = Dir.glob(File.join(File.dirname(__FILE__), 'rdf', 'vocab', '*.rb')).map { |f| File.basename(f)[0...-(File.extname(f).size)].to_sym } rescue [] 59 | 60 | # Use const_missing instead of autoload to load most vocabularies so we can provide deprecation messages 61 | def self.const_missing(constant) 62 | if VOCABS.include?(constant.to_s.downcase.to_sym) 63 | require "rdf/vocab/#{constant.to_s.downcase}" 64 | const_get(constant) 65 | else 66 | super 67 | end 68 | end 69 | 70 | # Utilities 71 | autoload :Util, 'rdf/util' 72 | 73 | ## 74 | # Alias for `RDF::Resource.new`. 75 | # 76 | # @param (see RDF::Resource#initialize) 77 | # @return [RDF::Resource] 78 | def self.Resource(*args, &block) 79 | Resource.new(*args, &block) 80 | end 81 | 82 | ## 83 | # Alias for `RDF::Node.new`. 84 | # 85 | # @param (see RDF::Node#initialize) 86 | # @return [RDF::Node] 87 | def self.Node(*args, &block) 88 | Node.new(*args, &block) 89 | end 90 | 91 | ## 92 | # Alias for `RDF::URI.new`. 93 | # 94 | # @param (see RDF::URI#initialize) 95 | # @return [RDF::URI] 96 | def self.URI(*args, &block) 97 | case uri = args.first 98 | when RDF::URI then uri 99 | else case 100 | when uri.respond_to?(:to_uri) then uri.to_uri 101 | else URI.new(*args, &block) 102 | end 103 | end 104 | end 105 | 106 | ## 107 | # Alias for `RDF::Literal.new`. 108 | # 109 | # @param (see RDF::Literal#initialize) 110 | # @return [RDF::Literal] 111 | def self.Literal(*args, &block) 112 | case literal = args.first 113 | when RDF::Literal then literal 114 | else Literal.new(*args, &block) 115 | end 116 | end 117 | 118 | ## 119 | # Alias for `RDF::Graph.new`. 120 | # 121 | # @param (see RDF::Graph#initialize) 122 | # @return [RDF::Graph] 123 | def self.Graph(**options, &block) 124 | Graph.new(options, &block) 125 | end 126 | 127 | ## 128 | # @overload List() 129 | # @return [RDF::URI] returns the IRI for `rdf:List` 130 | # 131 | # @overload List(*args) 132 | # @param (see RDF::List#[]) 133 | # @return [RDF::List] 134 | # 135 | # @overload List(array) 136 | # @param [Array] array 137 | # @return [RDF::List] 138 | # 139 | # @overload List(list) 140 | # @param [RDF::List] list 141 | # @return [RDF::List] returns itself 142 | def self.List(*args) 143 | case 144 | when args.empty? 145 | RDF[:List] 146 | when args.length == 1 && args.first.is_a?(RDF::List) 147 | args.first 148 | when args.length == 1 && args.first.is_a?(Array) 149 | List[*args.first] 150 | else 151 | List[*args] 152 | end 153 | end 154 | 155 | ## 156 | # @overload Statement() 157 | # @return [RDF::URI] returns the IRI for `rdf:Statement` 158 | # 159 | # @overload Statement(options = {}) 160 | # @param [Hash{Symbol => Object}] options 161 | # @option options [RDF::Resource] :subject (nil) 162 | # @option options [RDF::URI] :predicate (nil) 163 | # @option options [RDF::Term] :object (nil) 164 | # @option options [RDF::Resource] :graph_name (nil) 165 | # Note, a graph_name MUST be an IRI or BNode. 166 | # @return [RDF::Statement] 167 | # 168 | # @overload Statement(subject, predicate, object, options = {}) 169 | # @param [RDF::Resource] subject 170 | # @param [RDF::URI] predicate 171 | # @param [RDF::Term] object 172 | # @param [Hash{Symbol => Object}] options 173 | # @option options [RDF::Resource] :graph_name (nil) 174 | # @return [RDF::Statement] 175 | # 176 | def self.Statement(*args) 177 | if args.empty? 178 | RDF[:Statement] 179 | else 180 | Statement.new(*args) 181 | end 182 | end 183 | 184 | ## 185 | # Alias for `RDF::Vocabulary.create`. 186 | # 187 | # @param (see RDF::Vocabulary#initialize) 188 | # @return [Class] 189 | def self.Vocabulary(uri) 190 | Vocabulary.create(uri) 191 | end 192 | 193 | ## 194 | # Alias for `RDF::StrictVocabulary.create`. 195 | # 196 | # @param (see RDF::Vocabulary#initialize) 197 | # @return [Class] 198 | def self.StrictVocabulary(prefix) 199 | StrictVocabulary.create(prefix) 200 | end 201 | 202 | ## 203 | # @return [#to_s] property 204 | # @return [URI] 205 | def self.[](property) 206 | property.to_s =~ %r{_\d+} ? RDF::URI("#{to_uri}#{property}") : RDF::RDFV[property] 207 | end 208 | 209 | ## 210 | # respond to module or RDFV 211 | def self.respond_to?(method, include_all = false) 212 | super || RDF::RDFV.respond_to?(method, include_all) 213 | end 214 | 215 | ## 216 | # Delegate other methods to RDF::RDFV 217 | def self.method_missing(property, *args, &block) 218 | if args.empty? 219 | # Special-case rdf:_n for all integers 220 | property.to_s =~ %r{_\d+} ? RDF::URI("#{to_uri}#{property}") : RDF::RDFV.send(property) 221 | else 222 | super 223 | end 224 | end 225 | end 226 | --------------------------------------------------------------------------------