sasquatch 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +57 -0
- data/VERSION +1 -0
- data/lib/sasquatch.rb +16 -0
- data/lib/sasquatch/changeset.rb +66 -0
- data/lib/sasquatch/http_party.rb +12 -0
- data/lib/sasquatch/rdf.rb +31 -0
- data/lib/sasquatch/rss10.rb +18 -0
- data/lib/sasquatch/rss10/format.rb +12 -0
- data/lib/sasquatch/rss10/reader.rb +226 -0
- data/lib/sasquatch/search_result.rb +87 -0
- data/lib/sasquatch/sparql_builder.rb +120 -0
- data/lib/sasquatch/store.rb +302 -0
- metadata +159 -0
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
|
data/lib/sasquatch.rb
ADDED
@@ -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,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
|
+
|