shex 0.6.0 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -35,8 +35,8 @@ module ShEx::Algebra
35
35
  # @option options [RDF::Resource] :id
36
36
  # Identifier of the operator
37
37
  # @raise [TypeError] if any operand is invalid
38
- def initialize(*operands)
39
- @options = operands.last.is_a?(Hash) ? operands.pop.dup : {}
38
+ def initialize(*operands, **options)
39
+ @options = options.dup
40
40
  @operands = operands.map! do |operand|
41
41
  case operand
42
42
  when Array
@@ -221,12 +221,19 @@ module ShEx::Algebra
221
221
  end
222
222
 
223
223
  ##
224
- # The optional TripleExpression for this Shape.
225
- # @return [TripleExpression]
224
+ # The first expression from {#expressions}.
225
+ # @return [RDF::Resource, Operand]
226
226
  def expression
227
227
  expressions.first
228
228
  end
229
229
 
230
+ ##
231
+ # References are all operands which are RDF::Resource
232
+ # @return [RDF::Resource, Operand]
233
+ def references
234
+ @references = operands.select {|op| op.is_a?(RDF::Resource)}
235
+ end
236
+
230
237
  ##
231
238
  # Returns the binary S-Expression (SXP) representation of this operator.
232
239
  #
@@ -243,11 +250,6 @@ module ShEx::Algebra
243
250
  #
244
251
  # @return [String]
245
252
  def to_sxp
246
- begin
247
- require 'sxp' # @see http://rubygems.org/gems/sxp
248
- rescue LoadError
249
- abort "SPARQL::Algebra::Operator#to_sxp requires the SXP gem (hint: `gem install sxp')."
250
- end
251
253
  require 'sparql/algebra/sxp_extensions'
252
254
 
253
255
  to_sxp_bin.to_sxp
@@ -335,7 +337,7 @@ module ShEx::Algebra
335
337
  end
336
338
  end
337
339
 
338
- new(*operands, options.merge(id: id))
340
+ new(*operands, **options.merge(id: id))
339
341
  end
340
342
 
341
343
  def json_type
@@ -432,6 +434,8 @@ module ShEx::Algebra
432
434
  case self
433
435
  when And, Or
434
436
  (obj['shapeExprs'] ||= []) << op.to_h
437
+ when Not
438
+ obj['shapeExpr'] = op.to_h
435
439
  else
436
440
  obj['valueExpr'] = op.to_h
437
441
  end
@@ -593,23 +597,23 @@ module ShEx::Algebra
593
597
 
594
598
  ##
595
599
  # Enumerate via depth-first recursive descent over operands, yielding each operator
596
- # @param [Integer] depth incrementeded for each depth of operator, and provided to block if Arity is 2
600
+ # @param [Boolean] include_self
597
601
  # @yield operator
598
602
  # @yieldparam [Object] operator
599
603
  # @return [Enumerator]
600
- def each_descendant(&block)
604
+ def each_descendant(include_self = false, &block)
601
605
  if block_given?
602
606
 
603
- block.call(self)
607
+ block.call(self) if include_self
604
608
 
605
609
  operands.each do |operand|
606
610
  case operand
607
611
  when Array
608
612
  operand.each do |op|
609
- op.each_descendant(&block) if op.respond_to?(:each_descendant)
613
+ op.each_descendant(true, &block) if op.respond_to?(:each_descendant)
610
614
  end
611
615
  else
612
- operand.each_descendant(&block) if operand.respond_to?(:each_descendant)
616
+ operand.each_descendant(true, &block) if operand.respond_to?(:each_descendant)
613
617
  end
614
618
  end
615
619
  end
@@ -632,6 +636,14 @@ module ShEx::Algebra
632
636
  @options[:parent]= operator
633
637
  end
634
638
 
