ld-patch 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2eb0185c5e3d87a311c57b6460363bf5e479fd0a
4
+ data.tar.gz: fd9772612616488a2ce9c595fa01ff3ebfff3879
5
+ SHA512:
6
+ metadata.gz: 9a0aece9207a9d91d34706b19051f54c95934ca4cd017555eefda8aa0a032f1c2c4cbbddd4cdb2492ba966f3866e3ba8af30df806b420c8b76612a54d73b19d2
7
+ data.tar.gz: 98914c762b9fc8152c5399727c374dce01364d9ce6416e829c1ca3c4fbef686ef6dc78b4558cf9ce917b6f7f22da560f064bf0d4f1d2e18951a487978eac760f
data/AUTHORS ADDED
@@ -0,0 +1 @@
1
+ * Gregg Kellogg <gregg@greggkellogg.net>
data/LICENSE ADDED
@@ -0,0 +1,25 @@
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 <http://unlicense.org>
25
+
@@ -0,0 +1,81 @@
1
+ # LD Patch for RDF.rb
2
+
3
+ This is a [Ruby][] implementation of [LD Patch][] for [RDF.rb][].
4
+
5
+ [![Gem Version](https://badge.fury.io/rb/ld-patch.png)](http://badge.fury.io/rb/ld-patch)
6
+ [![Build Status](https://travis-ci.org/ruby-rdf/ld-patch.png?branch=master)](http://travis-ci.org/ruby-rdf/ld-patch)
7
+ [![Coverage Status](https://coveralls.io/repos/ruby-rdf/ld-patch/badge.svg)](https://coveralls.io/r/ruby-rdf/ld-patch)
8
+
9
+ ## Description
10
+
11
+ This gem implements the [LD Patch][] specification with a couple of changes and/or limitations:
12
+
13
+ * The `INDEX` terminal was replaced by `INTEGER`. Having two terminals matching the same token strings causes a conflict. As a result, a _slice_ may contain positive integers, as well as unsigned and negative-integers.
14
+ * The _graph_ rule is changed to the following:
15
+
16
+ [18] graph ::= triples ('.' triples?)*
17
+
18
+ This is necessary as the specified production is not context-free. As a result, it is possible for a graph to contain multiple trailing "`.`".
19
+
20
+ [LD Patch][] is useful inside a Rack container where it can respond to `POST` messages to affect the modification of a _target graph_ identified using the URL of the `POST`.
21
+
22
+ ## Features
23
+
24
+ * 100% free and unencumbered [public domain](http://unlicense.org/) software.
25
+ * Complete [Linked Data Patch Format][LD Patch] parsing and execution
26
+ * Implementation Report: {file:etc/earl.html EARL}
27
+ * Compatible with Ruby >= 2.0.0.
28
+
29
+ ## Documentation
30
+ Full documentation available on [Rubydoc.info][LD-Patch doc]
31
+
32
+ ## Implementation Notes
33
+ The reader uses the [EBNF][] gem to generate first, follow and branch tables, and uses the `Parser` and `Lexer` modules to implement the LD Patch parser.
34
+
35
+ The parser takes branch and follow tables generated from the [LD Patch Grammar](file.ld-patch.html) described in the [specification][LD Patch]. Branch and Follow tables are specified in the generated {LD::Patch::Meta}.
36
+
37
+ ## Dependencies
38
+
39
+ * [Ruby](http://ruby-lang.org/) (>= 2.0.0)
40
+ * [RDF.rb](http://rubygems.org/gems/rdf) (>= 1.1.15)
41
+ * [EBNF][] (>= 0.3.0)
42
+ * [SPARQL][] (>= 1.1.7)
43
+ * [SXP][] (>= 0.1.5)
44
+ * [RDF::XSD][] (>= 1.1.4)
45
+
46
+ ## Mailing List
47
+ * <http://lists.w3.org/Archives/Public/public-rdf-ruby/>
48
+
49
+ ## Author
50
+ * [Gregg Kellogg](http://github.com/gkellogg) - <http://greggkellogg.net/>
51
+
52
+ ## Contributing
53
+ This repository uses [Git Flow](https://github.com/nvie/gitflow) to mange development and release activity. All submissions _must_ be on a feature branch based on the _develop_ branch to ease staging and integration.
54
+
55
+ * Do your best to adhere to the existing coding conventions and idioms.
56
+ * Don't use hard tabs, and don't leave trailing whitespace on any line.
57
+ * Do document every method you add using [YARD][] annotations. Read the [tutorial][YARD-GS] or just look at the existing code for examples.
58
+ * Don't touch the `.gemspec`, `VERSION` or `AUTHORS` files. If you need to change them, do so on your private branch only.
59
+ * Do feel free to add yourself to the `CREDITS` file and the corresponding list in the the `README`. Alphabetical order applies.
60
+ * Do note that in order for us to merge any non-trivial changes (as a rule of thumb, additions larger than about 15 lines of code), we need an explicit [public domain dedication][PDD] on record from you.
61
+
62
+ ## License
63
+ This is free and unencumbered public domain software. For more information,
64
+ see <http://unlicense.org/> or the accompanying {file:LICENSE} file.
65
+
66
+ A copy of the [LD Patch EBNF](file:etc/ld-patch.ebnf) and derived parser files are included in the repository, which are not covered under the UNLICENSE. These files are covered via the [W3C Document License](http://www.w3.org/Consortium/Legal/2002/copyright-documents-20021231).
67
+
68
+ [Ruby]: http://ruby-lang.org/
69
+ [RDF]: http://www.w3.org/RDF/
70
+ [YARD]: http://yardoc.org/
71
+ [YARD-GS]: http://rubydoc.info/docs/yard/file/docs/GettingStarted.md
72
+ [PDD]: http://lists.w3.org/Archives/Public/public-rdf-ruby/2010May/0013.html
73
+ [RDF.rb]: http://rubygems.org/gems/rdf
74
+ [RDF::XSD]: http://rubygems.org/gems/rdf-xsd
75
+ [EBNF]: http://rubygems.org/gems/ebnf
76
+ [SPARQL]: http://rubygems.org/gems/sparql
77
+ [Linked Data]: http://rubygems.org/gems/linkeddata
78
+ [SSE]: http://openjena.org/wiki/SSE
79
+ [SXP]: http://rubygems.org/gems/sxp-ruby
80
+ [LD Patch]: http://www.w3.org/TR/ldpatch/
81
+ [LD-Patch doc]: http://rubydoc.info/github/ruby-rdf/ld-patch
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", 'lib')))
4
+ require 'ld/patch'
5
+ begin
6
+ require 'linkeddata'
7
+ rescue LoadError
8
+ require 'rdf/ntriples'
9
+ require 'rdf/turtle'
10
+ end
11
+ require 'getoptlong'
12
+
13
+ def run(input, options = {})
14
+ if options[:debug]
15
+ puts "input graph:\n#{options[:graph].dump(:ttl, standard_prefixes: true)}\n" if options[:graph]
16
+ puts "query:\n#{input}\n"
17
+ end
18
+ options[:graph] ||= RDF::Graph.new
19
+
20
+ if options[:verbose]
21
+ puts ("\nPATCH:\n" + input)
22
+ end
23
+
24
+ patch = if options[:sse]
25
+ SPARQL::Algebra.parse(input, options)
26
+ else
27
+ # Only do grammar debugging if we're generating SSE
28
+ LD::Patch.parse(input, options)
29
+ end
30
+
31
+ puts ("\nSSE:\n" + patch.to_sse) if options[:debug] || options[:to_sse]
32
+
33
+ unless options[:to_sse]
34
+ res = patch.execute(options[:graph], debug: options[:debug])
35
+ puts res.inspect if options[:verbose]
36
+ puts res.dump(:ttl, base_uri: patch.base_uri, prefixes: patch.prefixes, standard_prefixes: true)
37
+ end
38
+ end
39
+
40
+ opts = GetoptLong.new(
41
+ ["--debug", GetoptLong::NO_ARGUMENT],
42
+ ["--dump", GetoptLong::NO_ARGUMENT],
43
+ ["--execute", "-e", GetoptLong::REQUIRED_ARGUMENT],
44
+ ["--progress", GetoptLong::NO_ARGUMENT],
45
+ ["--sse", GetoptLong::NO_ARGUMENT],
46
+ ["--to-sse", GetoptLong::NO_ARGUMENT],
47
+ ["--validate", GetoptLong::NO_ARGUMENT],
48
+ ["--verbose", GetoptLong::NO_ARGUMENT],
49
+ ["--help", "-?", GetoptLong::NO_ARGUMENT]
50
+ )
51
+
52
+ options = {
53
+ graph: RDF::Repository.new,
54
+ }
55
+
56
+ input = nil
57
+
58
+ opts.each do |opt, arg|
59
+ case opt
60
+ when '--debug' then options[:debug] = true
61
+ when '--dump' then $dump = true
62
+ when '--execute' then input = arg
63
+ when '--progress' then options[:debug] ||= 2
64
+ when '--sse' then options[:sse] = true
65
+ when '--to-sse' then options[:to_sse] = true
66
+ when '--validate' then options[:validate] = true
67
+ when '--verbose' then options[:verbose] = true
68
+ when "--help"
69
+ puts "Usage: #{$0} [options] file-or-uri ..."
70
+ puts "Options:"
71
+ puts " --execute,-e: Use option argument as the SPARQL input if no files are given"
72
+ puts " --dump: Dump raw output, otherwise serialize to SSE"
73
+ puts " --debug: Display detailed debug output"
74
+ puts " --sse: Input is in SSE format"
75
+ puts " --to-sse: Generate SSE instead of running query"
76
+ puts " --verbose: Display details of processing"
77
+ puts " --help,-?: This message"
78
+ exit(0)
79
+ end
80
+ end
81
+
82
+ if ARGV.empty?
83
+ s = input ? input : $stdin.read
84
+ run(s, options)
85
+ else
86
+ ARGV.each do |test_file|
87
+ puts "parse #{test_file}"
88
+ run(RDF::Util::File.open_file(test_file).read, options.merge(base_uri: RDF::URI(test_file)))
89
+ end
90
+ end
91
+ puts
@@ -0,0 +1,79 @@
1
+ $:.unshift(File.expand_path("../..", __FILE__))
2
+ require 'rdf'
3
+ require 'sparql'
4
+ require 'sparql/algebra'
5
+ require 'sxp'
6
+
7
+ module LD
8
+ # **`LD::Patch`** is a Linked Data Patch extension for RDF.rb.
9
+ #
10
+ # @author [Gregg Kellogg](http://greggkellogg.net/)
11
+ module Patch
12
+ autoload :Algebra, 'ld/patch/algebra'
13
+ autoload :Meta, 'ld/patch/meta'
14
+ autoload :Parser, 'ld/patch/parser'
15
+ autoload :Terminals, 'ld/patch/terminals'
16
+ autoload :Version, 'ld/patch/version'
17
+
18
+ ##
19
+ # Parse the given LD Patch `input` string.
20
+ #
21
+ # @example
22
+ # query = LD::Patch.parse("Add { <http://example.org/s2> <http://example.org/p2> <http://example.org/o2> } .")
23
+ #
24
+ # @param [IO, StringIO, String, #to_s] input
25
+ # @param [Hash{Symbol => Object}] options
26
+ # @return [Object]
27
+ # The parsed Patch
28
+ def self.parse(input, options = {})
29
+ LD::Patch::Parser.new(input, options).parse
30
+ end
31
+
32
+ class Error < StandardError
33
+ # The status code associated with this error
34
+ attr_reader :code
35
+
36
+ ##
37
+ # Initializes a new patch error instance.
38
+ #
39
+ # @param [String, #to_s] message
40
+ # @param [Hash{Symbol => Object}] options
41
+ # @option options [Integer] :code (422)
42
+ def initialize(message, options = {})
43
+ @code = options.fetch(:status_code, 422)
44
+ super(message.to_s)
45
+ end
46
+ end
47
+
48
+ # Indicates bad syntax found in LD Patch document
49
+ class ParseError < Error
50
+ ##
51
+ # The invalid token which triggered the error.
52
+ #
53
+ # @return [String]
54
+ attr_reader :token
55
+
56
+ ##
57
+ # The line number where the error occurred.
58
+ #
59
+ # @return [Integer]
60
+ attr_reader :lineno
61
+
62
+ ##
63
+ # Initializes a new parser error instance.
64
+ #
65
+ # @param [String, #to_s] message
66
+ # @param [Hash{Symbol => Object}] options
67
+ # @option options [String] :token (nil)
68
+ # @option options [Integer] :lineno (nil)
69
+ # @option options [Integer] :code (400)
70
+ def initialize(message, options = {})
71
+ @token = options[:token]
72
+ @lineno = options[:lineno] || (@token.lineno if @token.respond_to?(:lineno))
73
+ super(message.to_s, code: options.fetch(:code, 400))
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+
@@ -0,0 +1,24 @@
1
+ $:.unshift(File.expand_path("../..", __FILE__))
2
+ require 'sparql/algebra'
3
+ require 'sxp'
4
+
5
+ module LD::Patch
6
+ # Based on the SPARQL Algebra, operators for executing a patch
7
+ #
8
+ # @author [Gregg Kellogg](http://greggkellogg.net/)
9
+ module Algebra
10
+ autoload :Add, 'ld/patch/algebra/add'
11
+ autoload :Bind, 'ld/patch/algebra/bind'
12
+ autoload :Constraint, 'ld/patch/algebra/constraint'
13
+ autoload :Cut, 'ld/patch/algebra/cut'
14
+ autoload :Delete, 'ld/patch/algebra/delete'
15
+ autoload :Index, 'ld/patch/algebra/index'
16
+ autoload :Patch, 'ld/patch/algebra/patch'
17
+ autoload :Path, 'ld/patch/algebra/path'
18
+ autoload :Prefix, 'ld/patch/algebra/prefix'
19
+ autoload :Reverse, 'ld/patch/algebra/reverse'
20
+ autoload :UpdateList, 'ld/patch/algebra/update_list'
21
+ end
22
+ end
23
+
24
+
@@ -0,0 +1,59 @@
1
+ module LD::Patch::Algebra
2
+
3
+ ##
4
+ # The LD Patch `add` operator.
5
+ #
6
+ # The Add operation is used to add triples to the target graph with or without allowing duplicates.
7
+ #
8
+ # @example
9
+ # (add ((<a> <b> <c>)))
10
+ #
11
+ class Add < SPARQL::Algebra::Operator::Unary
12
+ include SPARQL::Algebra::Update
13
+ include SPARQL::Algebra::Evaluatable
14
+
15
+ NAME = :add
16
+
17
+ ##
18
+ # Executes this upate on the given `writable` graph or repository.
19
+ #
20
+ # @param [RDF::Queryable] queryable
21
+ # the graph or repository to write
22
+ # @param [Hash{Symbol => Object}] options
23
+ # any additional options
24
+ # @option options [Boolean] :new
25
+ # Specifies that triples may not already exist in the target graph
26
+ # @return [RDF::Query::Solutions] A single solution including passed bindings with `var` bound to the solution.
27
+ # @raise [Error]
28
+ # If the :new option is specified and any triples already exist in queryable or if unbound variables are used
29
+ # @see http://www.w3.org/TR/sparql11-update/
30
+ def execute(queryable, options = {})
31
+ debug(options) {"Add"}
32
+ bindings = options.fetch(:bindings)
33
+ solution = bindings.first
34
+
35
+ # Bind variables to triples
36
+ triples = operand(0).dup.replace_vars! do |var|
37
+ case var
38
+ when RDF::Query::Pattern
39
+ s = var.bind(solution)
40
+ raise LD::Patch::Error.new("Operand uses unbound pattern #{var.inspect}", code: 400) if s.variable?
41
+ s
42
+ when RDF::Query::Variable
43
+ raise LD::Patch::Error.new("Operand uses unbound variable #{var.inspect}", code: 400) unless solution.bound?(var)
44
+ solution[var]
45
+ end
46
+ end
47
+
48
+ # If `:new` is specified, verify that no triple in triples exists in queryable
49
+ if @options[:new]
50
+ triples.each do |triple|
51
+ raise LD::Patch::Error, "Target graph contains added triple #{triple.to_ntriples}" if queryable.has_statement?(triple)
52
+ end
53
+ end
54
+
55
+ queryable.insert(*triples)
56
+ bindings
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,90 @@
1
+ module LD::Patch::Algebra
2
+
3
+ ##
4
+ # The LD Patch `bind` operator.
5
+ #
6
+ # The Bind operation is used to bind a single term to a variable.
7
+ #
8
+ # @example simple value to variable binding
9
+ # Bind ?x <http://example.org/s> . #=>
10
+ # (bind ?x <http://example.org/s> ())
11
+ #
12
+ # @example constant path (filter-forward.ldpatch)
13
+ # Bind ?x <http://example.org/s> / <http://example.org/p1> . #=>
14
+ # (bind ?x <http://example.org/s> (<http://example.org/p1>))
15
+ #
16
+ # @example list index (path-at.ldpatch)
17
+ # Bind ?x <http://example.org/s> / 1 . #=>
18
+ # (bind ?x <http://example.org/s> ((index 1)))
19
+ #
20
+ # @example reverse (path-backward.ldpath)
21
+ # Bind ?x <http://example.org/s> / ^<http://example.org/p1> . #=>
22
+ # (bind ?x <http://example.org/s> ((reverse <http://example.org/p1>)))
23
+ #
24
+ # @example constraint (path-filter-equal.ldpatch)
25
+ # Bind ?x <http://example.org/s> / <http://example.org/p2> [ / <http://example.org/l> = "b" ] . #=>
26
+ # (bind ?x <http://example.org/s> (
27
+ # <http://example.org/p2>
28
+ # (constraint (<http://example.org/l>) "b")))
29
+ #
30
+ # @example constraint (path-filter.ldpatch)
31
+ # Bind ?x <http://example.org/s> / <http://example.org/p2> [ / <http://example.org/p1> ] . #=>
32
+ # (bind ?x <http://example.org/s> (
33
+ # <http://example.org/p2>
34
+ # (constraint (<http://example.org/l>))))
35
+ #
36
+ # @example starting with a literal
37
+ # Bind ?x "a" / ^<http://example.org/l> / ^<http://example.org/p2> . #=>
38
+ # (bind ?x "a" (
39
+ # (reverse <http://example.org/l>)
40
+ # (reverse <http://example.org/p2>)))
41
+ #
42
+ # @example unicity (path-unicity.ldpath)
43
+ # Bind ?x <http://example.org/s> / <http://example.org/p1> ! . #=>
44
+ # SELECT ?x
45
+ # WHERE {<http//example.org/s> <http://example.org/p1> ?x}
46
+ # GROUP BY ?x
47
+ # HAVING COUNT(?x) = 1
48
+ # (bind ?x ?0
49
+ # ((pattern <http://example.org/s> <http://example.org/p1> ??0)
50
+ # (unique ??0)))
51
+ class Bind < SPARQL::Algebra::Operator::Ternary
52
+ include SPARQL::Algebra::Query
53
+ include SPARQL::Algebra::Evaluatable
54
+
55
+ NAME = :bind
56
+
57
+ ##
58
+ # Maps the value to a single output term by executing the path and updates `bindings` with `var` set to that output term
59
+ #
60
+ # @param [RDF::Queryable] queryable
61
+ # the graph or repository to write
62
+ # @param [Hash{Symbol => Object}] options
63
+ # any additional options
64
+ # @option options [RDF::Query::Solutions] :bindings
65
+ # @return [RDF::Query::Solutions] A single solution including passed bindings with `var` bound to the solution.
66
+ # @raise [Error]
67
+ # If path does not evaluate to a single term or if unbound variables are used.
68
+ # @see http://www.w3.org/TR/sparql11-update/
69
+ def execute(queryable, options = {})
70
+ debug(options) {"Bind"}
71
+ bindings = options.fetch(:bindings)
72
+ solution = bindings.first
73
+ var, value, path = operands
74
+
75
+ # Bind variables to path
76
+ if value.variable?
77
+ raise LD::Patch::Error.new("Operand uses unbound variable #{value.inspect}", code: 400) unless solution.bound?(value)
78
+ value = solution[value]
79
+ end
80
+
81
+ path = path.dup.replace_vars! do |v|
82
+ raise Error, "Operand uses unbound variable #{v.inspect}" unless solution.bound?(v)
83
+ solution[v]
84
+ end
85
+ results = path.execute(queryable, terms: [value])
86
+ raise LD::Patch::Error, "Bind path bound to #{results.length} terms, expected just one" unless results.length == 1
87
+ RDF::Query::Solutions.new [solution.merge(var.to_sym => results.first.path)]
88
+ end
89
+ end
90
+ end