summon 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.specification +58 -0
- data/History.txt +4 -0
- data/Manifest.txt +52 -0
- data/PostInstall.txt +4 -0
- data/README.rdoc +77 -0
- data/Rakefile +25 -0
- data/bin/summon +10 -0
- data/bin/summonh +19 -0
- data/ispec/integration_spec.rb +61 -0
- data/lib/summon.rb +41 -0
- data/lib/summon/cli.rb +136 -0
- data/lib/summon/log.rb +40 -0
- data/lib/summon/schema.rb +109 -0
- data/lib/summon/schema/availability.rb +14 -0
- data/lib/summon/schema/citation.rb +11 -0
- data/lib/summon/schema/date.rb +23 -0
- data/lib/summon/schema/document.rb +84 -0
- data/lib/summon/schema/error.rb +4 -0
- data/lib/summon/schema/facet.rb +51 -0
- data/lib/summon/schema/query.rb +96 -0
- data/lib/summon/schema/range.rb +52 -0
- data/lib/summon/schema/search.rb +37 -0
- data/lib/summon/schema/suggestion.rb +5 -0
- data/lib/summon/service.rb +44 -0
- data/lib/summon/transport.rb +13 -0
- data/lib/summon/transport/canned.json +2327 -0
- data/lib/summon/transport/canned.rb +9 -0
- data/lib/summon/transport/errors.rb +12 -0
- data/lib/summon/transport/headers.rb +55 -0
- data/lib/summon/transport/http.rb +114 -0
- data/lib/summon/transport/qstring.rb +49 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/summon/log_spec.rb +28 -0
- data/spec/summon/schema/availability_spec.rb +31 -0
- data/spec/summon/schema/citation_spec.rb +34 -0
- data/spec/summon/schema/date_spec.rb +12 -0
- data/spec/summon/schema/document_spec.rb +235 -0
- data/spec/summon/schema/facet_spec.rb +115 -0
- data/spec/summon/schema/query_spec.rb +163 -0
- data/spec/summon/schema/range_spec.rb +45 -0
- data/spec/summon/schema/search_spec.rb +62 -0
- data/spec/summon/schema_spec.rb +143 -0
- data/spec/summon/service_spec.rb +18 -0
- data/spec/summon/transport/headers_spec.rb +47 -0
- data/spec/summon/transport/http_spec.rb +29 -0
- data/spec/summon/transport/qstring_spec.rb +24 -0
- data/spec/summon_spec.rb +48 -0
- data/summon.gemspec +41 -0
- metadata +145 -0
@@ -0,0 +1,12 @@
|
|
1
|
+
module Summon::Transport
|
2
|
+
|
3
|
+
class TransportError < StandardError; end
|
4
|
+
|
5
|
+
class AuthorizationError < TransportError; end
|
6
|
+
|
7
|
+
class ServiceError < TransportError; end
|
8
|
+
|
9
|
+
class RequestError < TransportError; end
|
10
|
+
|
11
|
+
class UnknownResponseError < TransportError; end
|
12
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'uri'
|
3
|
+
require 'openssl'
|
4
|
+
require 'sha1'
|
5
|
+
require 'base64'
|
6
|
+
|
7
|
+
module Summon::Transport
|
8
|
+
class Headers < Hash
|
9
|
+
include Qstring
|
10
|
+
def initialize(options = {})
|
11
|
+
@params = options[:params] || {}
|
12
|
+
@url = options[:url]
|
13
|
+
@uri = URI.parse(@url)
|
14
|
+
@access_id = options[:access_id]
|
15
|
+
@client_key = options[:client_key]
|
16
|
+
@secret_key = options[:secret_key]
|
17
|
+
@session_id = options[:session_id]
|
18
|
+
@log = Summon::Log.new(options[:log])
|
19
|
+
@accept = "application/#{options[:accept] || 'json'}"
|
20
|
+
@time = Time.now.httpdate
|
21
|
+
validate!
|
22
|
+
merge!({
|
23
|
+
"Content-Type" => "application/x-www-form-urlencoded; charset=utf8",
|
24
|
+
"Accept" => @accept,
|
25
|
+
"x-summon-date" => @time,
|
26
|
+
"x-summon-session-id" => @session_id,
|
27
|
+
"Authorization" => "Summon #{[@access_id, @client_key, digest].reject {|o| o.nil?}.join(';')}",
|
28
|
+
"Host" => "#{@uri.host}:#{@uri.port}"
|
29
|
+
}).reject! {|k,v| v.nil?}
|
30
|
+
@log.debug {
|
31
|
+
"#{self.class.name}\n#{self.map {|k,v| "#{k}: #{v}"}.join("\n")}"
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def digest
|
36
|
+
id = [@accept, @time, "#{@uri.host}:#{@uri.port}", @uri.path, qstr].join("\n") + "\n"
|
37
|
+
Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::SHA1.new, @secret_key, id)).chomp.tap {|digest|
|
38
|
+
# @log.debug {"ID: #{id.inspect}"}
|
39
|
+
# @log.debug {"DIGEST: #{digest}\n"}
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def qstr
|
44
|
+
to_query_string(@params, false)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def validate!
|
50
|
+
raise AuthorizationError, "No Access ID specified" if @access_id.nil?
|
51
|
+
raise AuthorizationError, "Secret Key not provided for Access ID: #{@access_id}" if @secret_key.nil?
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'net/http'
|
3
|
+
|
4
|
+
module Summon::Transport
|
5
|
+
class Http
|
6
|
+
include Qstring
|
7
|
+
|
8
|
+
DEFAULTS = {:url => "http://api.summon.serialssolutions.com"}
|
9
|
+
|
10
|
+
def initialize(options = {:url => nil, :access_id => nil, :secret_key => nil, :client_key => nil, :session_id => nil, :log => nil})
|
11
|
+
@options = DEFAULTS.merge options
|
12
|
+
@access_id = @options[:access_id]
|
13
|
+
@secret_key = @options[:secret_key]
|
14
|
+
@client_key = @options[:client_key]
|
15
|
+
@session_id = @options[:session_id] || "SUMMON-SESSION-#{Socket.gethostname}-#{$$}-#{sidalloc}"
|
16
|
+
@url = @options[:url]
|
17
|
+
@log = Summon::Log.new(options[:log])
|
18
|
+
end
|
19
|
+
|
20
|
+
def get(path, params = {})
|
21
|
+
session_id = params["s.session.id"]
|
22
|
+
params.delete "s.session.id"
|
23
|
+
urlget "#{@url}#{path}?#{to_query_string(params, true)}", params, session_id
|
24
|
+
end
|
25
|
+
|
26
|
+
def urlget(url, params = nil, session_id = nil)
|
27
|
+
uri = URI.parse url
|
28
|
+
params ||= from_query_string(uri.query)
|
29
|
+
session_id ||= @session_id
|
30
|
+
headers = Headers.new(
|
31
|
+
:url => url,
|
32
|
+
:params => params,
|
33
|
+
:access_id => @access_id,
|
34
|
+
:secret_key => @secret_key,
|
35
|
+
:client_key => @client_key,
|
36
|
+
:session_id => session_id,
|
37
|
+
:log => @log
|
38
|
+
)
|
39
|
+
|
40
|
+
@log.info("ruby-summon:transport") {
|
41
|
+
"GET: #{url}"
|
42
|
+
}
|
43
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
44
|
+
http.start do
|
45
|
+
get = Net::HTTP::Get.new("#{uri.path}#{'?' + uri.query if uri.query && uri.query != ''}")
|
46
|
+
get.merge! headers
|
47
|
+
http.request(get) do |response|
|
48
|
+
case response
|
49
|
+
when Net::HTTPSuccess
|
50
|
+
return parse(response)
|
51
|
+
when Net::HTTPUnauthorized
|
52
|
+
raise AuthorizationError, status(response)
|
53
|
+
when Net::HTTPClientError
|
54
|
+
raise RequestError, error(response)
|
55
|
+
when Net::HTTPServerError
|
56
|
+
raise ServiceError, error(response)
|
57
|
+
else
|
58
|
+
raise UnknownResponseError, "Unknown response: #{response}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def parse(response)
|
65
|
+
case response.content_type
|
66
|
+
when "application/json"
|
67
|
+
JSON.parse(response.body).tap do |json|
|
68
|
+
@log.debug("ruby-summon::transport") { "JSON RESPONSE: #{json.inspect}" }
|
69
|
+
end
|
70
|
+
|
71
|
+
when "text/plain"
|
72
|
+
response.body.tap do |text|
|
73
|
+
@log.debug("ruby-summon::transport") { "TEXT RESPONSE: #{text.inspect}" }
|
74
|
+
end
|
75
|
+
|
76
|
+
else
|
77
|
+
raise ServiceError, "service returned unexpected #{response.content_type} : #{response.body}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def error(response)
|
82
|
+
case response.content_type
|
83
|
+
when "application/json"
|
84
|
+
JSON.parse(response.body)["errors"].first["message"]
|
85
|
+
else
|
86
|
+
status(response)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def status(response)
|
91
|
+
"#{response.code}: #{response.message}"
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
@sessions = 0
|
97
|
+
|
98
|
+
def sidalloc
|
99
|
+
self.class.instance_eval do
|
100
|
+
@sessions += 1
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
module Net::HTTPHeader
|
109
|
+
def merge!(hash)
|
110
|
+
for k, v in hash
|
111
|
+
self[k] = v
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
|
2
|
+
require 'cgi'
|
3
|
+
|
4
|
+
module Summon::Transport
|
5
|
+
module Qstring
|
6
|
+
|
7
|
+
def to_query_string(hash, urlencode = true)
|
8
|
+
hash.reject {|k,v| v.nil? || v == ''}.inject([]) do |qs,pair|
|
9
|
+
qs.tap do
|
10
|
+
k,v = pair
|
11
|
+
if v.is_a?(Array)
|
12
|
+
for el in v
|
13
|
+
qs << encode_param(k, el, urlencode)
|
14
|
+
end
|
15
|
+
else
|
16
|
+
qs << encode_param(k, v, urlencode)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end.reject{|o| o.nil? || o.empty?}.sort.join('&')
|
20
|
+
end
|
21
|
+
|
22
|
+
def urlencode(str)
|
23
|
+
str.gsub(/[^a-zA-Z0-9_\.\-]/n) {|s| sprintf('%%%02x', s[0]) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def urldecode(str)
|
27
|
+
CGI.unescape(str)
|
28
|
+
end
|
29
|
+
|
30
|
+
def encode_param(k, v, do_urlencode)
|
31
|
+
"#{k.to_s}=#{do_urlencode ? urlencode(v.to_s) : v.to_s}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def from_query_string(qstr)
|
35
|
+
qstr ||= ""
|
36
|
+
{}.tap do |result|
|
37
|
+
for param in qstr.split('&')
|
38
|
+
m,k,v = *param.match(/^(.*?)=(.*?)$/)
|
39
|
+
if current = result[k]
|
40
|
+
result[k] = current.to_a
|
41
|
+
result[k] << urldecode(v)
|
42
|
+
else
|
43
|
+
result[k] = urldecode(v)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/script/console
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# File: script/console
|
3
|
+
irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
|
4
|
+
|
5
|
+
libs = " -r irb/completion -rubygems"
|
6
|
+
# Perhaps use a console_lib to store any extra methods I may want available in the cosole
|
7
|
+
# libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
|
8
|
+
libs << " -r #{File.dirname(__FILE__) + '/../lib/summon.rb'}"
|
9
|
+
puts "Loading summon gem"
|
10
|
+
exec "#{irb} #{libs} --simple-prompt"
|
data/script/destroy
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/destroy'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Destroy.new.run(ARGV)
|
data/script/generate
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/generate'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Generate.new.run(ARGV)
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../lib/summon')
|
2
|
+
gem 'rspec'
|
3
|
+
require 'spec'
|
4
|
+
|
5
|
+
def pre(text)
|
6
|
+
puts "<pre>" +
|
7
|
+
text.to_s.
|
8
|
+
gsub("&", "&").
|
9
|
+
gsub("<", "<").
|
10
|
+
gsub(">", ">") +
|
11
|
+
"</pre>"
|
12
|
+
end
|
13
|
+
|
14
|
+
class Object
|
15
|
+
def remove_src
|
16
|
+
@src = nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
3
|
+
|
4
|
+
describe Summon::Log do
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
@logger = stub(:Logger)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "passes through all methods to the underlying logger" do
|
11
|
+
log = Summon::Log.new(@logger)
|
12
|
+
@logger.should_receive(:info).with("foo")
|
13
|
+
log.info("foo")
|
14
|
+
end
|
15
|
+
|
16
|
+
it "creates a stderr logger when no spec is provided" do
|
17
|
+
Logger.should_receive(:new).with($stderr).and_return(@logger)
|
18
|
+
@logger.should_receive(:level=).with(Logger::WARN)
|
19
|
+
log = Summon::Log.new
|
20
|
+
end
|
21
|
+
|
22
|
+
it "creates a logger from an option hash when the spec is a set of options" do
|
23
|
+
out = stub(:OutputStream)
|
24
|
+
Logger.should_receive(:new).with(out).and_return(@logger)
|
25
|
+
@logger.should_receive(:level=).with(Logger::ERROR)
|
26
|
+
log = Summon::Log.new(:level => :error, :initialize => [out])
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
2
|
+
|
3
|
+
describe Summon::Availability do
|
4
|
+
it "should map" do
|
5
|
+
availabilities = Summon::Availability.parse_results("Result"=>{"RecordSummary"=>[
|
6
|
+
{
|
7
|
+
"ID"=>"1038053",
|
8
|
+
"RecordList"=>"http://api.staging.summon.serialssolutions.com:8093/status?key=HD3UV5FG9E&output=application%2Fjson&action=list&token=1038053",
|
9
|
+
"Record"=>{"Status"=>"unknown", "StatusMessage"=>"Available", "Location"=>"Architecture Library", "CallNumber"=>" 720.979 A534"},
|
10
|
+
"RecordCount"=>1},
|
11
|
+
{
|
12
|
+
"ID"=>"1038082",
|
13
|
+
"RecordList"=>"http://api.staging.summon.serialssolutions.com:8093/status?key=HD3UV5FG9E&output=application%2Fjson&action=list&token=1038082",
|
14
|
+
"Record"=>{"Status"=>"unknown", "StatusMessage"=>"Available", "Location"=>"Main Library", "CallNumber"=>" 628.5 B6157"},
|
15
|
+
"RecordCount"=>1}]})
|
16
|
+
|
17
|
+
one, two = availabilities
|
18
|
+
|
19
|
+
one.token.should == "1038053"
|
20
|
+
one.status.should == "unknown"
|
21
|
+
one.status_message.should == "Available"
|
22
|
+
one.location.should == "Architecture Library"
|
23
|
+
one.call_number.should == " 720.979 A534"
|
24
|
+
|
25
|
+
two.token.should == "1038082"
|
26
|
+
two.status.should == "unknown"
|
27
|
+
two.status_message.should == "Available"
|
28
|
+
two.location.should == "Main Library"
|
29
|
+
two.call_number.should == " 628.5 B6157"
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
2
|
+
|
3
|
+
describe Summon::Citation do
|
4
|
+
it "should map" do
|
5
|
+
results = {"Results"=>{"Citations"=>{"Citation"=>[
|
6
|
+
{
|
7
|
+
"Name"=>"American Medical Association, 10th Edition",
|
8
|
+
"Text"=>"Morrow BH, Price VB. <span class=\"italic\">Anasazi architecture and American design</span>. 1st ed. ed. Albuquerque: University of New Mexico Press; Wed. www.magnolia.com.",
|
9
|
+
"ShortName"=>"AMA"},
|
10
|
+
{
|
11
|
+
"Label"=>"Chicago 15th Edition (Notes & Bibliography)",
|
12
|
+
"Text"=>"Morrow, Baker H. and V. B. Price. Anasazi Architecture and American Design. 1st ed. ed. Albuquerque: University of New Mexico Press, Wed, (accessed August 5, 2009).",
|
13
|
+
"ShortName"=>"Chicago NB"},
|
14
|
+
{
|
15
|
+
"Label"=>"MLA",
|
16
|
+
"Text"=>"Morrow, Baker H., and V. B. Price. Anasazi Architecture and American Design. 1st ed. ed. Albuquerque: University of New Mexico Press, Wed. . 5 Aug. 2009",
|
17
|
+
"Caption"=>"Modern Language Association, 6th Edition",
|
18
|
+
"ShortName"=>"MLA"},
|
19
|
+
{
|
20
|
+
"Label"=>"Vancouver",
|
21
|
+
"Text"=>"Morrow BH, Price VB. Anasazi architecture and American design. 1st ed. ed. Albuquerque: University of New Mexico Press; Wed.",
|
22
|
+
"ShortName"=>"Vancouver"}
|
23
|
+
]}, "Timing"=>{"ElapsedQueryTime"=>16, "TotalQueryTime"=>14, "TotalExecutionTime"=>1938}}}
|
24
|
+
|
25
|
+
citations = Summon::Citation.parse_results(results)
|
26
|
+
citations[0].name.should == "American Medical Association, 10th Edition"
|
27
|
+
citations[0].text.should == "Morrow BH, Price VB. <span class=\"italic\">Anasazi architecture and American design</span>. 1st ed. ed. Albuquerque: University of New Mexico Press; Wed. www.magnolia.com."
|
28
|
+
citations[0].short_name.should == "AMA"
|
29
|
+
|
30
|
+
citations[2].name.should == nil
|
31
|
+
citations[2].label.should == "MLA"
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
2
|
+
|
3
|
+
describe Summon::Date do
|
4
|
+
it "should do stuff w/ date" do
|
5
|
+
date = Summon::Date.new({"month"=>"01", "text"=>"c2000.", "day"=>"02", "year"=>"2000"})
|
6
|
+
|
7
|
+
date.day.should == 2
|
8
|
+
date.month.should == 1
|
9
|
+
date.year.should == 2000
|
10
|
+
date.text.should == "c2000."
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,235 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
2
|
+
|
3
|
+
describe Summon::Document do
|
4
|
+
it "should map" do
|
5
|
+
doc = Summon::Document.new(JSON.parse(EXAMPLE_DOCUMENT_JSON))
|
6
|
+
doc.remove_src
|
7
|
+
doc.publication_date.remove_src
|
8
|
+
doc.authors.each {|a| a.remove_src }
|
9
|
+
doc.to_yaml.should == EXPECTED_DOCUMENT_YAML
|
10
|
+
end
|
11
|
+
|
12
|
+
EXAMPLE_DOCUMENT_JSON = <<-JSON
|
13
|
+
{
|
14
|
+
"Publisher_xml": [
|
15
|
+
{
|
16
|
+
"name": "Spirulina Records"
|
17
|
+
},
|
18
|
+
{
|
19
|
+
"name": "Swingsistersound"
|
20
|
+
}
|
21
|
+
],
|
22
|
+
"Abstract": ["This is the most awesome document ever"],
|
23
|
+
"DOI": [ "10.1109\/CBMS.2008.1"],
|
24
|
+
"ISSN": ["1063-7125", "0000-1111"],
|
25
|
+
"Issue": ["7"],
|
26
|
+
"PublicationTitle": ["Batman Books"],
|
27
|
+
"StartPage": ["pp23"],
|
28
|
+
"SubjectTerms": [
|
29
|
+
"Women's music",
|
30
|
+
"Popular music",
|
31
|
+
"Rock music"
|
32
|
+
],
|
33
|
+
"EndPage": ["i"],
|
34
|
+
"TableOfContents": [
|
35
|
+
"The very thing -- Dark night -- 2 truths & uh lie -- Reckless -- Spin -- Free -- Real of you -- Fire -- Purple hair -- Ghost -- Nuthin' -- Faith."
|
36
|
+
],
|
37
|
+
"XQueryRevision": [
|
38
|
+
"Rev: 6229"
|
39
|
+
],
|
40
|
+
"inHoldings": false,
|
41
|
+
"DBID": [
|
42
|
+
"GXQ"
|
43
|
+
],
|
44
|
+
"Notes": [
|
45
|
+
"Compact disc."
|
46
|
+
],
|
47
|
+
"PublicationDateYear": [
|
48
|
+
"2000"
|
49
|
+
],
|
50
|
+
"hasFullText": false,
|
51
|
+
"timestamp": [
|
52
|
+
"Thu Jul 09 19:44:12 EDT 2009"
|
53
|
+
],
|
54
|
+
"Author": [
|
55
|
+
"Hunter, Lisa"
|
56
|
+
],
|
57
|
+
"PublicationDate": [
|
58
|
+
"c2000."
|
59
|
+
],
|
60
|
+
"Subtitle": [
|
61
|
+
"the life and death of Joey Stefano"
|
62
|
+
],
|
63
|
+
"Title": [
|
64
|
+
"Lisa Hunter -- alive"
|
65
|
+
],
|
66
|
+
"ID": [
|
67
|
+
"gvsu_catalog_b16644323"
|
68
|
+
],
|
69
|
+
"LCCallNum": [
|
70
|
+
"M1630.18 .H95 2000"
|
71
|
+
],
|
72
|
+
"Language": [
|
73
|
+
"English"
|
74
|
+
],
|
75
|
+
"PublicationDateCentury": [
|
76
|
+
"2000"
|
77
|
+
],
|
78
|
+
"PublicationDateDecade": [
|
79
|
+
"2000"
|
80
|
+
],
|
81
|
+
"openUrl": "ctx_ver=Z39.88-2004&rfr_id=info:sid\/summon.serialssolutions.com&rft_val_fmt=info:ofi\/fmt:kev:mtx:dc&rft.title=Lisa+Hunter+--+alive&rft.creator=Hunter%2C+Lisa&rft.date=c200-0.&rft.pub=Spirulina+Records&rft.externalDBID=n%2Fa&rft.externalDocID=b16644323",
|
82
|
+
"Author_xml": [
|
83
|
+
{
|
84
|
+
"fullname": "Hunter, Lisa",
|
85
|
+
"surname": "Hunter",
|
86
|
+
"givenname": "Lisa"
|
87
|
+
}
|
88
|
+
],
|
89
|
+
"Library": [
|
90
|
+
"Women's Center Library"
|
91
|
+
],
|
92
|
+
"PublicationDate_xml": [
|
93
|
+
{
|
94
|
+
"month": "01",
|
95
|
+
"text": "c2000.",
|
96
|
+
"day": "02",
|
97
|
+
"year": "2000"
|
98
|
+
}
|
99
|
+
],
|
100
|
+
"PublicationPlace": [
|
101
|
+
"S.l.",
|
102
|
+
"Ann Arbor, Mich"
|
103
|
+
],
|
104
|
+
"TemporalSubjectTerms": [
|
105
|
+
"1991-2000"
|
106
|
+
],
|
107
|
+
"PublicationPlace_xml": [
|
108
|
+
{
|
109
|
+
"name": "S.l."
|
110
|
+
},
|
111
|
+
{
|
112
|
+
"name": "Ann Arbor, Mich"
|
113
|
+
}
|
114
|
+
],
|
115
|
+
"DocumentTitleAlternate": [
|
116
|
+
"Alive"
|
117
|
+
],
|
118
|
+
"score": [
|
119
|
+
"1.0"
|
120
|
+
],
|
121
|
+
"Snippet": [
|
122
|
+
"This is the snippet"
|
123
|
+
],
|
124
|
+
"availabilityToken": "b16644323",
|
125
|
+
"ContentType": [
|
126
|
+
"Audio Recording"
|
127
|
+
],
|
128
|
+
"Publisher": [
|
129
|
+
"Spirulina Records",
|
130
|
+
"Swingsistersound"
|
131
|
+
],
|
132
|
+
"PageCount": [
|
133
|
+
"xxviii, 140 p."
|
134
|
+
],
|
135
|
+
"Genre": [
|
136
|
+
"Biography",
|
137
|
+
"Congress"
|
138
|
+
],
|
139
|
+
"ISBN": [
|
140
|
+
"0849343763 (v. 1)",
|
141
|
+
"0849343771 (v. 2)"
|
142
|
+
],
|
143
|
+
"PublicationSeriesTitle": [
|
144
|
+
"A Bantam book"
|
145
|
+
],
|
146
|
+
"DissertationAdvisor": [
|
147
|
+
"Claudio Friedmann"
|
148
|
+
],
|
149
|
+
"DissertationCategory": [
|
150
|
+
"Education"
|
151
|
+
],
|
152
|
+
"DissertationDegree": [
|
153
|
+
"M.S.J."
|
154
|
+
],
|
155
|
+
"DissertationDegreeDate": [
|
156
|
+
"2001"
|
157
|
+
],
|
158
|
+
"DissertationDegreeDateCentury": [
|
159
|
+
"2000"
|
160
|
+
],
|
161
|
+
"DissertationDegreeDateDecade":[
|
162
|
+
"2000"
|
163
|
+
],
|
164
|
+
"DissertationDegreeDateYear": [
|
165
|
+
"2001"
|
166
|
+
],
|
167
|
+
"DissertationSchool": [
|
168
|
+
"West Virginia University"
|
169
|
+
]
|
170
|
+
}
|
171
|
+
JSON
|
172
|
+
|
173
|
+
EXPECTED_DOCUMENT_YAML = <<-YAML
|
174
|
+
--- !ruby/object:Summon::Document
|
175
|
+
abstract: This is the most awesome document ever
|
176
|
+
authors:
|
177
|
+
- Hunter, Lisa
|
178
|
+
availability_token: b16644323
|
179
|
+
call_number: M1630.18 .H95 2000
|
180
|
+
content_type: Audio Recording
|
181
|
+
dissertation_advisor: Claudio Friedmann
|
182
|
+
dissertation_category: Education
|
183
|
+
dissertation_degree: M.S.J.
|
184
|
+
dissertation_degree_date: "2001"
|
185
|
+
dissertation_degree_date_century: "2000"
|
186
|
+
dissertation_degree_date_decade: "2000"
|
187
|
+
dissertation_degree_date_year: "2001"
|
188
|
+
dissertation_school: West Virginia University
|
189
|
+
doi: 10.1109\/CBMS.2008.1
|
190
|
+
end_page: i
|
191
|
+
fulltext: false
|
192
|
+
genres:
|
193
|
+
- Biography
|
194
|
+
- Congress
|
195
|
+
id: gvsu_catalog_b16644323
|
196
|
+
isbns:
|
197
|
+
- 0849343763 (v. 1)
|
198
|
+
- 0849343771 (v. 2)
|
199
|
+
issns:
|
200
|
+
- 1063-7125
|
201
|
+
- 0000-1111
|
202
|
+
issue: "7"
|
203
|
+
languages:
|
204
|
+
- English
|
205
|
+
library: Women's Center Library
|
206
|
+
open_url: ctx_ver=Z39.88-2004&rfr_id=info:sid/summon.serialssolutions.com&rft_val_fmt=info:ofi/fmt:kev:mtx:dc&rft.title=Lisa+Hunter+--+alive&rft.creator=Hunter%2C+Lisa&rft.date=c200-0.&rft.pub=Spirulina+Records&rft.externalDBID=n%2Fa&rft.externalDocID=b16644323
|
207
|
+
page_count: xxviii, 140 p.
|
208
|
+
patent_number:
|
209
|
+
publication_date: !ruby/object:Summon::Date
|
210
|
+
day: "02"
|
211
|
+
month: "01"
|
212
|
+
src:
|
213
|
+
text: c2000.
|
214
|
+
year: "2000"
|
215
|
+
publication_series_title: A Bantam book
|
216
|
+
publication_title: Batman Books
|
217
|
+
publishers:
|
218
|
+
- Spirulina Records
|
219
|
+
- Swingsistersound
|
220
|
+
snippet: This is the snippet
|
221
|
+
src:
|
222
|
+
start_page: pp23
|
223
|
+
subject_terms:
|
224
|
+
- Women's music
|
225
|
+
- Popular music
|
226
|
+
- Rock music
|
227
|
+
subtitle: the life and death of Joey Stefano
|
228
|
+
thumbnail_large:
|
229
|
+
thumbnail_medium:
|
230
|
+
thumbnail_small:
|
231
|
+
title: Lisa Hunter -- alive
|
232
|
+
url:
|
233
|
+
volume:
|
234
|
+
YAML
|
235
|
+
end
|