sasquatch 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,57 @@
1
+ Sasquatch - A DSL for the Talis Platform
2
+ ==============================
3
+
4
+ Sasquatch makes it easy to add/delete/replace resources and triples on the Talis Platform using RDF.rb.
5
+
6
+ Example:
7
+
8
+ store = Sasquatch::Store.new(storename, {:username=>u, :password=>p}) # authentication is optional
9
+
10
+ resource = store.describe("http://example.org/1")
11
+ => #<RDF::Graph:0x81467e78(<>)>
12
+
13
+ resources = store.describe_multi(['http://example.org/1', 'http://example.org/2'])
14
+ => #<RDF::Graph:0x8100fd58(<>)>
15
+
16
+ store.delete_uri('http://example.org/1')
17
+
18
+ => true
19
+
20
+ store.delete_uris(['http://example.org/2', 'http://example.org/3'])
21
+
22
+ => true
23
+
24
+ Because ChangeSets are somewhat cumbersome, there are methods to replace triples or resources wholesale:
25
+
26
+ store.replace(resources) # where 'resources' is an RDF::Graph, array of RDF::Statements or RDF::RDFObjects::Resource
27
+
28
+ This will replace all of the subjects with what is sent. Pass a boolean true to make it a versioned changeset.
29
+
30
+ There is also:
31
+
32
+ store.replace_triple(old_triple, new_triple)
33
+
34
+ where old_triple and new_triple are RDF::Statements
35
+
36
+ and
37
+
38
+ store.replace_triples
39
+
40
+ which takes a Hash where the keys are the old triples and the values are the new triples.
41
+
42
+ search = store.search("italy")
43
+ => [#<RDF::URI:0x80f92484(http://id.loc.gov/authorities/sh89006665#concept)>, #<RDF::URI:0x80f90df0(http://lcsubjects.org/subjects/sh89006665#concept)>, #<RDF::URI:0x80f9042c(http://id.loc.gov/authorities/sh2004008861#concept)>, #<RDF::URI:0x80f8f5e0(http://id.loc.gov/authorities/sh2006002475#concept)>, #<RDF::URI:0x80f8e974(http://id.loc.gov/authorities/sh86005484#concept)>, #<RDF::URI:0x80f8d31c(http://lcsubjects.org/subjects/sh2004008861#concept)>, #<RDF::URI:0x80f8c548(http://lcsubjects.org/subjects/sh2006002475#concept)>, #<RDF::URI:0x80f8b29c(http://lcsubjects.org/subjects/sh86005484#concept)>, #<RDF::URI:0x80f8a48c(http://id.loc.gov/authorities/sh85069035#concept)>, #<RDF::URI:0x80f88f38(http://lcsubjects.org/subjects/sh85069035#concept)>]
44
+
45
+ #search returns a Sasquatch::SearchResult, which is sort of a glorified array.
46
+
47
+ To get the next page of search results, use SearchResult#next, to get the previous, use SearchResult#previous
48
+
49
+ You can access the search result graph at SearchResult#graph
50
+
51
+ Because it is really an array of URIs, you can chain it into other Store methods:
52
+
53
+ store.delete_uris(store.search('italy'))
54
+
55
+ => true
56
+
57
+ etc.
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,16 @@
1
+ require 'httparty'
2
+ require 'cgi'
3
+ require 'rdf'
4
+ require 'rdf/ntriples'
5
+ require 'rdf/json'
6
+ require 'sparql/client'
7
+ require 'json'
8
+ module Sasquatch
9
+ require File.dirname(__FILE__) + '/sasquatch/http_party'
10
+ require File.dirname(__FILE__) + '/sasquatch/store'
11
+ require File.dirname(__FILE__) + '/sasquatch/rdf'
12
+ require File.dirname(__FILE__) + '/sasquatch/changeset'
13
+ require File.dirname(__FILE__) + '/sasquatch/rss10'
14
+ require File.dirname(__FILE__) + '/sasquatch/search_result'
15
+ require File.dirname(__FILE__) + '/sasquatch/sparql_builder'
16
+ end
@@ -0,0 +1,66 @@
1
+ module RDF
2
+ module Talis
3
+ class Changeset < RDF::Vocabulary('http://purl.org/vocab/changeset/schema#')
4
+ property :removal
5
+ property :addition
6
+ property :creatorName
7
+ property :createdDate
8
+ property :subjectOfChange
9
+ property :changeReason
10
+ property :ChangeSet
11
+ property :precedingChangeSet
12
+ end
13
+
14
+ class Bigfoot < RDF::Vocabulary('http://schemas.talis.com/2006/bigfoot/configuration#')
15
+ property :jobType
16
+ property :ResetDataJob
17
+ property :startTime
18
+ property :JobRequest
19
+ end
20
+
21
+ class TalisDir < RDF::Vocabulary('http://schemas.talis.com/2005/dir/schema#')
22
+ property :etag
23
+ end
24
+ end
25
+ end
26
+
27
+ module Sasquatch
28
+ class Changeset
29
+ attr_reader :resource, :statements, :subject_of_change
30
+ def initialize(subject_of_change, resource=nil)
31
+ @resource = RDF::Node.new unless resource
32
+ @subject_of_change = subject_of_change
33
+ @statements = []
34
+ @statements.concat [RDF::Statement.new(@resource, RDF.type, RDF::Talis::Changeset.ChangeSet),
35
+ RDF::Statement.new(@resource, RDF::Talis::Changeset.changeReason, "Generated in Platform Party"),
36
+ RDF::Statement.new(@resource, RDF::Talis::Changeset.createdDate, Time.now),
37
+ RDF::Statement.new(@resource, RDF::Talis::Changeset.subjectOfChange, subject_of_change)]
38
+ end
39
+
40
+ def remove_statements(stmts)
41
+ stmts = [stmts] if stmts.is_a?(RDF::Statement)
42
+ stmts.each do |stmt|
43
+ raise ArgumentError unless stmt.subject == @subject_of_change
44
+ @statements.concat changeset_statement(stmt, :removal)
45
+ end
46
+ end
47
+
48
+ def add_statements(stmts)
49
+ stmts = [stmts] if stmts.is_a?(RDF::Statement)
50
+ stmts.each do |stmt|
51
+ next unless stmt
52
+ raise ArgumentError unless stmt.subject == @subject_of_change
53
+ @statements.concat changeset_statement(stmt, :addition)
54
+ end
55
+ end
56
+
57
+ def changeset_statement(stmt, action)
58
+ s = RDF::Node.new
59
+ [RDF::Statement.new(@resource, RDF::Talis::Changeset.send(action), s),
60
+ RDF::Statement.new(s, RDF.type, RDF.to_rdf+"Statement"),
61
+ RDF::Statement.new(s, RDF.subject, stmt.subject),
62
+ RDF::Statement.new(s, RDF.predicate, stmt.predicate),
63
+ RDF::Statement.new(s, RDF.object, stmt.object)]
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,12 @@
1
+ module HTTParty
2
+ class Request
3
+ def auth_headers
4
+ credentials[:headers]
5
+ end
6
+ def setup_digest_auth
7
+ if auth_headers
8
+ @raw_request.digest_auth(username, password, {'www-authenticate' => auth_headers})
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,31 @@
1
+ module RDF
2
+ class Statement
3
+ def to_ntriples
4
+ RDF::Writer.for(:ntriples).buffer do |writer|
5
+ writer << self
6
+ end
7
+ end
8
+ end
9
+
10
+ class Graph
11
+ attr_reader :requested_resource
12
+ def to_ntriples
13
+ RDF::Writer.for(:ntriples).buffer do |writer|
14
+ self.statements.each do |statement|
15
+ writer << statement
16
+ end
17
+ end
18
+ end
19
+ def set_requested_resource(uri)
20
+ @requested_resource = uri
21
+ end
22
+
23
+ def requested_resource
24
+ if self.respond_to?(:"[]") # in case rdf-rdfobjects is available
25
+ self[@requested_resource]
26
+ else
27
+ RDF::URI.intern(@requested_resource)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,18 @@
1
+
2
+ module RDF
3
+ module RSS10
4
+ require File.dirname(__FILE__) + '/rss10/format'
5
+ require File.dirname(__FILE__) + '/rss10/reader'
6
+ # require 'rdf/n3/vocab'
7
+ # require 'rdf/n3/patches/array_hacks'
8
+ # require 'rdf/n3/patches/graph_properties'
9
+ # autoload :Meta, 'rdf/n3/reader/meta'
10
+ # autoload :Parser, 'rdf/n3/reader/parser'
11
+ # autoload :Reader, 'rdf/n3/reader'
12
+ # autoload :VERSION, 'rdf/n3/version'
13
+ # autoload :Writer, 'rdf/n3/writer'
14
+ #
15
+ # def self.debug?; @debug; end
16
+ # def self.debug=(value); @debug = value; end
17
+ end
18
+ end
@@ -0,0 +1,12 @@
1
+ module RDF::RSS10
2
+
3
+ class Format < RDF::Format
4
+ #content_type 'application/xml', :extension => :rss
5
+ #content_type 'application/rdf+xml', :extension => :rss
6
+ content_encoding 'utf-8'
7
+
8
+ reader { RDF::RSS10::Reader }
9
+ writer { RDF::RSS10::Writer }
10
+ end
11
+ end
12
+
@@ -0,0 +1,226 @@
1
+ module RDF::RSS10
2
+ class Reader < RDF::Reader
3
+ require 'nokogiri'
4
+ # copied indiscriminately from gkellogg's rdf/xml parser
5
+
6
+ def initialize(input = $stdin, options = {}, &block)
7
+ super do
8
+
9
+ @base_uri = uri(options[:base_uri]) if options[:base_uri]
10
+
11
+ @doc = case input
12
+ when Nokogiri::XML::Document then input
13
+ else Nokogiri::XML.parse(input, @base_uri.to_s)
14
+ end
15
+
16
+ raise RDF::ReaderError, "Synax errors:\n#{@doc.errors}" if !@doc.errors.empty? && validate?
17
+ raise RDF::ReaderError, "Empty document" if (@doc.nil? || @doc.root.nil?) && validate?
18
+
19
+ block.call(self) if block_given?
20
+ end
21
+ end
22
+ ##
23
+ # Iterates the given block for each RDF statement in the input.
24
+ #
25
+ # @yield [statement]
26
+ # @yieldparam [RDF::Statement] statement
27
+ # @return [void]
28
+ def each_statement(&block)
29
+ # Block called from add_statement
30
+ @callback = block
31
+
32
+ root = @doc.root
33
+
34
+
35
+ rdf_nodes = root.xpath("/rdf:RDF", "rdf" => RDF.to_uri.to_s)
36
+ statements = []
37
+ rdf_nodes.each do |node|
38
+
39
+
40
+ root.xpath("//rss:channel", "rss"=>RDF::RSS.to_s).each do |channel|
41
+ if channel.attribute('about')
42
+ channel_uri = RDF::URI.intern(channel.attribute('about').value)
43
+ else
44
+ channel_uri = RDF::Node.new
45
+ end
46
+ statements << RDF::Statement.new(channel_uri, RDF.type, RDF::RSS.channel)
47
+ channel.children.each do |elem|
48
+ unless elem.name == 'items'
49
+ if elem.children.length == 1 && elem.children.first.is_a?(Nokogiri::XML::Text)
50
+ statements << RDF::Statement.new(channel_uri, RDF::URI.intern(elem.namespace.href + elem.name), literal(elem.children.first))
51
+ elsif elem.attribute('resource')
52
+ statements << RDF::Statement.new(channel_uri, RDF::URI.intern(elem.namespace.href + elem.name), RDF::URI.intern(elem.attribute('resource').value))
53
+ end
54
+ else
55
+ stmt = RDF::Statement.new(:subject=>channel_uri, :predicate=>RDF::URI.intern(elem.namespace.href + elem.name))
56
+ elem.children.each do |list|
57
+ if list.attribute('about')
58
+ list_uri = RDF::URI.intern(list.attribute('about').value)
59
+ else
60
+ list_uri = RDF::Node.new
61
+ end
62
+
63
+ stmt.object = list_uri
64
+ statements << stmt
65
+ list_type = RDF::URI.intern(list.namespace.href + list.name)
66
+ unless list_type == RDF.Description
67
+ statements << RDF::Statement.new(:subject=>list_uri, :predicate=>RDF.type, :object=>list_type)
68
+ end
69
+ list.children.each do |li|
70
+ stmt = RDF::Statement.new(:subject=>list_uri, :predicate=>RDF::URI.intern(li.namespace.href + li.name))
71
+ if li.attribute('resource')
72
+ stmt.object = RDF::URI.intern(li.attribute('resource').value)
73
+ elsif li.children.length == 1 && li.children.first.is_a?(Nokogiri::XML::Text)
74
+ stmt.object = literal(li.children.first)
75
+ end
76
+ statements << stmt if stmt.object
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ root.xpath("/rdf:RDF/rss:item", "rdf"=>RDF.to_uri.to_s, "rss"=>RDF::RSS.to_s).each do |item|
83
+ if item.attribute('about')
84
+ item_uri = RDF::URI.intern(item.attribute('about').value)
85
+ else
86
+ item_uri = RDF::Node.new
87
+ end
88
+ statements.concat statements_from_element(item, item_uri)
89
+ end
90
+
91
+
92
+ end
93
+ statements.each do |stmt |
94
+ yield stmt
95
+ end
96
+ statements.to_enum
97
+ end
98
+
99
+ def statements_from_element(elem, resource)
100
+ child_elements = {}
101
+ statements = []
102
+ elem.children.each do |el|
103
+ if el.attribute_with_ns('resource', RDF.to_uri.to_s)
104
+ statements << RDF::Statement.new(:subject=>resource, :predicate=>RDF::URI.intern(el.namespace.href+el.name), :object=>RDF::URI.intern(el.attribute_with_ns('resource', RDF.to_uri.to_s).value))
105
+ elsif all_text_nodes?(el.children)
106
+ statements << RDF::Statement.new(:subject=>resource, :predicate=>RDF::URI.intern(el.namespace.href+el.name),:object=>literal(el.children.first))
107
+ else
108
+ el.children.each do |e|
109
+ if e.attribute_with_ns('about', RDF.to_uri.to_s)
110
+ c = RDF::URI.intern(e.attribute_with_ns('about', RDF.to_uri.to_s).value)
111
+ statements << RDF::Statement.new(:subject=>resource, :predicate=>RDF::URI.intern(el.namespace.href+el.name), :object=>c)
112
+ child_elements[c] = e
113
+ e_type = RDF::URI.intern(e.namespace.href + e.name)
114
+ unless e_type == RDF.Description || RDF::RSS.item
115
+ statements << RDF::Statement.new(:subject=>c, :predicate=>RDF.type, :object=>e_type)
116
+ end
117
+ elsif has_child_elements?(e)
118
+ c = RDF::Node.new
119
+ statements << RDF::Statement.new(:subject=>resource, :predicate=>RDF::URI.intern(el.namespace.href+el.name), :object=>c)
120
+ child_elements[c] = e
121
+ e_type = RDF::URI.intern(e.namespace.href + e.name)
122
+ unless e_type == RDF.Description || RDF::RSS.item
123
+ statements << RDF::Statement.new(:subject=>c, :predicate=>RDF.type, :object=>e_type)
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ child_elements.each_pair do |r,e|
130
+ statements.concat statements_from_element(e, r)
131
+ end
132
+ statements
133
+ end
134
+
135
+ def all_text_nodes?(nodes)
136
+ all_text = true
137
+ nodes.each {|n| all_text = false unless n.is_a?(Nokogiri::XML::Text)}
138
+ all_text
139
+ end
140
+
141
+ def has_child_elements?(elem)
142
+ children = false
143
+ elem.each {|e| children = true if e.is_a?(Nokogiri::XML::Element)}
144
+ children
145
+ end
146
+
147
+ def literal(txt)
148
+ options = {}
149
+ if txt.attribute('lang')
150
+ options[:language] = txt.attribute('lang').value.to_sym
151
+ end
152
+ if txt.attribute_with_ns('datatype', RDF.to_uri.to_s)
153
+ options[:datatype] = RDF::URI.intern(txt.attribute_with_ns('datatype', RDF.to_uri.to_s).value)
154
+ end
155
+ RDF::Literal.new(txt.inner_text, options)
156
+ end
157
+
158
+ def parse_children(elem, stmt)
159
+ old_stmt = nil
160
+ statements = []
161
+ if elem.attribute_with_ns("about", RDF.to_uri.to_s)
162
+ old_stmt = stmt
163
+ stmt = RDF::Statement.new(:subject=>elem.attribute_with_ns("about", RDF.to_uri.to_s).value)
164
+ type_object = RDF::URI.intern(elem.namespace.href+elem.name)
165
+ unless type_object == RDF.Description || type_object == RDF::RSS.item
166
+ stmt.predicate = RDF.type
167
+ stmt.object = type_object
168
+ statements << stmt
169
+ stmt = RDF::Statement.new
170
+ stmt.subject = RDF::URI.intern(elem.attribute_with_ns('about', RDF.to_uri.to_s).value)
171
+ end
172
+ end
173
+ elem.children.each do |el|
174
+ next if el.is_a?(Nokogiri::XML::Text)
175
+ if el.attribute_with_ns('resource', RDF.to_uri.to_s) || el.attribute('resource')
176
+ if el.attribute_with_ns('resource', RDF.to_uri.to_s)
177
+ stmt.object = RDF::URI.intern(el.attribute_with_ns('resource', RDF.to_uri.to_s).value)
178
+ else
179
+ stmt.object = RDF::URI.intern(el.attribute('resource').value)
180
+ end
181
+ stmt.predicate = RDF::URI.intern(el.namespace.href+el.name)
182
+ statements << stmt.dup
183
+ stmt = RDF::Statement.new(:subject=>stmt.subject)
184
+ elsif el.children.length == 1 && el.children.first.is_a?(Nokogiri::XML::Text)
185
+ stmt.predicate = RDF::URI.intern(el.namespace.href+el.name)
186
+ options = {}
187
+ txt = el.children.first
188
+ if txt.attribute('lang')
189
+ options[:language] = txt.attribute('lang').value.to_sym
190
+ end
191
+ if txt.attribute_with_ns('datatype', RDF.to_uri.to_s)
192
+ options[:datatype] = RDF::URI.intern(txt.attribute_with_ns('datatype', RDF.to_uri.to_s).value)
193
+ end
194
+ stmt.object = RDF::Literal.new(txt.inner_text, options)
195
+ statements << stmt.dup
196
+ stmt = RDF::Statement.new(:subject=>stmt.subject)
197
+ else
198
+ stmt_found = false
199
+ el.children.each do |child|
200
+ next unless child.is_a?(Nokogiri::XML::Element)
201
+ stmt.predicate = RDF::URI.intern(el.namespace.href+el.name)
202
+ if child.attribute_with_ns("about", RDF.to_uri.to_s)
203
+ stmt.object = RDF::URI.intern(child.attribute_with_ns("about", RDF.to_uri.to_s).value)
204
+ statements << stmt.dup
205
+ stmt = RDF::Statement.new(:subject=>stmt.subject)
206
+ stmt_found = true
207
+ else
208
+ stmt.object = RDF::Node.new
209
+ statements << stmt.dup
210
+ stmt = RDF::Statement.new(:subject=>stmt.subject)
211
+ stmt_found = true
212
+ end
213
+ end
214
+ puts "#{el.name}: #{el.inspect}" unless stmt_found
215
+ end
216
+
217
+ #puts "#{el.name}: #{el.class.name}"
218
+ statements.concat parse_children(el, stmt) unless el.children.empty?
219
+ end
220
+ if old_stmt
221
+ stmt = old_stmt
222
+ end
223
+ statements
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,87 @@
1
+ module Sasquatch
2
+ class SearchResult < Array
3
+ attr_reader :graph, :total_results, :max_results, :start_index, :search_context
4
+
5
+ def initialize
6
+ @results = []
7
+ end
8
+
9
+ def self.new_from_query(graph, options, store)
10
+ search_result = self.new
11
+ search_result.graph = graph
12
+ search_result.search_context={:store=>store, :options=>options}
13
+ search_result.parse_graph
14
+ search_result
15
+ end
16
+
17
+ def graph=(g)
18
+ @graph = g
19
+ end
20
+
21
+ def search_context=(sc)
22
+ @search_context=sc
23
+ end
24
+
25
+ def parse_graph
26
+ return unless @graph
27
+ @graph.query(:predicate=>RDF::URI.intern("http://a9.com/-/spec/opensearch/1.1/totalResults")).each do |stmt|
28
+ @total_results = stmt.object.value.to_i
29
+ end
30
+ @total_results ||= 0
31
+ @graph.query(:predicate=>RDF::URI.intern("http://a9.com/-/spec/opensearch/1.1/startIndex")).each do |stmt|
32
+ @start_index = stmt.object.value.to_i
33
+ end
34
+ @start_index ||= 0
35
+ @graph.query(:predicate=>RDF::URI.intern("http://a9.com/-/spec/opensearch/1.1/itemsPerPage")).each do |stmt|
36
+ @max_results = stmt.object.value.to_i
37
+ end
38
+ @max_results ||= 0
39
+
40
+ set_results
41
+ end
42
+
43
+ def set_results
44
+ channel_uri = nil
45
+ @graph.query(:predicate=>RDF.type, :object=>RDF::RSS.channel).each do |channel|
46
+ channel_uri = channel.subject
47
+ end
48
+ return unless channel_uri
49
+ seq_uri = nil
50
+ @graph.query(:subject=>channel_uri, :predicate=>RDF::RSS.items).each do |items|
51
+ seq_uri = items.object
52
+ end
53
+ return unless seq_uri
54
+ @graph.query(:subject=>seq_uri).each do |li|
55
+ if li.predicate.to_s =~ /http:\/\/www\.w3\.org\/1999\/02\/22-rdf-syntax-ns#_(\d)+$/
56
+ (ns,idx) = li.predicate.to_s.split("#_")
57
+ idx = (idx.to_i - 1)
58
+ self[idx] = li.object
59
+ end
60
+ end
61
+ end
62
+
63
+ def previous
64
+ if @start_index == 0
65
+ nil
66
+ else
67
+ o = {}
68
+ o[:offset] = @start_index - @max_results
69
+ o[:offset] = 0 if o[:offset] < 0
70
+ o[:sort] = @search_context[:options][:sort]
71
+ o[:max] = @max_results
72
+ @search_context[:store].search(@search_context[:options][:query][:query],o)
73
+ end
74
+ end
75
+ def next
76
+ if (@start_index + self.count) >= @total_results
77
+ nil
78
+ else
79
+ o = {}
80
+ o[:offset] = @start_index + @max_results
81
+ o[:sort] = @search_context[:options][:sort]
82
+ o[:max] = @max_results
83
+ @search_context[:store].search(@search_context[:options][:query][:query],o)
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,120 @@
1
+ module Sasquatch
2
+ class SparqlBuilder < SPARQL::Client::Query
3
+ attr_reader :store, :variables
4
+
5
+ def self.init(store, *variables)
6
+ options = variables.last.is_a?(Hash) ? variables.pop : {}
7
+ self.new(store, options).set_variables(*variables)
8
+ end
9
+ def initialize(store, options={}, &block)
10
+ @store = store
11
+
12
+ super(:sparql, options, &block)
13
+ end
14
+
15
+ def sparql
16
+ @form = "CHANGEME"
17
+ self
18
+ end
19
+
20
+ def set_variables(*vars)
21
+ @variables = vars
22
+ self
23
+ end
24
+ def describe!(graph=:default)
25
+ describe
26
+ execute(graph)
27
+ end
28
+
29
+ def describe
30
+ @form = :describe
31
+ @values = *@variables.flatten.map { |var|
32
+ [var, var.is_a?(RDF::URI) ? var : RDF::Query::Variable.new(var)]
33
+ }
34
+ self
35
+ end
36
+
37
+ def select!(graph=:default)
38
+ select
39
+ execute(graph)
40
+ end
41
+
42
+ def select
43
+ @form = :select
44
+ @values = @variables.flatten.map { |var| [var, RDF::Query::Variable.new(var)] }
45
+ self
46
+ end
47
+
48
+ def ask!(graph=:default)
49
+ ask
50
+ execute(graph)
51
+ end
52
+
53
+ def ask
54
+ @form = :ask
55
+ self
56
+ end
57
+
58
+ def construct!(graph=:default)
59
+ construct
60
+ execute(graph)
61
+ end
62
+
63
+ def construct
64
+ @form = :construct
65
+ options[:template] = build_patterns(*@variables)
66
+ self
67
+ end
68
+
69
+ def execute(graph)
70
+ @store.send(:"sparql_#{@form}", self.to_s, graph)
71
+ end
72
+
73
+ ##
74
+ # Returns the string representation of this query.
75
+ #
76
+ # @return [String]
77
+ def to_s
78
+ buffer = [form.to_s.upcase]
79
+ case form
80
+ when :select, :describe
81
+ buffer << 'DISTINCT' if options[:distinct]
82
+ buffer << 'REDUCED' if options[:reduced]
83
+ buffer << (values.empty? ? '*' : values.map { |v| serialize_value(v[1]) }.join(' '))
84
+ when :construct
85
+ buffer << '{'
86
+ buffer += serialize_patterns(options[:template])
87
+ buffer << '}'
88
+ end
89
+
90
+ buffer << "FROM #{serialize_value(options[:from])}" if options[:from]
91
+
92
+ unless (patterns.empty? && (options[:optionals].nil? || options[:optionals].empty?)) && form == :describe
93
+ buffer << 'WHERE {'
94
+ buffer += serialize_patterns(patterns)
95
+ if options[:optionals]
96
+ options[:optionals].each do |patterns|
97
+ buffer << 'OPTIONAL {'
98
+ buffer += serialize_patterns(patterns)
99
+ buffer << '}'
100
+ end
101
+ end
102
+ if options[:filters]
103
+ buffer += options[:filters].map { |filter| "FILTER(#{filter})" }
104
+ end
105
+ buffer << '}'
106
+ end
107
+
108
+ if options[:order_by]
109
+ buffer << 'ORDER BY'
110
+ buffer += options[:order_by].map { |var| var.is_a?(String) ? var : "?#{var}" }
111
+ end
112
+
113
+ buffer << "OFFSET #{options[:offset]}" if options[:offset]
114
+ buffer << "LIMIT #{options[:limit]}" if options[:limit]
115
+ options[:prefixes].reverse.each {|e| buffer.unshift("PREFIX #{e}") } if options[:prefixes]
116
+
117
+ buffer.join(' ')
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,302 @@
1
+ module Sasquatch
2
+ class Store
3
+ include HTTParty
4
+ default_timeout 30
5
+
6
+ attr_reader :store_name, :last_response, :sparql_clients
7
+ def initialize(storename, options={})
8
+ self.class.base_uri "http://api.talis.com/stores/#{storename}"
9
+ @store_name = storename
10
+ if options[:username]
11
+ set_credentials(options[:username], options[:password])
12
+ end
13
+
14
+ end
15
+
16
+ def set_credentials(username, password)
17
+ @auth = {:username => username, :password => password}
18
+ @auth[:headers] = self.get("/snapshots", {}).headers['www-authenticate']
19
+ end
20
+
21
+ def describe(uri)
22
+ options = {:query=>{:about=>uri, :output=>"ntriples"}}
23
+ @last_response = get("/meta", options)
24
+ graph = parse_ntriples(@last_response.body)
25
+ graph.set_requested_resource(uri)
26
+ graph
27
+ end
28
+
29
+ def describe_multi(uris)
30
+ sparql = "DESCRIBE "
31
+ uris.each {|uri| sparql << "<#{uri}> "}
32
+ options = {:query=>{:query=>sparql, :output=>"ntriples"}}
33
+ @last_response = get("/services/sparql", options)
34
+ graph = parse_ntriples(@last_response.body)
35
+ graph
36
+ end
37
+
38
+ def augment(uri, pattern=:cbd)
39
+ graph = augment_multi([uri], pattern)
40
+ graph.set_requested_resource(uri)
41
+ graph
42
+ end
43
+
44
+ def augment_multi(uris, pattern=:cbd)
45
+ sparql = "DESCRIBE ?o "
46
+ i = 1
47
+ where = []
48
+ uris.each do |uri|
49
+ sparql << "<#{uri}> "
50
+ where << "{<#{uri}> ?p ?o . }"
51
+ i += 1
52
+ end
53
+ sparql << "\nWHERE\n{ #{where.join(" UNION ")} }"
54
+ options = {:body=>{:query=>sparql, :output=>"ntriples"}}
55
+ @last_response = post("/services/sparql", options)
56
+ graph = parse_ntriples(@last_response.body)
57
+ graph
58
+ end
59
+
60
+ def get(path, options)
61
+ self.class.get(path, options)
62
+ end
63
+
64
+ def post(path, options)
65
+ self.class.post(path, options)
66
+ end
67
+
68
+ def save(graph_statement_or_resource, graph_name=nil)
69
+ path = "/meta"
70
+ path << "/graphs/#{graph_name}" if graph_name
71
+ options = {:headers=>{"Content-Type"=> "text/turtle"}, :body=>graph_statement_or_resource.to_ntriples, :digest_auth=>@auth}
72
+ @last_response = post(path, options )
73
+ if @last_response.response.code == "204"
74
+ true
75
+ else
76
+ false
77
+ end
78
+ end
79
+
80
+ def search(query, options={})
81
+ accept = self.class.headers['Accept']
82
+ self.class.headers 'Accept' => 'application/json'
83
+ path = "/items"
84
+ opts = {:query=>options}
85
+ opts[:query][:query] = query
86
+ @last_response = get(path, opts)
87
+ #graph = parse_rss10(@last_response.body)
88
+ graph = parse_json(@last_response.body)
89
+ self.class.headers 'Accept' => accept
90
+ SearchResult.new_from_query(graph, opts, self)
91
+ end
92
+
93
+ def remove_triple(stmt, versioned=false)
94
+ remove_triples([stmt], versioned, creator)
95
+ end
96
+
97
+ def remove_triples(stmts, versioned=false)
98
+ changesets = {}
99
+ stmts.each do |stmt|
100
+ changesets[stmt.subject] ||= Changeset.new(stmt.subject)
101
+ changesets[stmt.subject].remove_statements(stmt)
102
+ end
103
+ graph = RDF::Graph.new
104
+ changesets.each_pair do |uri, changeset|
105
+ changeset.statements.each do |stmt|
106
+ graph << stmt
107
+ end
108
+ end
109
+ send_changeset(graph, versioned, creator)
110
+ end
111
+
112
+ def replace_triple(old_stmt, new_stmt, versioned=false)
113
+ replace_triples({old_triple=>new_triple}, versioned, creator)
114
+ end
115
+
116
+ ##
117
+ # takes a Hash in form of {old_statement=>replacement_statement}
118
+ #
119
+ def replace_triples(changes, versioned=false, creator="sasquatch.rb")
120
+ changesets = {}
121
+ changes.each_pair do |old_stmt, new_stmt|
122
+ changesets[old_stmt.subject] ||= Changeset.new(old_stmt.subject)
123
+ changesets[old_stmt.subject].remove_statements(*old_stmt)
124
+ changesets[new_stmt.subject] ||= Changeset.new(new_stmt.subject)
125
+ changesets[new_stmt.subject].remove_statements(*new_stmt)
126
+ end
127
+ graph = RDF::Graph.new
128
+ changesets.each_pair do |uri, changeset|
129
+ changeset.statements.each do |stmt|
130
+ graph << stmt
131
+ end
132
+ end
133
+ send_changeset(graph, versioned, creator)
134
+ end
135
+
136
+ def replace(graph_statements_or_resource, versioned=false, creator="sasquatch.rb")
137
+ uris = case graph_statements_or_resource.class.name
138
+ when "RDF::Graph"
139
+ subjects = []
140
+ graph_statements_or_resource.each_subject {|s| subjects << s}
141
+ subjects
142
+ when "Array"
143
+ subjects = []
144
+ graph_statements_or_resource.each_subject {|s| subjects << s}
145
+ subjects.uniq
146
+ else
147
+ # This should only work for rdf-rdfobjects Resources
148
+ if graph_statements_or_resource.respond_to?(:predicates)
149
+ [graph_statements_or_resource.to_s]
150
+ end
151
+ end
152
+ raise ArgumentError unless uris
153
+ current_graph = describe_multi(uris)
154
+ changesets = {}
155
+ current_graph.each_statement.each do |stmt|
156
+ changesets[stmt.subject] ||= Changeset.new(stmt.subject)
157
+ changesets[stmt.subject].remove_statements(stmt)
158
+ end
159
+ replacements = case graph_statements_or_resource.class.name
160
+ when "Array" then graph_statements_or_resource
161
+ else
162
+ graph_statements_or_resource.statements
163
+ end
164
+ replacements.each do |stmt|
165
+ changesets[stmt.subject] ||= Changeset.new(stmt.subject)
166
+ changesets[stmt.subject].add_statements(stmt)
167
+ end
168
+ graph = RDF::Graph.new
169
+ changesets.each do |uri, changeset|
170
+ changeset.statements.each do |stmt|
171
+ graph << stmt
172
+ end
173
+ end
174
+ send_changeset(graph, versioned, creator)
175
+ end
176
+
177
+ def send_changeset(graph, versioned=false, creator="sasquatch.rb")
178
+ path = "/meta"
179
+ path << "/changesets" if versioned
180
+
181
+ graph.query(:predicate=>RDF.type, :object=>RDF::Talis::Changeset.ChangeSet).each_subject do |cs|
182
+ graph << [cs, RDF::Talis::Changeset.creatorName, creator]
183
+ end
184
+
185
+ options = {:headers=>{"Content-Type"=> "application/vnd.talis.changeset+turtle"}, :body=>graph.to_ntriples, :digest_auth=>@auth}
186
+ @last_response = post(path, options )
187
+ if !versioned && @last_response.response.code =~ /^20[0-9]$/
188
+ true
189
+ elsif versioned && @last_response.response.code =~ /20[012]/
190
+ true
191
+ else
192
+ false
193
+ end
194
+ end
195
+
196
+ def delete_uri(uri, versioned=false, creator="sasquatch.rb")
197
+ delete_uris([uri], versioned, creator)
198
+ end
199
+
200
+ def delete_uris(uris, versioned=false, creator="sasquatch.rb")
201
+ current_graph = describe_multi(uris)
202
+ changesets = []
203
+ uris.each do |uri|
204
+ u = RDF::URI.intern(uri)
205
+ cs = Changeset.new(u)
206
+ cs.remove_statements(current_graph.query(:subject=>u))
207
+ changesets << cs
208
+ end
209
+ graph = RDF::Graph.new
210
+ changesets.each do |changeset|
211
+ changeset.statements.each do |stmt|
212
+ graph << stmt
213
+ end
214
+ end
215
+ send_changeset(graph, versioned, creator)
216
+ end
217
+
218
+ def sparql(*variables)
219
+ SparqlBuilder.init(self,variables)
220
+ end
221
+
222
+ def sparql_describe(query, graph=:default)
223
+ path = "/services/sparql"
224
+ unless graph == :default
225
+ path << "/graphs/#{graph}"
226
+ end
227
+ options = {:query=>{:query=>query, :output=>'ntriples'}}
228
+ @last_response = get(path, options)
229
+ graph = parse_ntriples(@last_response.body)
230
+ graph
231
+ end
232
+
233
+ alias :sparql_construct :sparql_describe
234
+
235
+ def sparql_select(query, graph=:default)
236
+ path = "/services/sparql"
237
+ unless graph == :default
238
+ path << "/graphs/#{graph}"
239
+ end
240
+ options = {:query=>{:query=>query, :output=>'json'}}
241
+ @last_response = get(path, options)
242
+ SPARQL::Client.parse_json_bindings(@last_response.body) || false
243
+ end
244
+
245
+ alias :sparql_ask :sparql_select
246
+
247
+ def access_status
248
+ accept = self.class.headers['Accept']
249
+ self.class.headers 'Accept' => 'application/json'
250
+ path = "/config/access-status"
251
+ @last_response = get(path, {})
252
+ graph = parse_json(@last_response.body)
253
+ self.class.headers 'Accept' => accept
254
+ graph
255
+ end
256
+
257
+ def read_only?
258
+ access_status.query(:predicate=>RDF::URI.intern('http://schemas.talis.com/2006/bigfoot/configuration#accessMode')).each do |stmt|
259
+ return true if stmt.object == RDF::URI.intern("http://schemas.talis.com/2006/bigfoot/statuses#read-only")
260
+ end
261
+ return false
262
+ end
263
+
264
+ def status_message
265
+ access_status.query(:predicate=>RDF::URI.intern('http://schemas.talis.com/2006/bigfoot/configuration#statusMessage')).each do |stmt|
266
+ return stmt.object.value
267
+ end
268
+ nil
269
+ end
270
+
271
+ def parse_ntriples(body)
272
+ read_graph(body, :ntriples)
273
+ end
274
+
275
+ def parse_json(body)
276
+ read_graph(body, :json)
277
+ end
278
+
279
+ def parse_rss10(body)
280
+ read_graph(body, :rss10)
281
+ end
282
+
283
+ def read_graph(data, format)
284
+ graph = RDF::Graph.new
285
+ RDF::Reader.for(format).new(data) do |reader|
286
+ reader.each_statement do |statement|
287
+ graph << statement
288
+ end
289
+ end
290
+ graph
291
+ end
292
+
293
+ # Parse the response body however you like
294
+ class Parser::Simple < HTTParty::Parser
295
+ def parse
296
+ body
297
+ end
298
+ end
299
+
300
+ parser Parser::Simple
301
+ end
302
+ end
metadata ADDED
@@ -0,0 +1,159 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sasquatch
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Ross Singer
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-04-01 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: httparty
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 11
30
+ segments:
31
+ - 0
32
+ - 7
33
+ - 4
34
+ version: 0.7.4
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: rdf
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 17
46
+ segments:
47
+ - 0
48
+ - 3
49
+ - 1
50
+ version: 0.3.1
51
+ type: :runtime
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: rdf-json
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ hash: 19
62
+ segments:
63
+ - 0
64
+ - 3
65
+ - 0
66
+ version: 0.3.0
67
+ type: :runtime
68
+ version_requirements: *id003
69
+ - !ruby/object:Gem::Dependency
70
+ name: sparql-client
71
+ prerelease: false
72
+ requirement: &id004 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ hash: 13
78
+ segments:
79
+ - 0
80
+ - 0
81
+ - 9
82
+ version: 0.0.9
83
+ type: :runtime
84
+ version_requirements: *id004
85
+ - !ruby/object:Gem::Dependency
86
+ name: rspec
87
+ prerelease: false
88
+ requirement: &id005 !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ hash: 11
94
+ segments:
95
+ - 2
96
+ - 1
97
+ - 0
98
+ version: 2.1.0
99
+ type: :development
100
+ version_requirements: *id005
101
+ description: A highly opinionated Ruby client for the Talis Platform using HTTParty and RDF.rb
102
+ email: ross.singer@talis.com
103
+ executables: []
104
+
105
+ extensions: []
106
+
107
+ extra_rdoc_files: []
108
+
109
+ files:
110
+ - README
111
+ - VERSION
112
+ - lib/sasquatch/changeset.rb
113
+ - lib/sasquatch/http_party.rb
114
+ - lib/sasquatch/rdf.rb
115
+ - lib/sasquatch/rss10/format.rb
116
+ - lib/sasquatch/rss10/reader.rb
117
+ - lib/sasquatch/rss10.rb
118
+ - lib/sasquatch/search_result.rb
119
+ - lib/sasquatch/sparql_builder.rb
120
+ - lib/sasquatch/store.rb
121
+ - lib/sasquatch.rb
122
+ has_rdoc: false
123
+ homepage: http://github.com/rsinger/sasquatch
124
+ licenses:
125
+ - MIT
126
+ post_install_message:
127
+ rdoc_options: []
128
+
129
+ require_paths:
130
+ - lib
131
+ required_ruby_version: !ruby/object:Gem::Requirement
132
+ none: false
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ hash: 57
137
+ segments:
138
+ - 1
139
+ - 8
140
+ - 7
141
+ version: 1.8.7
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ none: false
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ hash: 3
148
+ segments:
149
+ - 0
150
+ version: "0"
151
+ requirements: []
152
+
153
+ rubyforge_project:
154
+ rubygems_version: 1.3.7
155
+ signing_key:
156
+ specification_version: 3
157
+ summary: A highly opinionated Ruby client for the Talis Platform.
158
+ test_files: []
159
+