rdf 2.0.0.beta1 → 2.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d6b5ce1d8969fb6463efd82a7fe9826589bac799
4
- data.tar.gz: e0c0066693ddc81d5089bf63f7502527750d1380
3
+ metadata.gz: 93fe92b9d0141e5c9537bdd33df4ac48b16dd614
4
+ data.tar.gz: 48c1f403490e61487a6d66cc2b9434e1199ddd2f
5
5
  SHA512:
6
- metadata.gz: f866ac2a48e98e154bd786f6e0c30f5298e70c01de47847c660fded022f14ef58f19b0a727e20a53704d0d46f610cae4f53a08b5b2b94dc5f82d85da006e9d0b
7
- data.tar.gz: ccede9fff108419d7d0df702df4dd8e8a24bd53be1e0aa0bd1a1986158ff5dcdbfd7bb824eb36cbe1dc0ea41243732bb49f0aec5dbbf869721595911e7f39709
6
+ metadata.gz: 766b89bac9456158ba2e3bc4640e5b3e4c361e4a20ac72737d259146223f224523f059f302331edb9a58ade726a03fc3f45e0672a404e47f73b9044529548aaa
7
+ data.tar.gz: e5327946f1f6b1d41e05b262b8fb64a4efeec329aafb14107e2e46a92c15b0e7cd217fded4e4339f83f034b6f87a690d1c387ebe21f8923e731e2006e4fc4201
data/README.md CHANGED
@@ -79,12 +79,13 @@ Notably, {RDF::Queryable#query} and {RDF::Query#execute} are now completely symm
79
79
  When installed, RDF.rb includes a `rdf` shell script which acts as a wrapper to perform a number of different
80
80
  operations on RDF files using available readers and writers.
81
81
 
82
- * `serialize`: Parse an RDF input and re-serializing to [N-Triples][] or another available format using `--output-format` option.
83
82
  * `count`: Parse and RDF input and count the number of statements.
84
- * `subjects`: Returns unique subjects from parsed input.
85
- * `objects`: Returns unique objects from parsed input.
86
83
  * `predicates`: Returns unique objects from parsed input.
84
+ * `objects`: Returns unique objects from parsed input.
85
+ * `serialize`: Parse an RDF input and re-serializing to [N-Triples][] or another available format using `--output-format` option.
86
+ * `subjects`: Returns unique subjects from parsed input.
87
87
 
88
+ The `serialize` command can also be used to serialize as a vocabulary
88
89
  ## Examples
89
90
 
90
91
  require 'rdf'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.0.beta1
1
+ 2.0.0.beta2
data/bin/rdf CHANGED
@@ -11,8 +11,19 @@ options = RDF::CLI.options do
11
11
  self.on('-V', '--version', 'Display the RDF.rb version and exit.') do
12
12
  puts RDF::VERSION; exit
13
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
14
25
  end
15
26
 
16
27
  abort options.banner if ARGV.empty? && !options.options[:evaluate]
17
28
 
18
- RDF::CLI.exec_command(command = ARGV.shift, ARGV, options.options)
29
+ RDF::CLI.exec(ARGV, options.options)
data/lib/rdf/cli.rb CHANGED
@@ -1,24 +1,19 @@
1
1
  require 'rdf'
2
2
  require 'rdf/ntriples'
3
3
  require 'rdf/nquads'
4
+ require 'rdf/vocab/writer'
4
5
  require 'logger'
5
6
  require 'optparse'
6
7
  begin
7
- gem 'linkeddata'
8
8
  require 'linkeddata'
9
9
  rescue LoadError
10
10
  # Silently load without linkeddata, but try some others
11
- %w(rdfa rdfxml turtle).each do |ser|
11
+ %w(reasoner rdfa rdfxml turtle json/ld ld/patch).each do |ser|
12
12
  begin
13
- require "rdf/#{ser}"
13
+ require ser.include?('/') ? ser : "rdf/#{ser}"
14
14
  rescue LoadError
15
15
  end
16
16
  end
17
-
18
- begin
19
- require 'json/ld'
20
- rescue LoadError
21
- end
22
17
  end
23
18
 
24
19
  class OptionParser
@@ -29,6 +24,10 @@ end
29
24
  module RDF
30
25
  # Individual formats can modify options by updating {Reader.options} or {Writer.options}. Format-specific commands are taken from {Format.cli_commands} for each loaded format, which returns an array of lambdas taking arguments and options.
31
26
  #
27
+ # Other than `help`, all commands parse an input file.
28
+ #
29
+ # Multiple commands may be added in sequence to execute a pipeline.
30
+ #
32
31
  # @example Creating Reader-specific options:
33
32
  # class Reader
34
33
  # def self.options
@@ -38,7 +37,11 @@ module RDF
38
37
  # datatype: TrueClass,
39
38
  # on: ["--canonicalize"],
40
39
  # description: "Canonicalize input/output.") {true},
41
- # ...
40
+ # RDF::CLI::Option.new(
41
+ # symbol: :uri,
42
+ # datatype: RDF::URI,
43
+ # on: ["--uri STRING"],
44
+ # description: "URI.") {|v| RDF::URI(v)},
42
45
  # ]
43
46
  # end
44
47
  #
@@ -46,18 +49,27 @@ module RDF
46
49
  # class Format
47
50
  # def self.cli_commands
48
51
  # {
49
- # count: ->(argv, opts) do
50
- # count = 0
51
- # RDF::CLI.parse(argv, opts) do |reader|
52
- # reader.each_statement do |statement|
53
- # count += 1
54
- # end
55
- # end
56
- # $stdout.puts "Parsed #{count} statements"
57
- # end,
52
+ # count: {
53
+ # description: "",
54
+ # parse: true,
55
+ # lambda: ->(argv, opts) {}
56
+ # },
58
57
  # }
59
58
  # end
60
59
  #
60
+ # @example Adding a command manually
61
+ # class MyCommand
62
+ # RDF::CLI.add_command(:count, description: "Count statements") do |argv, opts|
63
+ # count = 0
64
+ # RDF::CLI.parse(argv, opts) do |reader|
65
+ # reader.each_statement do |statement|
66
+ # count += 1
67
+ # end
68
+ # end
69
+ # $stdout.puts "Parsed #{count} statements"
70
+ # end
71
+ # end
72
+ #
61
73
  # Format-specific commands should verify that the reader and/or output format are appropriate for the command.
62
74
  class CLI
63
75
 
@@ -107,78 +119,102 @@ module RDF
107
119
 
108
120
  # @private
109
121
  COMMANDS = {
110
- count: ->(argv, opts) do
111
- start = Time.new
112
- count = 0
113
- self.parse(argv, opts) do |reader|
114
- reader.each_statement do |statement|
115
- count += 1
122
+ count: {
123
+ description: "Count statements in parsed input",
124
+ parse: false,
125
+ help: "count [options] [args...]\nreturns number of parsed statements",
126
+ lambda: ->(argv, opts) do
127
+ unless repository.count > 0
128
+ start = Time.new
129
+ count = 0
130
+ self.parse(argv, opts) do |reader|
131
+ reader.each_statement do |statement|
132
+ count += 1
133
+ end
134
+ end
135
+ secs = Time.new - start
136
+ $stdout.puts "Parsed #{count} statements with #{@readers.join(', ')} in #{secs} seconds @ #{count/secs} statements/second."
116
137
  end
117
138
  end
118
- secs = Time.new - start
119
- $stdout.puts "Parsed #{count} statements with #{@readers.join(', ')} in #{secs} seconds @ #{count/secs} statements/second."
120
- end,
121
- help: ->(argv, opts) do
122
- self.usage(self.options)
123
- end,
124
- lenghts: ->(argv, opts) do
125
- self.parse(argv, opts) do |reader|
126
- reader.each_statement do |statement|
139
+ },
140
+ help: {
141
+ description: "This message",
142
+ parse: false,
143
+ lambda: ->(argv, opts) {self.usage(self.options)}
144
+ },
145
+ lengths: {
146
+ description: "Lengths of each parsed statement",
147
+ parse: true,
148
+ help: "lengths [options] [args...]\nreturns statement lengths",
149
+ lambda: ->(argv, opts) do
150
+ repository.each_statement do |statement|
127
151
  $stdout.puts statement.to_s.size
128
152
  end
129
153
  end
130
- end,
131
- objects: ->(argv, opts) do
132
- $stdout.set_encoding(Encoding::UTF_8) if RUBY_PLATFORM == "java"
133
- self.parse(argv, opts) do |reader|
134
- reader.each_statement do |statement|
135
- $stdout.puts statement.object.to_ntriples
154
+ },
155
+ objects: {
156
+ description: "Serialize each parsed object to N-Triples",
157
+ parse: true,
158
+ help: "objects [options] [args...]\nreturns unique objects",
159
+ lambda: ->(argv, opts) do
160
+ $stdout.puts "Objects"
161
+ repository.each_object do |object|
162
+ $stdout.puts object.to_ntriples
136
163
  end
137
164
  end
138
- end,
139
- predicates: ->(argv, opts) do
140
- $stdout.set_encoding(Encoding::UTF_8) if RUBY_PLATFORM == "java"
141
- self.parse(argv, opts) do |reader|
142
- reader.each_statement do |statement|
143
- $stdout.puts statement.predicate.to_ntriples
165
+ },
166
+ predicates: {
167
+ description: "Serialize each parsed predicate to N-Triples",
168
+ parse: true,
169
+ help: "predicates [options] [args...]\nreturns unique predicates",
170
+ lambda: ->(argv, opts) do
171
+ $stdout.puts "Predicates"
172
+ repository.each_predicate do |predicate|
173
+ $stdout.puts predicate.to_ntriples
144
174
  end
145
175
  end
146
- end,
147
- serialize: ->(argv, opts) do
148
- writer_class = RDF::Writer.for(opts[:output_format]) || RDF::NTriples::Writer
149
- out = opts[:output] || $stdout
150
- out.set_encoding(Encoding::UTF_8) if out.respond_to?(:set_encoding) && RUBY_PLATFORM == "java"
151
- opts = opts.merge(prefixes: {})
152
- writer_opts = opts.merge(standard_prefixes: true)
153
- self.parse(argv, opts) do |reader|
176
+ },
177
+ serialize: {
178
+ description: "Serialize each parsed statement to N-Triples, or the specified output format",
179
+ parse: true,
180
+ help: "serialize [options] [args...]\nserialize output using specified format (or n-triples if not specified)",
181
+ lambda: ->(argv, opts) do
182
+ writer_class = RDF::Writer.for(opts[:output_format]) || RDF::NTriples::Writer
183
+ out = opts[:output] || $stdout
184
+ opts = opts.merge(prefixes: {})
185
+ writer_opts = opts.merge(standard_prefixes: true)
154
186
  writer_class.new(out, writer_opts) do |writer|
155
- writer << reader
187
+ writer << repository
156
188
  end
157
189
  end
158
- end,
159
- subjects: ->(argv, opts) do
160
- $stdout.set_encoding(Encoding::UTF_8) if RUBY_PLATFORM == "java"
161
- self.parse(argv, opts) do |reader|
162
- reader.each_statement do |statement|
163
- $stdout.puts statement.subject.to_ntriples
190
+ },
191
+ subjects: {
192
+ description: "Serialize each parsed subject to N-Triples",
193
+ parse: true,
194
+ help: "subjects [options] [args...]\nreturns unique subjects",
195
+ lambda: ->(argv, opts) do
196
+ $stdout.puts "Subjects"
197
+ repository.each_subject do |subject|
198
+ $stdout.puts subject.to_ntriples
164
199
  end
165
200
  end
166
- end,
167
- validate: ->(argv, opts) do
168
- start = Time.new
169
- count = 0
170
- valid = true
171
- self.parse(argv, opts) do |reader|
172
- reader.each_statement do |statement|
173
- count += 1
174
- valid = false if statement.invalid?
175
- end
201
+ },
202
+ validate: {
203
+ description: "Validate parsed input",
204
+ parse: true,
205
+ help: "validate [options] [args...]\nvalidates parsed input (may also be used with --validate)",
206
+ lambda: ->(argv, opts) do
207
+ $stdout.puts "Input is " + (repository.valid? ? "" :"in") + "valid"
176
208
  end
177
- secs = Time.new - start
178
- $stdout.puts "Validated #{count} statements with #{@readers.join(', ')} in #{secs} seconds @ #{count/secs} statements/second."
179
- end
209
+ }
180
210
  }
181
211
 
212
+ class << self
213
+ # Repository containing parsed statements
214
+ # @return [RDF::Repository]
215
+ attr_accessor :repository
216
+ end
217
+
182
218
  ##
183
219
  # @return [String]
184
220
  def self.basename() File.basename($0) end
@@ -226,7 +262,7 @@ module RDF
226
262
  else options.instance_eval(&block)
227
263
  end
228
264
  end
229
- options.banner ||= "Usage: #{self.basename} [options] command [args...]"
265
+ options.banner = "Usage: #{self.basename} command+ [options] [args...]"
230
266
 
231
267
  options.on('-d', '--debug', 'Enable debug output for troubleshooting.') do
232
268
  opts[:logger].level = Logger::DEBUG
@@ -276,6 +312,7 @@ module RDF
276
312
 
277
313
  options.on_tail("-h", "--help", "Show this message") do
278
314
  self.usage(options)
315
+ exit(0)
279
316
  end
280
317
 
281
318
  begin
@@ -289,7 +326,8 @@ module RDF
289
326
 
290
327
  ##
291
328
  # Output usage message
292
- def self.usage(options)
329
+ def self.usage(options, banner: nil)
330
+ options.banner = banner if banner
293
331
  $stdout.puts options
294
332
  $stdout.puts "Note: available commands and options may be different depending on selected --input-format and/or --output-format."
295
333
  $stdout.puts "Available commands:\n\t#{self.commands.join("\n\t")}"
@@ -297,15 +335,47 @@ module RDF
297
335
  end
298
336
 
299
337
  ##
300
- # @param [#to_sym] command
338
+ # Execute one or more commands, parsing input as necessary
339
+ #
301
340
  # @param [Array<String>] args
302
341
  # @return [Boolean]
303
- def self.exec_command(command, args, options = {})
304
- unless commands.include?(command.to_s)
305
- abort "unknown command `#{command}'"
342
+ def self.exec(args, options = {})
343
+ out = options[:output] || $stdout
344
+ out.set_encoding(Encoding::UTF_8) if out.respond_to?(:set_encoding) && RUBY_PLATFORM == "java"
345
+ cmds, args = args.partition {|e| commands.include?(e.to_s)}
346
+
347
+ if cmds.empty?
348
+ usage(options)
349
+ abort "No command given"
350
+ end
351
+
352
+ if cmds.first == 'help'
353
+ on_cmd = cmds[1]
354
+ if on_cmd && COMMANDS.fetch(on_cmd.to_sym, {})[:help]
355
+ usage(self.options, banner: "Usage: #{self.basename.split('/').last} #{COMMANDS[on_cmd.to_sym][:help]}")
356
+ else
357
+ usage(self.options)
358
+ end
359
+ return
360
+ end
361
+
362
+ @repository = RDF::Repository.new
363
+
364
+ # Parse input files if any command requires it
365
+ if cmds.any? {|c| COMMANDS[c.to_sym][:parse]}
366
+ start = Time.new
367
+ count = 0
368
+ self.parse(args, options) do |reader|
369
+ @repository << reader
370
+ end
371
+ secs = Time.new - start
372
+ $stdout.puts "Parsed #{repository.count} statements with #{@readers.join(', ')} in #{secs} seconds @ #{count/secs} statements/second."
306
373
  end
307
374
 
308
- COMMANDS[command.to_sym].call(args, options)
375
+ # Run each command in sequence
376
+ cmds.each do |command|
377
+ COMMANDS[command.to_sym][:lambda].call(args, options)
378
+ end
309
379
  rescue ArgumentError => e
310
380
  abort e.message
311
381
  end
@@ -316,8 +386,9 @@ module RDF
316
386
  # First, load commands from other formats
317
387
  unless @commands_loaded
318
388
  RDF::Format.each do |format|
319
- format.cli_commands.each do |command, lambda|
320
- COMMANDS[command] ||= lambda
389
+ format.cli_commands.each do |command, options|
390
+ options = {lambda: options} unless options.is_a?(Hash)
391
+ add_command(command, options)
321
392
  end
322
393
  end
323
394
  @commands_loaded = true
@@ -325,11 +396,29 @@ module RDF
325
396
  COMMANDS.keys.map(&:to_s).sort
326
397
  end
327
398
 
399
+ ##
400
+ # Add a command.
401
+ #
402
+ # @param [#to_sym] command
403
+ # @param [Hash{Symbol => String}] options
404
+ # @option options [String] description
405
+ # @option options [String] help string to display for help
406
+ # @option options [Boolean] parse parse input files in to Repository, or not.
407
+ # @option options [Array<RDF::CLI::Option>] options specific to this command
408
+ # @yield argv, opts
409
+ # @yieldparam [Array<String>] argv
410
+ # @yieldparam [Hash] opts
411
+ # @yieldreturn [void]
412
+ def self.add_command(command, options = {}, &block)
413
+ options[:lambda] = block if block_given?
414
+ COMMANDS[command.to_sym] ||= options
415
+ end
416
+
328
417
  ##
329
418
  # @return [Array<String>] list of available formats
330
419
  def self.formats(reader: false, writer: false)
331
- f = RDF::Format.each.
332
- select {|f| (reader ? f.reader : (writer ? f.writer : true))}.
420
+ f = RDF::Format.sort_by(&:to_sym).each.
421
+ select {|f| (reader ? f.reader : (writer ? f.writer : (f.reader || f.writer)))}.
333
422
  inject({}) do |memo, reader|
334
423
  memo.merge(reader.to_sym => reader.name)
335
424
  end
data/lib/rdf/format.rb CHANGED
@@ -361,7 +361,7 @@ module RDF
361
361
 
362
362
  ##
363
363
  # Hash of CLI commands appropriate for this format
364
- # @return [Hash{Symbol => Lambda(Array, Hash)}]
364
+ # @return [Hash{Symbol => {description: String, lambda: Lambda(Array, Hash)}}]
365
365
  def self.cli_commands
366
366
  {}
367
367
  end
@@ -761,6 +761,15 @@ module RDF
761
761
  end
762
762
  end
763
763
 
764
+ ##
765
+ # @note this instantiates an writer; it could probably be done more
766
+ # efficiently by refactoring `RDF::Reader` and/or `RDF::Format` to expose
767
+ # a list of valid format symbols.
768
+ def respond_to_missing?(name, include_private = false)
769
+ return RDF::Writer.for(name.to_s[3..-1].to_sym) if name.to_s[0,3] == 'to_'
770
+ super
771
+ end
772
+
764
773
  ##
765
774
  # @private
766
775
  # @param [Symbol, #to_sym] method
@@ -31,14 +31,11 @@ module RDF
31
31
  include Queryable
32
32
  include Enumerable
33
33
 
34
- def method_missing(method, *args)
35
- self.to_a if method.to_sym == :to_ary
36
- end
37
-
38
34
  # Make sure returned arrays are also queryable
39
35
  def to_a
40
36
  return super.to_a.extend(RDF::Queryable, RDF::Enumerable)
41
37
  end
38
+ alias_method :to_ary, :to_a
42
39
  end
43
40
  end
44
- end
41
+ end
@@ -260,6 +260,16 @@ module RDF
260
260
  super
261
261
  end
262
262
  end
263
+
264
+ ##
265
+ # @note this instantiates an entire reader; it could probably be done more
266
+ # efficiently by refactoring `RDF::Reader` and/or `RDF::Format` to expose
267
+ # a list of valid format symbols.
268
+ def respond_to_missing?(name, include_private = false)
269
+ return RDF::Reader.for(name.to_s[5..-1].to_sym) if name.to_s[0,5] == 'from_'
270
+ super
271
+ end
272
+
263
273
  protected
264
274
 
265
275
  ##
@@ -20,22 +20,37 @@ module RDF
20
20
  # Executes the given block in a transaction.
21
21
  #
22
22
  # @example running a transaction
23
- # repository.transaction do |tx|
23
+ # repository.transaction(mutable: true) do |tx|
24
24
  # tx.insert [RDF::URI("http://rubygems.org/gems/rdf"), RDF::RDFS.label, "RDF.rb"]
25
25
  # end
26
26
  #
27
27
  # Raising an error within the transaction block causes automatic rollback.
28
28
  #
29
- # @param mutable [Boolean]
30
- # allows changes to the transaction, otherwise it is a read-only snapshot of the underlying repository.
31
- # @yield [tx]
32
- # @yieldparam [RDF::Transaction] tx
33
- # @yieldreturn [void] ignored
34
- # @return [self]
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
+ #
35
48
  # @see RDF::Transaction
36
49
  # @since 0.3.0
37
50
  def transaction(mutable: false, &block)
38
51
  tx = begin_transaction(mutable: mutable)
52
+ return tx unless block_given?
53
+
39
54
  begin
40
55
  case block.arity
41
56
  when 1 then block.call(tx)
@@ -27,7 +27,7 @@ module RDF; class Literal
27
27
  when 'INF' then 1/0.0
28
28
  when '-INF' then -1/0.0
29
29
  when 'NaN' then 0/0.0
30
- else Float(value) rescue nil
30
+ else Float(value.sub(/\.[eE]/, '.0E')) rescue nil
31
31
  end
32
32
  when value.is_a?(::Float) then value
33
33
  when value.respond_to?(:to_f) then value.to_f
@@ -162,13 +162,13 @@ module RDF
162
162
  # @see http://www.w3.org/TR/rdf11-concepts/#section-Graph-Literal
163
163
  # @see http://www.w3.org/TR/rdf11-concepts/#section-Datatypes
164
164
  def initialize(value, options = {})
165
- @object = value
165
+ @object = value.freeze
166
166
  @string = options[:lexical] if options[:lexical]
167
167
  @string = value if !defined?(@string) && value.is_a?(String)
168
- @string = @string.encode(Encoding::UTF_8) if @string
168
+ @string = @string.encode(Encoding::UTF_8).freeze if @string
169
169
  @object = @string if @string && @object.is_a?(String)
170
170
  @language = options[:language].to_s.downcase.to_sym if options[:language]
171
- @datatype = RDF::URI(options[:datatype]) if options[:datatype]
171
+ @datatype = RDF::URI(options[:datatype]).freeze if options[:datatype]
172
172
  @datatype ||= self.class.const_get(:DATATYPE) if self.class.const_defined?(:DATATYPE)
173
173
  @datatype ||= @language ? RDF.langString : RDF::XSD.string
174
174
  raise ArgumentError, "datatype of rdf:langString requires a language" if !@language && @datatype == RDF::langString
@@ -425,7 +425,8 @@ module RDF
425
425
  gsub("\n", '\\n').
426
426
  gsub("\r", '\\r').
427
427
  gsub("\f", '\\f').
428
- gsub('"', '\\"')
428
+ gsub('"', '\\"').
429
+ freeze
429
430
  end
430
431
 
431
432
  ##
@@ -433,7 +434,7 @@ module RDF
433
434
  #
434
435
  # @return [String]
435
436
  def to_s
436
- @object.to_s
437
+ @object.to_s.freeze
437
438
  end
438
439
 
439
440
  ##
@@ -442,7 +443,7 @@ module RDF
442
443
  # @return [String]
443
444
  # @since 1.1.6
444
445
  def humanize(lang = :en)
445
- to_s
446
+ to_s.freeze
446
447
  end
447
448
 
448
449
  ##
@@ -55,7 +55,7 @@ module RDF
55
55
  # @return [RDF::Node]
56
56
  # @since 0.2.0
57
57
  def self.intern(id)
58
- (cache[id = id.to_s] ||= self.new(id)).freeze
58
+ (cache[(id = id.to_s).to_sym] ||= self.new(id)).freeze
59
59
  end
60
60
 
61
61
  ##
@@ -82,7 +82,7 @@ module RDF
82
82
  # @param [#to_s] id
83
83
  def initialize(id = nil)
84
84
  id = nil if id.to_s.empty?
85
- @id = (id || "g#{__id__.to_i.abs}").to_s
85
+ @id = (id || "g#{__id__.to_i.abs}").to_s.freeze
86
86
  end
87
87
 
88
88
  ##
@@ -56,14 +56,6 @@ module RDF
56
56
  super
57
57
  end
58
58
 
59
- ##
60
- # Returns a base representation of `self`.
61
- #
62
- # @return [RDF::Value]
63
- def to_base
64
- self
65
- end
66
-
67
59
  ##
68
60
  # Returns `true` if `self` is a {RDF::Term}.
69
61
  #
@@ -85,7 +77,7 @@ module RDF
85
77
  #
86
78
  # @return [Sring]
87
79
  def to_base
88
- RDF::NTriples.serialize(self)
80
+ RDF::NTriples.serialize(self).freeze
89
81
  end
90
82
 
91
83
  ##
data/lib/rdf/model/uri.rb CHANGED
@@ -143,7 +143,7 @@ module RDF
143
143
  # @return [RDF::URI] an immutable, frozen URI object
144
144
  def self.intern(*args)
145
145
  str = args.first
146
- (cache[str = str.to_s] ||= self.new(*args)).freeze
146
+ (cache[(str = str.to_s).to_sym] ||= self.new(*args)).freeze
147
147
  end
148
148
 
149
149
  ##
@@ -229,6 +229,7 @@ module RDF
229
229
  if @value.encoding != Encoding::UTF_8
230
230
  @value = @value.dup if @value.frozen?
231
231
  @value.force_encoding(Encoding::UTF_8)
232
+ @value.freeze
232
233
  end
233
234
  else
234
235
  %w(
@@ -815,7 +816,7 @@ module RDF
815
816
  path,
816
817
  ("?#{query}" if query),
817
818
  ("##{fragment}" if fragment)
818
- ].compact.join("")
819
+ ].compact.join("").freeze
819
820
  end