639
+ ##
640
+ # Find a ShapeExpression or TripleExpression by identifier
641
+ # @param [#to_s] id
642
+ # @return [TripleExpression, ShapeExpression]
643
+ def find(id)
644
+ each_descendant(false).detect {|op| op.id == id}
645
+ end
646
+
635
647
  ##
636
648
  # Ancestors of this Operator
637
649
  # @return [Array<Operator>]
@@ -661,12 +673,12 @@ module ShEx::Algebra
661
673
  self
662
674
  end
663
675
 
664
- protected
665
- def dup
666
- operands = @operands.map {|o| o.dup rescue o}
667
- self.class.new(*operands, id: @id)
668
- end
676
+ def dup
677
+ operands = @operands.map {|o| o.dup rescue o}
678
+ self.class.new(*operands, id: @id)
679
+ end
669
680
 
681
+ protected
670
682
  ##
671
683
  # A unary operator.
672
684
  #
@@ -67,22 +67,13 @@ module ShEx::Algebra
67
67
  end
68
68
 
69
69
  ##
70
- # expressions must be ShapeExpressions
70
+ # expressions must be ShapeExpressions or references to ShapeExpressions
71
71
  #
72
72
  # @return [Operator] `self`
73
73
  # @raise [ShEx::StructureError] if the value is invalid
74
74
  def validate!
75
- expressions.each do |op|
76
- case op
77
- when ShapeExpression
78
- when RDF::Resource
79
- ref = schema.find(op)
80
- ref.is_a?(ShapeExpression) ||
81
- structure_error("#{json_type} must reference a ShapeExpression: #{ref}")
82
- else
83
- structure_error("#{json_type} must reference a ShapeExpression: #{ref}")
84
- end
85
- end
75
+ validate_expressions!
76
+ validate_self_references!
86
77
  super
87
78
  end
88
79
 
@@ -25,8 +25,9 @@ module ShEx::Algebra
25
25
  end
26
26
 
27
27
  # (see Operator#initialize)
28
- def initialize(*operands)
28
+ def initialize(*operands, **options)
29
29
  super
30
+ schema = self
30
31
  each_descendant do |op|
31
32
  # Set schema everywhere
32
33
  op.schema = self
@@ -39,7 +40,7 @@ module ShEx::Algebra
39
40
  # @param [RDF::Queryable] graph
40
41
  # @param [Hash{RDF::Term => <RDF::Resource>}, Array<Array(RDF::Term, RDF::Resource)>] map
41
42
  # A set of (`term`, `resource`) pairs where `term` is a node within `graph`, and `resource` identifies a shape
42
- # @param [Array<RDF::Term>] *focus
43
+ # @param [Array<RDF::Term>] focus ([])
43
44
  # One or more nodes within `graph` for which to run the start expression.
44
45
  # @param [Array<Schema, String>] shapeExterns ([])
45
46
  # One or more schemas, or paths to ShEx schema resources used for finding external shapes.
@@ -211,14 +212,6 @@ module ShEx::Algebra
211
212
  @start ||= operands.detect {|op| op.is_a?(Start)}
212
213
  end
213
214
 
214
- ##
215
- # Find a ShapeExpression or TripleExpression by identifier
216
- # @param [#to_s] id
217
- # @return [TripleExpression, ShapeExpression]
218
- def find(id)
219
- each_descendant.detect {|op| op.id == id}
220
- end
221
-
222
215
  ##
223
216
  # Validate shapes, in addition to other operands
224
217
  # @return [Operator] `self`
@@ -250,7 +243,7 @@ module ShEx::Algebra
250
243
  attr_accessor :expression
251
244
 
252
245
  # Holds the result of processing a shape
253
- # @param [RDF::Resource] label
246
+ # @param [RDF::Resource] shape
254
247
  # @return [ShapeResult]
255
248
  def initialize(shape)
256
249
  @shape = shape
@@ -18,13 +18,14 @@ module ShEx::Algebra
18
18
  ##
19
19
  # Called on entry
20
20
  #
