sasquatch 0.0.1

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.
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
+