820
821
 
821
822
  ##
@@ -273,5 +273,11 @@ class RDF::Query
273
273
  super # raises NoMethodError
274
274
  end
275
275
  end
276
+
277
+ ##
278
+ # @return [Boolean]
279
+ def respond_to_missing?(name, include_private = false)
280
+ @bindings.has_key?(name.to_sym) || super
281
+ end
276
282
  end # Solution
277
283
  end # RDF::Query
@@ -16,7 +16,13 @@ module RDF; module Util
16
16
  def logger(options = {})
17
17
  logger = options.fetch(:logger, @logger)
18
18
  logger = @options[:logger] if logger.nil? && @options
19
- logger = (@options || options)[:logger] = $stderr if logger.nil?
19
+ if logger.nil?
20
+ # Unless otherwise specified, use $stderr
21
+ logger = (@options || options)[:logger] = $stderr
22
+
23
+ # Reset log_statistics so that it's not inherited across different instances
24
+ logger.log_statistics.clear if logger.respond_to?(:log_statistics)
25
+ end
20
26
  logger = (@options || options)[:logger] = ::Logger.new(::File.open(::File::NULL, "w")) unless logger # Incase false was used, which is frozen
21
27
  logger.extend(LoggerBehavior) unless logger.is_a?(LoggerBehavior)