21
- # @param [String] code
22
- # @param [Array<RDF::Statement>] arcs_in available statements to be matched having `focus` as an object
23
- # @param [Array<RDF::Statement>] arcs_out available statements to be matched having `focus` as a subject
24
- # @param [Integer] depth for logging
25
- # @param [Hash{Symbol => Object}] options
26
- # Other, operand-specific options
27
- # @return [Boolean] Returning `false` results in {ShEx::NotSatisfied} exception
21
+ # @overload enter(code, arcs_in, arcs_out, logging)
22
+ # @param [String] code
23
+ # @param [Array<RDF::Statement>] arcs_in available statements to be matched having `focus` as an object
24
+ # @param [Array<RDF::Statement>] arcs_out available statements to be matched having `focus` as a subject
25
+ # @param [Integer] depth for logging
26
+ # @param [Hash{Symbol => Object}] options
27
+ # Other, operand-specific options
28
+ # @return [Boolean] Returning `false` results in {ShEx::NotSatisfied} exception
28
29
  def enter(**options)
29
30
  if implementation = schema.extensions[operands.first.to_s]
30
31
  implementation.enter(code: operands[0], expression: parent, **options)
@@ -67,7 +68,6 @@ module ShEx::Algebra
67
68
  # @param [String] code
68
69
  # @param [Array<RDF::Statement>] matched statements matched by this expression
69
70
  # @param [Array<RDF::Statement>] unmatched statements considered, but not matched by this expression
70
- # @param [ShEx::Algebra::TripleExpression] expression containing this semantic act
71
71
  # @param [Integer] depth for logging
72
72
  # @param [Hash{Symbol => Object}] options
73
73
  # Other, operand-specific options
@@ -36,8 +36,8 @@ module ShEx::Algebra
36
36
  # neigh(G, n) is the neighbourhood of the node n in the graph G.
37
37
  #
38
38
  # neigh(G, n) = arcsOut(G, n) ∪ arcsIn(G, n)
39
- arcs_in = schema.graph.query(object: focus).to_a.sort_by(&:to_sxp)
40
- arcs_out = schema.graph.query(subject: focus).to_a.sort_by(&:to_sxp)
39
+ arcs_in = schema.graph.query({object: focus}).to_a.sort_by(&:to_sxp)
40
+ arcs_out = schema.graph.query({subject: focus}).to_a.sort_by(&:to_sxp)
41
41
  neigh = (arcs_in + arcs_out).uniq
42
42
 
43
43
  # `matched` is the subset of statements which match `expression`.
@@ -106,7 +106,7 @@ module ShEx::Algebra
106
106
  end
107
107
 
108
108
  ##
109
- # expression must be a TripleExpression
109
+ # expression must be a TripleExpression and must not reference itself recursively.
110
110
  #
111
111
  # @return [Operator] `self`
112
112
  # @raise [ShEx::StructureError] if the value is invalid
@@ -118,8 +118,10 @@ module ShEx::Algebra
118
118
  ref.is_a?(TripleExpression) ||
119
119
  structure_error("#{json_type} must reference a TripleExpression: #{ref}")
120
120
  else
121
- structure_error("#{json_type} must reference a TripleExpression: #{ref}")
121
+ structure_error("#{json_type} must be a TripleExpression or reference: #{expression.to_sxp}")
122
122
  end
123
+ # FIXME: this runs afoul of otherwise legitamate self-references, through a TripleExpression.
124
+ #!validate_self_references!
123
125
  super
124
126
  end
125
127
 
@@ -15,5 +15,34 @@ module ShEx::Algebra
15
15
  def satisfies?(focus, depth: 0, **options)
16
16
  raise NotImplementedError, "#satisfies? Not implemented in #{self.class}"
17
17
  end
