summon 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/.specification +58 -0
  2. data/History.txt +4 -0
  3. data/Manifest.txt +52 -0
  4. data/PostInstall.txt +4 -0
  5. data/README.rdoc +77 -0
  6. data/Rakefile +25 -0
  7. data/bin/summon +10 -0
  8. data/bin/summonh +19 -0
  9. data/ispec/integration_spec.rb +61 -0
  10. data/lib/summon.rb +41 -0
  11. data/lib/summon/cli.rb +136 -0
  12. data/lib/summon/log.rb +40 -0
  13. data/lib/summon/schema.rb +109 -0
  14. data/lib/summon/schema/availability.rb +14 -0
  15. data/lib/summon/schema/citation.rb +11 -0
  16. data/lib/summon/schema/date.rb +23 -0
  17. data/lib/summon/schema/document.rb +84 -0
  18. data/lib/summon/schema/error.rb +4 -0
  19. data/lib/summon/schema/facet.rb +51 -0
  20. data/lib/summon/schema/query.rb +96 -0
  21. data/lib/summon/schema/range.rb +52 -0
  22. data/lib/summon/schema/search.rb +37 -0
  23. data/lib/summon/schema/suggestion.rb +5 -0
  24. data/lib/summon/service.rb +44 -0
  25. data/lib/summon/transport.rb +13 -0
  26. data/lib/summon/transport/canned.json +2327 -0
  27. data/lib/summon/transport/canned.rb +9 -0
  28. data/lib/summon/transport/errors.rb +12 -0
  29. data/lib/summon/transport/headers.rb +55 -0
  30. data/lib/summon/transport/http.rb +114 -0
  31. data/lib/summon/transport/qstring.rb +49 -0
  32. data/script/console +10 -0
  33. data/script/destroy +14 -0
  34. data/script/generate +14 -0
  35. data/spec/spec.opts +1 -0
  36. data/spec/spec_helper.rb +19 -0
  37. data/spec/summon/log_spec.rb +28 -0
  38. data/spec/summon/schema/availability_spec.rb +31 -0
  39. data/spec/summon/schema/citation_spec.rb +34 -0
  40. data/spec/summon/schema/date_spec.rb +12 -0
  41. data/spec/summon/schema/document_spec.rb +235 -0
  42. data/spec/summon/schema/facet_spec.rb +115 -0
  43. data/spec/summon/schema/query_spec.rb +163 -0
  44. data/spec/summon/schema/range_spec.rb +45 -0
  45. data/spec/summon/schema/search_spec.rb +62 -0
  46. data/spec/summon/schema_spec.rb +143 -0
  47. data/spec/summon/service_spec.rb +18 -0
  48. data/spec/summon/transport/headers_spec.rb +47 -0
  49. data/spec/summon/transport/http_spec.rb +29 -0
  50. data/spec/summon/transport/qstring_spec.rb +24 -0
  51. data/spec/summon_spec.rb +48 -0
  52. data/summon.gemspec +41 -0
  53. metadata +145 -0
@@ -0,0 +1,9 @@
1
+ module Summon
2
+ module Transport
3
+ class Canned
4
+ def get(*args)
5
+ JSON.parse(File.read(File.dirname(__FILE__) + '/canned.json'))
6
+ end
7
+ end
8
+ end
9
+ end
@@ -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
@@ -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("&", "&amp;").
9
+ gsub("<", "&lt;").
10
+ gsub(">", "&gt;") +
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