22
28
  logger
@@ -267,6 +273,13 @@ module RDF; module Util
267
273
  super
268
274
  end
269
275
  end
276
+
277
+ def respond_to_missing?(name, include_private = false)
278
+ return true if
279
+ [:fatal, :error, :warn, :info, :debug, :level, :sev_threshold]
280
+ .include?(name.to_sym)
281
+ super
282
+ end
270
283
  end
271
284
  end # Logger
272
285
  end; end # RDF::Util
@@ -0,0 +1,176 @@
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 vocab-fetch from #{source}
96
+ require 'rdf'
97
+ module #{module_name}
98
+ # @!parse
99
+ # # Vocabulary for <#{base_uri}>
100
+ # class #{vocab.name} < RDF::#{"Strict" if strict}Vocabulary
101
+ # end
102
+ class #{vocab.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
@@ -263,27 +263,24 @@ module RDF
263
263
  end
264
264
 
265
265
  ##
266
- # Load a vocabulary, optionally from a separate location.
266
+ # Load an RDFS vocabulary, optionally from a separate location.
267
267
  #
268
268
  # @param [URI, #to_s] url
269
269
  # @param [String] class_name
270
270
  # The class_name associated with the vocabulary, used for creating the class name of the vocabulary. This will create a new class named with a top-level constant based on `class_name`.