18
+
19
+ ##
20
+ # expressions must be ShapeExpressions or references.
21
+ #
22
+ # @raise [ShEx::StructureError] if the value is invalid
23
+ def validate_expressions!
24
+ expressions.each do |op|
25
+ case op
26
+ when ShapeExpression
27
+ when RDF::Resource
28
+ ref = schema.find(op)
29
+ ref.is_a?(ShapeExpression) ||
30
+ structure_error("#{json_type} must reference a ShapeExpression: #{ref}")
31
+ else
32
+ structure_error("#{json_type} must be a ShapeExpression or reference: #{op.to_sxp}")
33
+ end
34
+ end
35
+ end
36
+
37
+ ##
38
+ # An Operator with a label must contain a reference to itself.
39
+ #
40
+ # @raise [ShEx::StructureError] if the shape is invalid
41
+ def validate_self_references!
42
+ return # FIXME: needs to stop at a TripleConstraint
43
+ each_descendant do |op|
44
+ structure_error("#{json_type} must not reference itself (#{id}): #{op.to_sxp}") if op.references.include?(id)
45
+ end
46
+ end
18
47
  end
19
48
  end
@@ -30,20 +30,12 @@ module ShEx::Algebra
30
30
  end
31
31
 
32
32
  ##
33
- # expression must be a ShapeExpression
33
+ # expressions must be ShapeExpressions or references to ShapeExpressions
34
34
  #
35
35
  # @return [Operator] `self`
36
36
  # @raise [ShEx::StructureError] if the value is invalid
37
37
  def validate!
38
- case expression
39
- when ShapeExpression
40
- when RDF::Resource
41
- ref = schema.find(expression)
42
- ref.is_a?(ShapeExpression) ||
43
- structure_error("#{json_type} must reference a ShapeExpression: #{ref}")
44
- else
45
- structure_error("#{json_type} must reference a ShapeExpression: #{ref}")
46
- end
38
+ validate_expressions!
47
39
  super
48
40
  end
49
41
  end
@@ -66,8 +66,12 @@ module ShEx::Algebra
66
66
  NAME = :languageStem
67
67
 
68
68
  # (see Stem#match?)
69
+ # If the operand is empty, than any language will do,
70
+ # otherwise, it matches the substring up to that first '-', if any.
69
71
  def match?(value, depth: 0)