271
- # @param [URI, #to_s] location
272
- # Location from which to load the vocabulary, if not from `uri`.
271
+ # @param [RDF::Queryable, URI, #to_s] location
272
+ # Location from which to load the vocabulary, or Queryable containing already loaded vocabulary triples, if not from `uri`.
273
273
  # @param [Array<Symbol>, Hash{Symbol => Hash}] extra
274
274
  # Extra terms to add to the vocabulary. In the first form, it is an array of symbols, for which terms are created. In the second, it is a Hash mapping symbols to property attributes, as described in {RDF::Vocabulary.property}.
275
275
  # @param [String] patch
276
276
  # A patch to run on the graph after loading. Requires the `ld-patch` gem to be available.
277
277
  # @return [RDF::Vocabulary] the loaded vocabulary
278
+ # @deprecated Use Vocabulary.from_graph
278
279
  def load(url, class_name: nil, location: nil, extra: nil, patch: nil)
280
+ warn "[DEPRECATION] Vocabulary.load is deprecated, use Vocabulary.from_graph instead. Called from #{Gem.location_of_caller.join(':')}"
279
281
  source = location || url
280
- vocab = if class_name
281
- Object.const_set(class_name, Class.new(self.create(url)))
282
- else
283
- Class.new(self.create(url))
284
- end
285
282
 
286
- graph = RDF::Repository.load(source)
283
+ graph = source.is_a?(RDF::Queryable) ? source : RDF::Repository.load(source)
287
284
 
288
285
  if patch
289
286
  begin
@@ -294,6 +291,27 @@ module RDF
294
291
  raise ArgumentError, "patching vocabulary requires the ld-patch gem"
295
292
  end
296
293
  end
294
+
295
+ from_graph(graph, url: url, class_name: nil, extra: extra)
296
+ end
297
+
298
+ ##
299
+ # Create a vocabulary from a graph or enumerable
300
+ #
301
+ # @param [RDF::Enumerable] graph
302
+ # @param [URI, #to_s] url
303
+ # @param [String] class_name
304
+ # The class_name associated with the vocabulary, used for creating the class name of the vocabulary. This will create a new class named with a top-level constant based on `class_name`.
305
+ # @param [Array<Symbol>, Hash{Symbol => Hash}] extra
306
+ # Extra terms to add to the vocabulary. In the first form, it is an array of symbols, for which terms are created. In the second, it is a Hash mapping symbols to property attributes, as described in {RDF::Vocabulary.property}.
307
+ # @return [RDF::Vocabulary] the loaded vocabulary
308
+ def from_graph(graph, url: nil, class_name: nil, extra: nil)
309
+ vocab = if class_name
310
+ Object.const_set(class_name, Class.new(self.create(url)))
311
+ else
312
+ Class.new(self.create(url))
313
+ end
314
+
297
315
  term_defs = {}