70
- if value.literal? && value.language.to_s.start_with?(operands.first)
72
+ if value.literal? &&
73
+ value.language? &&
74
+ (operands.first.to_s.empty? || value.language.to_s.match?(%r(^#{operands.first}((-.*)?)$)))
71
75
  status "matched #{value}", depth: depth
72
76
  true
73
77
  else
@@ -102,7 +102,9 @@ module ShEx::Algebra
102
102
  def match?(value, depth: 0)
103
103
  initial_match = case operands.first
104
104
  when :wildcard then true
105
- when RDF::Literal then value.language.to_s.start_with?(operands.first)
105
+ when RDF::Literal
106
+ value.language? &&
107
+ (operands.first.to_s.empty? || value.language.to_s.match?(%r(^#{operands.first}((-.*)?)$)))
106
108
  else false
107
109
  end
108
110
 
@@ -97,7 +97,7 @@ module ShEx::Algebra
97
97
  ref.is_a?(ShapeExpression) ||
98
98
  structure_error("#{json_type} must reference a ShapeExpression: #{ref}")
99
99
  else
100
- structure_error("#{json_type} must reference a ShapeExpression: #{ref}")
100
+ structure_error("#{json_type} must be a ShapeExpression or reference: #{expresson.to_sxp}")
101
101
  end
102
102
  super
103
103
  end
@@ -16,6 +16,24 @@ module ShEx::Algebra
16
16
  raise NotImplementedError, "#matches Not implemented in #{self.class}"
17
17
  end
18
18
 
19
+ ##
20
+ # expressions must be TripleExpressions or references to TripleExpressions
21
+ #
22
+ # @raise [ShEx::StructureError] if the value is invalid
23
+ def validate_expressions!
24
+ expressions.each do |op|
25
+ case op
26
+ when TripleExpression
27
+ when RDF::Resource
28
+ ref = schema.find(op)
29
+ ref.is_a?(TripleExpression) ||
30
+ structure_error("#{json_type} must reference a TripleExpression: #{ref}")
31
+ else
32
+ structure_error("#{json_type} must be a TripleExpression or reference: #{op.to_sxp}")
33
+ end
34
+ end
35
+ end
36
+
19
37
  ##
20
38
  # Included TripleConstraints
21
39
  # @return [Array<TripleConstraints>]
data/lib/shex/algebra.rb CHANGED
@@ -1,11 +1,10 @@
1
1
  $:.unshift(File.expand_path("../..", __FILE__))
2
2
  require 'sparql/algebra'
3
- require 'sxp'
4
3
 
5
4
  module ShEx
6
5
  # Based on the SPARQL Algebra, operators for executing a patch
7
6
  #
8
- # @author [Gregg Kellogg](http://greggkellogg.net/)
7
+ # @author [Gregg Kellogg](https://greggkellogg.net/)
9
8
  module Algebra
10
9
  autoload :And, 'shex/algebra/and'
11
10
  autoload :Annotation, 'shex/algebra/annotation'
@@ -6,21 +6,23 @@
6
6
  # @see http://shex.io/extensions/Test/
7
7
  require 'shex'
8
8
 
9
- class ShEx::Test < ShEx::Extension("http://shex.io/extensions/Test/")
10
- # (see ShEx::Extension#visit)
11
- def visit(code: nil, matched: nil, depth: 0, **options)
12
- str = if md = /^ *(fail|print) *\( *(?:(\"(?:[^\\"]|\\")*\")|([spo])) *\) *$/.match(code.to_s)
13
- md[2] || case md[3]
14
- when 's' then matched.subject
15
- when 'p' then matched.predicate
16
- when 'o' then matched.object
17
- else matched.to_sxp
18
- end.to_s
19
- else
20
- matched ? matched.to_sxp : 'no statement'
21
- end
9
+ module ShEx
10
+ Test = Class.new(ShEx::Extension("http://shex.io/extensions/Test/")) do
11
+ # (see ShEx::Extension#visit)
12
+ def visit(code: nil, matched: nil, depth: 0, **options)
13
+ str = if md = /^ *(fail|print) *\( *(?:(\"(?:[^\\"]|\\")*\")|([spo])) *\) *$/.match(code.to_s)
14
+ md[2] || case md[3]
15
+ when 's' then matched.subject
16
+ when 'p' then matched.predicate
17
+ when 'o' then matched.object
18
+ else matched.to_sxp
19
+ end.to_s
20
+ else
21
+ matched ? matched.to_sxp : 'no statement'
22
+ end
22
23
 
23
- $stdout.puts str
24
- return !md || md[1] == 'print'
24
+ $stdout.puts str
25
+ return !md || md[1] == 'print'
26
+ end
25
27
  end
26
- end
28
+ end
data/lib/shex/format.rb CHANGED
@@ -4,14 +4,12 @@ module ShEx
4
4
  ##
5
5
  # ShEx format specification. Note that this format does not define any readers or writers.
6
6
  #
7
- # @example Obtaining an LD Patch format class
8
- # RDF::Format.for(:shex) #=> LD::Patch::Format
7
+ # @example Obtaining an ShEx format class
8
+ # RDF::Format.for(:shex) #=> ShEx::Format
9
9
  # RDF::Format.for("etc/foaf.shex")
10
10
  # RDF::Format.for(file_name: "etc/foaf.shex")
11
11
  # RDF::Format.for(file_extension: "shex")
12
12
  # RDF::Format.for(content_type: "application/shex")
13
- #
14
- # @see http://www.w3.org/TR/ldpatch/
15
13
  class Format < RDF::Format
16
14
  content_type 'application/shex', extension: :shex
17
15
  content_encoding 'utf-8'