298
316
  graph.each do |statement|
299
317
  next unless statement.subject.uri? && statement.subject.start_with?(url)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rdf
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.beta1
4
+ version: 2.0.0.beta2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arto Bendiken
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2016-02-22 00:00:00.000000000 Z
13
+ date: 2016-04-06 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: link_header
@@ -38,14 +38,14 @@ dependencies:
38
38
  requirements:
39
39
  - - "~>"
40
40
  - !ruby/object:Gem::Version
41
- version: '2.0'
41
+ version: '3.0'
42
42
  type: :runtime
43
43
  prerelease: false
44
44
  version_requirements: !ruby/object:Gem::Requirement
45
45
  requirements:
46
46
  - - "~>"
47
47
  - !ruby/object:Gem::Version
48
- version: '2.0'
48
+ version: '3.0'
49
49
  - !ruby/object:Gem::Dependency
50
50
  name: rdf-spec
51
51
  requirement: !ruby/object:Gem::Requirement
@@ -223,7 +223,6 @@ files:
223
223
  - lib/rdf.rb
224
224
  - lib/rdf/changeset.rb
225
225
  - lib/rdf/cli.rb
226
- - lib/rdf/cli/vocab-loader.rb
227
226
  - lib/rdf/format.rb
228
227
  - lib/rdf/mixin/countable.rb
229
228
  - lib/rdf/mixin/durable.rb
@@ -279,6 +278,7 @@ files:
279
278
  - lib/rdf/vocab/owl.rb
280
279
  - lib/rdf/vocab/rdfs.rb
281
280
  - lib/rdf/vocab/rdfv.rb
281
+ - lib/rdf/vocab/writer.rb
282
282
  - lib/rdf/vocab/xsd.rb
283
283
  - lib/rdf/vocabulary.rb
284
284
  - lib/rdf/writer.rb
@@ -302,7 +302,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
302
302
  version: 1.3.1
303
303
  requirements: []
304
304
  rubyforge_project: rdf
305
- rubygems_version: 2.5.1
305
+ rubygems_version: 2.4.8
306
306
  signing_key:
307
307
  specification_version: 4
308
308
  summary: A Ruby library for working with Resource Description Framework (RDF) data.
@@ -1,192 +0,0 @@
1
- require 'rdf'
2
- require 'linkeddata'
3
- require 'optparse'
4
-
5
- module RDF
6
- # Utility class to load RDF vocabularies from files or their canonical
7
- # definitions and emit either a class file for RDF::StrictVocabulary,
8
- # RDF::Vocabulary or the raw RDF vocabulary
9
- class VocabularyLoader
10
- def initialize(class_name = nil)
11
- @class_name = class_name
12
- @module_name = "RDF"
13
- @output = $stdout
14
- @output_class_file = true
15
- @uri = nil
16
- @strict = true
17
- @extra = []
18
- end
19
- attr_accessor :class_name, :module_name, :output, :output_class_file
20
- attr_reader :uri, :source
21
-
22
- # Set the URI for the loaded RDF file - by default, sets the source as
23
- # well
24
- def uri=(uri)
25
- @uri = uri
26
- @source ||= uri
27
- end
28
-
29
- # Set the source for the loaded RDF - by default, sets the URI as well
30
- def source=(uri)
31
- @source = uri
32
- @uri ||= uri
33
- end
34
-
35
- # Set output
36
- def output=(out)
37
- @output = out
38
- end
39
-
40
- # Extra properties to define
41
- def extra=(extra)
42
- @extra = extra
43
- end
44
-
45
- # patch to run over loaded vocabulary
46
- def patch=(patch)
47
- @patch = patch
48
- end
49
-
50
- # Use StrictVocabulary or Vocabulary
51
- def strict=(strict)
52
- @strict = strict
53
- end
54
-
55
- # Parses arguments, for use in a command line tool
56
- def parse_options(argv)
57
- opti = OptionParser.new
58
- opti.banner = "Usage: #{File.basename($0)} [options] [uri]\nFetch an RDFS file and produce an RDF::StrictVocabulary with it.\n\n"
59
-
60
- opti.on("--uri URI", "The URI for the fetched RDF vocabulary") do |uri|
61
- self.uri = uri
62
- end
63
-
64
- opti.on("--source SOURCE", "The source URI or file for the vocabulary") do |uri|
65
- self.source = uri
66
- end
67
-
68
- opti.on("--class-name NAME", "The class name for the output StrictVocabulary subclass") do |name|
69
- self.class_name = name
70
- end
71
-
72
- opti.on("--module-name NAME", "The module name for the output StrictVocabulary subclass") do |name|
73
- self.module_name = name
74
- end
75
-
76
- opti.on("--raw", "Don't output an output file - just the RDF") do
77
- @output_class_file = false
78
- end
79
-
80
- opti.on_tail("--help", "This help text") do
81
- $stdout.puts opti
82
- exit 1
83
- end
84
-
85
- others = opti.parse(argv)
86
-
87
- if @class_name.nil? and @output_class_file
88
- raise "Class name (--class-name) is required!"
89
- end
90
-
91
- uri ||= others.first
92
- end
93
-
94
- # Parse command line arguments and run the load-and-emit process
95
- def go(argv)
96
- parse_options(argv)
97
- run
98
-
99
- if @output != $stdout
100
- @output.close
101
- end
102
- end
103
-
104
- ##
105
- # Turn a node definition into a property/term expression
106
- def from_node(name, attributes, term_type)
107
- op = term_type == :property ? "property" : "term"
108
-
109
- components = [" #{op} #{name.to_sym.inspect}"]
110
- attributes.keys.sort_by(&:to_s).each do |key|
111
- next if key == :vocab
112
- value = Array(attributes[key])
113
- component = key.is_a?(Symbol) ? "#{key}: " : ":#{key.inspect} => "
114
- value = value.first if value.length == 1
115
- component << if value.is_a?(Array)
116
- '[' + value.map {|v| serialize_value(v, key)}.sort.join(", ") + "]"
117
- else
118
- serialize_value(value, key)
119
- end
120
- components << component
121
- end
122
- @output.puts components.join(",\n ")
123
- end
124
-
125
- def serialize_value(value, key)
126
- case key
127
- when :comment, String
128
- "%(#{value.gsub('(', '\(').gsub(')', '\)')}).freeze"
129
- else
130
- "#{value.inspect}.freeze"
131
- end
132
- end
133
-
134
- # Actually executes the load-and-emit process - useful when using this
135
- # class outside of a command line - instantiate, set attributes manually,
136
- # then call #run
137
- def run
138
- @output.print %(# -*- encoding: utf-8 -*-
139
- # frozen_string_literal: true
140
- # This file generated automatically using vocab-fetch from #{source}
141
- require 'rdf'
142
- module #{module_name}
143
- # @!parse
144
- # # Vocabulary for <#{uri}>
145
- # class #{class_name} < RDF::#{"Strict" if @strict}Vocabulary
146
- # end
147
- class #{class_name} < RDF::#{"Strict" if @strict}Vocabulary("#{uri}")
148
- ).gsub(/^ /, '') if @output_class_file
149
-
150
- # Extract statements with subjects that have the vocabulary prefix and organize into a hash of properties and values
151
- vocab = RDF::Vocabulary.load(uri, location: source, extra: @extra, patch: @patch)
152
-
153
- # Split nodes into Class/Property/Datatype/Other
154
- term_nodes = {
155
- class: {},
156
- property: {},
157
- datatype: {},
158
- other: {}
159
- }
160
-
161
- vocab.each.to_a.sort.each do |term|
162
- name = term.to_s[uri.length..-1].to_sym
163
- kind = begin
164
- case term.type.to_s
165
- when /Class/ then :class
166
- when /Property/ then :property
167
- when /Datatype/ then :datatype
168
- else :other
169
- end
170
- rescue KeyError
171
- # 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.
172
- :other
173
- end
174
- term_nodes[kind][name] = term.attributes
175
- end
176
-
177
- {
178
- class: "Class definitions",
179
- property: "Property definitions",
180
- datatype: "Datatype definitions",
181
- other: "Extra definitions"
182
- }.each do |tt, comment|
183
- next if term_nodes[tt].empty?
184
- @output.puts "\n # #{comment}"
185
- term_nodes[tt].each {|name, attributes| from_node name, attributes, tt}
186
- end
187
-
188
- # Query the vocabulary to extract property and class definitions
189
- @output.puts " end\nend" if @output_class_file
190
- end
191
- end
192
- end