evergreen-ils 0.2.0
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.
- checksums.yaml +7 -0
- data/.reek.yml +23 -0
- data/.rspec +3 -0
- data/.rubocop.yml +12 -0
- data/.tool-versions +1 -0
- data/Gemfile +28 -0
- data/Gemfile.lock +180 -0
- data/README.md +68 -0
- data/Rakefile +8 -0
- data/Steepfile +18 -0
- data/evergreen-ils.gemspec +38 -0
- data/lib/evergreen/bib_record.rb +41 -0
- data/lib/evergreen/configuration.rb +35 -0
- data/lib/evergreen/connection.rb +20 -0
- data/lib/evergreen/idl.rb +71 -0
- data/lib/evergreen/idl_object.rb +28 -0
- data/lib/evergreen/mixins/anonymous_pcrud.rb +22 -0
- data/lib/evergreen/mixins/retrieval_methods.rb +13 -0
- data/lib/evergreen/version.rb +5 -0
- data/lib/evergreen.rb +24 -0
- data/lib/opensrf/class_and_data.rb +28 -0
- data/lib/opensrf/http_translator_request.rb +53 -0
- data/sig/evergreen/configuration.rbs +22 -0
- data/sig/evergreen/connection.rbs +13 -0
- data/sig/evergreen/idl.rbs +31 -0
- data/sig/evergreen/idl_object.rbs +10 -0
- data/sig/evergreen/mixins/retrieval_methods.rb +9 -0
- data/sig/evergreen/version.rbs +6 -0
- data/sig/opensrf/class_and_data.rbs +12 -0
- data/sig/opensrf/http_translator_request.rbs +20 -0
- metadata +134 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: d4a950c35117be9bb820cc29a6c51a7b6d617182cea54d5fc83449dd02654e13
|
|
4
|
+
data.tar.gz: 44124d0b23d86d2da8d60a5e169ead1d23ab98238cffd16e1be5dbb149350971
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 6085835b66450141c9a330cc7b98f8e8e9b61e6d31779bcb4f0e974864a0a8942c78c5dc493c42216f8b827ad6559ac35bf286b3cdfd0fab5568fe9b04a5a402
|
|
7
|
+
data.tar.gz: 5ce0821e80d4ce4dd4d08c39685311a98edbe98edca5859724e4d5d589f8f3cbcf27ba1f51fdee503426eb42c8d33abfd705c23ffd87220a73e06583a95d47e3
|
data/.reek.yml
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Auto generated by Reeks --todo flag
|
|
2
|
+
---
|
|
3
|
+
detectors:
|
|
4
|
+
Attribute:
|
|
5
|
+
exclude:
|
|
6
|
+
- Evergreen::Configuration#default_password
|
|
7
|
+
- Evergreen::Configuration#default_username
|
|
8
|
+
- Evergreen::Configuration#host
|
|
9
|
+
- Evergreen::Configuration#read_only
|
|
10
|
+
FeatureEnvy:
|
|
11
|
+
exclude:
|
|
12
|
+
- Evergreen::Configuration#field_is_empty
|
|
13
|
+
LongParameterList:
|
|
14
|
+
exclude:
|
|
15
|
+
- Evergreen::IDL::IDLSaxHandler#start_element
|
|
16
|
+
NilCheck:
|
|
17
|
+
exclude:
|
|
18
|
+
- Evergreen::Configuration#field_is_empty
|
|
19
|
+
UtilityFunction:
|
|
20
|
+
exclude:
|
|
21
|
+
- Evergreen#initialize
|
|
22
|
+
exclude_paths:
|
|
23
|
+
- spec
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/.tool-versions
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ruby 3.2.1
|
data/Gemfile
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
source 'https://rubygems.org'
|
|
4
|
+
|
|
5
|
+
# Specify your gem's dependencies in evergreen-ils.gemspec
|
|
6
|
+
gemspec
|
|
7
|
+
|
|
8
|
+
gem 'rake'
|
|
9
|
+
|
|
10
|
+
group :test do
|
|
11
|
+
gem 'rspec'
|
|
12
|
+
gem 'webmock'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
group :check do
|
|
16
|
+
gem 'flay'
|
|
17
|
+
gem 'flog'
|
|
18
|
+
gem 'rbs'
|
|
19
|
+
gem 'reek'
|
|
20
|
+
gem 'rubocop', require: false
|
|
21
|
+
gem 'rubocop-performance', require: false
|
|
22
|
+
gem 'rubocop-rspec', require: false
|
|
23
|
+
gem 'steep'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
group :development do
|
|
27
|
+
gem 'debug'
|
|
28
|
+
end
|
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
evergreen-ils (0.2.0)
|
|
5
|
+
marc (>= 1.0.0, < 2.0)
|
|
6
|
+
rexml (>= 3.0.0, < 4.0)
|
|
7
|
+
zeitwerk (>= 2.0.0, < 3.0)
|
|
8
|
+
|
|
9
|
+
GEM
|
|
10
|
+
remote: https://rubygems.org/
|
|
11
|
+
specs:
|
|
12
|
+
activesupport (7.0.6)
|
|
13
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
14
|
+
i18n (>= 1.6, < 2)
|
|
15
|
+
minitest (>= 5.1)
|
|
16
|
+
tzinfo (~> 2.0)
|
|
17
|
+
addressable (2.8.5)
|
|
18
|
+
public_suffix (>= 2.0.2, < 6.0)
|
|
19
|
+
ast (2.4.2)
|
|
20
|
+
concurrent-ruby (1.2.2)
|
|
21
|
+
crack (0.4.5)
|
|
22
|
+
rexml
|
|
23
|
+
csv (3.2.7)
|
|
24
|
+
debug (1.8.0)
|
|
25
|
+
irb (>= 1.5.0)
|
|
26
|
+
reline (>= 0.3.1)
|
|
27
|
+
diff-lcs (1.5.0)
|
|
28
|
+
erubi (1.12.0)
|
|
29
|
+
ffi (1.15.5)
|
|
30
|
+
ffi (1.15.5-java)
|
|
31
|
+
fileutils (1.7.1)
|
|
32
|
+
flay (2.13.1)
|
|
33
|
+
erubi (~> 1.10)
|
|
34
|
+
path_expander (~> 1.0)
|
|
35
|
+
ruby_parser (~> 3.0)
|
|
36
|
+
sexp_processor (~> 4.0)
|
|
37
|
+
flog (4.7.0)
|
|
38
|
+
path_expander (~> 1.0)
|
|
39
|
+
ruby_parser (~> 3.1, > 3.1.0)
|
|
40
|
+
sexp_processor (~> 4.8)
|
|
41
|
+
hashdiff (1.0.1)
|
|
42
|
+
i18n (1.14.1)
|
|
43
|
+
concurrent-ruby (~> 1.0)
|
|
44
|
+
io-console (0.6.0)
|
|
45
|
+
io-console (0.6.0-java)
|
|
46
|
+
irb (1.7.4)
|
|
47
|
+
reline (>= 0.3.6)
|
|
48
|
+
json (2.6.3)
|
|
49
|
+
json (2.6.3-java)
|
|
50
|
+
kwalify (0.7.2)
|
|
51
|
+
language_server-protocol (3.17.0.3)
|
|
52
|
+
listen (3.8.0)
|
|
53
|
+
rb-fsevent (~> 0.10, >= 0.10.3)
|
|
54
|
+
rb-inotify (~> 0.9, >= 0.9.10)
|
|
55
|
+
logger (1.5.3)
|
|
56
|
+
marc (1.2.0)
|
|
57
|
+
rexml
|
|
58
|
+
scrub_rb (>= 1.0.1, < 2)
|
|
59
|
+
unf
|
|
60
|
+
minitest (5.19.0)
|
|
61
|
+
parallel (1.23.0)
|
|
62
|
+
parser (3.2.2.3)
|
|
63
|
+
ast (~> 2.4.1)
|
|
64
|
+
racc
|
|
65
|
+
path_expander (1.1.1)
|
|
66
|
+
public_suffix (5.0.3)
|
|
67
|
+
racc (1.7.1)
|
|
68
|
+
racc (1.7.1-java)
|
|
69
|
+
rainbow (3.1.1)
|
|
70
|
+
rake (13.0.6)
|
|
71
|
+
rb-fsevent (0.11.2)
|
|
72
|
+
rb-inotify (0.10.1)
|
|
73
|
+
ffi (~> 1.0)
|
|
74
|
+
rbs (3.1.3)
|
|
75
|
+
reek (6.1.4)
|
|
76
|
+
kwalify (~> 0.7.0)
|
|
77
|
+
parser (~> 3.2.0)
|
|
78
|
+
rainbow (>= 2.0, < 4.0)
|
|
79
|
+
regexp_parser (2.8.1)
|
|
80
|
+
reline (0.3.7)
|
|
81
|
+
io-console (~> 0.5)
|
|
82
|
+
rexml (3.2.6)
|
|
83
|
+
rspec (3.12.0)
|
|
84
|
+
rspec-core (~> 3.12.0)
|
|
85
|
+
rspec-expectations (~> 3.12.0)
|
|
86
|
+
rspec-mocks (~> 3.12.0)
|
|
87
|
+
rspec-core (3.12.2)
|
|
88
|
+
rspec-support (~> 3.12.0)
|
|
89
|
+
rspec-expectations (3.12.3)
|
|
90
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
91
|
+
rspec-support (~> 3.12.0)
|
|
92
|
+
rspec-mocks (3.12.6)
|
|
93
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
94
|
+
rspec-support (~> 3.12.0)
|
|
95
|
+
rspec-support (3.12.1)
|
|
96
|
+
rubocop (1.55.1)
|
|
97
|
+
json (~> 2.3)
|
|
98
|
+
language_server-protocol (>= 3.17.0)
|
|
99
|
+
parallel (~> 1.10)
|
|
100
|
+
parser (>= 3.2.2.3)
|
|
101
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
102
|
+
regexp_parser (>= 1.8, < 3.0)
|
|
103
|
+
rexml (>= 3.2.5, < 4.0)
|
|
104
|
+
rubocop-ast (>= 1.28.1, < 2.0)
|
|
105
|
+
ruby-progressbar (~> 1.7)
|
|
106
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
|
107
|
+
rubocop-ast (1.29.0)
|
|
108
|
+
parser (>= 3.2.1.0)
|
|
109
|
+
rubocop-capybara (2.18.0)
|
|
110
|
+
rubocop (~> 1.41)
|
|
111
|
+
rubocop-factory_bot (2.23.1)
|
|
112
|
+
rubocop (~> 1.33)
|
|
113
|
+
rubocop-performance (1.18.0)
|
|
114
|
+
rubocop (>= 1.7.0, < 2.0)
|
|
115
|
+
rubocop-ast (>= 0.4.0)
|
|
116
|
+
rubocop-rspec (2.23.1)
|
|
117
|
+
rubocop (~> 1.33)
|
|
118
|
+
rubocop-capybara (~> 2.17)
|
|
119
|
+
rubocop-factory_bot (~> 2.22)
|
|
120
|
+
ruby-progressbar (1.13.0)
|
|
121
|
+
ruby_parser (3.20.3)
|
|
122
|
+
sexp_processor (~> 4.16)
|
|
123
|
+
scrub_rb (1.0.1)
|
|
124
|
+
securerandom (0.2.2)
|
|
125
|
+
sexp_processor (4.17.0)
|
|
126
|
+
steep (1.5.2)
|
|
127
|
+
activesupport (>= 5.1)
|
|
128
|
+
concurrent-ruby (>= 1.1.10)
|
|
129
|
+
csv (>= 3.0.9)
|
|
130
|
+
fileutils (>= 1.1.0)
|
|
131
|
+
json (>= 2.1.0)
|
|
132
|
+
language_server-protocol (>= 3.15, < 4.0)
|
|
133
|
+
listen (~> 3.0)
|
|
134
|
+
logger (>= 1.3.0)
|
|
135
|
+
parser (>= 3.1)
|
|
136
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
137
|
+
rbs (>= 3.1.0)
|
|
138
|
+
securerandom (>= 0.1)
|
|
139
|
+
strscan (>= 1.0.0)
|
|
140
|
+
terminal-table (>= 2, < 4)
|
|
141
|
+
strscan (3.0.6)
|
|
142
|
+
strscan (3.0.6-java)
|
|
143
|
+
terminal-table (3.0.2)
|
|
144
|
+
unicode-display_width (>= 1.1.1, < 3)
|
|
145
|
+
tzinfo (2.0.6)
|
|
146
|
+
concurrent-ruby (~> 1.0)
|
|
147
|
+
unf (0.1.4)
|
|
148
|
+
unf_ext
|
|
149
|
+
unf (0.1.4-java)
|
|
150
|
+
unf_ext (0.0.8.2)
|
|
151
|
+
unicode-display_width (2.4.2)
|
|
152
|
+
webmock (3.18.1)
|
|
153
|
+
addressable (>= 2.8.0)
|
|
154
|
+
crack (>= 0.3.2)
|
|
155
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
|
156
|
+
zeitwerk (2.6.11)
|
|
157
|
+
|
|
158
|
+
PLATFORMS
|
|
159
|
+
arm64-darwin-21
|
|
160
|
+
arm64-darwin-22
|
|
161
|
+
universal-java-11
|
|
162
|
+
x86_64-linux
|
|
163
|
+
|
|
164
|
+
DEPENDENCIES
|
|
165
|
+
debug
|
|
166
|
+
evergreen-ils!
|
|
167
|
+
flay
|
|
168
|
+
flog
|
|
169
|
+
rake
|
|
170
|
+
rbs
|
|
171
|
+
reek
|
|
172
|
+
rspec
|
|
173
|
+
rubocop
|
|
174
|
+
rubocop-performance
|
|
175
|
+
rubocop-rspec
|
|
176
|
+
steep
|
|
177
|
+
webmock
|
|
178
|
+
|
|
179
|
+
BUNDLED WITH
|
|
180
|
+
2.4.6
|
data/README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Evergreen
|
|
2
|
+
|
|
3
|
+
A gem for interacting with [Evergreen ILS](https://evergreen-ils.org)
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install the gem and add to the application's Gemfile by executing:
|
|
8
|
+
|
|
9
|
+
$ bundle add evergreen-ils
|
|
10
|
+
|
|
11
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
12
|
+
|
|
13
|
+
$ gem install evergreen-ils
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### Configuration
|
|
18
|
+
|
|
19
|
+
Configure your `Evergreen` object with a hostname, and any other relevant
|
|
20
|
+
options. If using Rails, you might create an Evergreen service:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
class EvergreenService
|
|
24
|
+
attr_reader :evergreen
|
|
25
|
+
def initialize
|
|
26
|
+
@evergreen ||= Evergreen.new do |config|
|
|
27
|
+
config.host = 'my.evergreen.server'
|
|
28
|
+
config.default_username = 'user1'
|
|
29
|
+
config.default_username = ENV['my_pass']
|
|
30
|
+
config.read_only = false
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
You can then access those configurations in your app at
|
|
37
|
+
`service.evergreen.configuration.host`.
|
|
38
|
+
|
|
39
|
+
### Retrieving objects
|
|
40
|
+
|
|
41
|
+
Once you have an object:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
Evergreen.new(host: 'my.evergreen.server') do |evergreen|
|
|
45
|
+
bib = evergreen.get_bib_record(123)
|
|
46
|
+
bib.to_marc
|
|
47
|
+
evergreen.get_item(345)
|
|
48
|
+
evergreen.get_call_number(2345)
|
|
49
|
+
end
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
## Development
|
|
54
|
+
|
|
55
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
56
|
+
|
|
57
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
58
|
+
|
|
59
|
+
## Goals
|
|
60
|
+
|
|
61
|
+
The priorities here are:
|
|
62
|
+
* Thread safety
|
|
63
|
+
* Good documentation
|
|
64
|
+
* Good tests
|
|
65
|
+
|
|
66
|
+
## Contributing
|
|
67
|
+
|
|
68
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/sandbergja/evergreen-rb.
|
data/Rakefile
ADDED
data/Steepfile
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# D = Steep::Diagnostic
|
|
4
|
+
#
|
|
5
|
+
target :lib do
|
|
6
|
+
signature 'sig'
|
|
7
|
+
|
|
8
|
+
check 'lib'
|
|
9
|
+
|
|
10
|
+
library 'json', 'net-http', 'uri'
|
|
11
|
+
|
|
12
|
+
ignore 'lib/evergreen.rb'
|
|
13
|
+
ignore 'lib/evergreen/bib_record.rb'
|
|
14
|
+
ignore 'lib/evergreen/connection.rb'
|
|
15
|
+
ignore 'lib/evergreen/idl.rb'
|
|
16
|
+
ignore 'lib/evergreen/mixins'
|
|
17
|
+
ignore 'lib/opensrf/http_translator_request.rb'
|
|
18
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/evergreen/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'evergreen-ils'
|
|
7
|
+
spec.version = Evergreen::VERSION
|
|
8
|
+
spec.authors = ['Jane Sandberg']
|
|
9
|
+
spec.email = ['sandbergja@gmail.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'Evergreen ILS'
|
|
12
|
+
spec.description = 'A gem for interacting with the Evergreen Integrated Library System'
|
|
13
|
+
spec.homepage = 'https://github.com/sandbergja/evergreen-rb'
|
|
14
|
+
spec.required_ruby_version = '>= 3.0.0'
|
|
15
|
+
|
|
16
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
17
|
+
spec.metadata['source_code_uri'] = 'https://github.com/sandbergja/evergreen-rb'
|
|
18
|
+
|
|
19
|
+
# Specify which files should be added to the gem when it is released.
|
|
20
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
21
|
+
spec.files = Dir.chdir(__dir__) do
|
|
22
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
23
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
spec.bindir = 'exe'
|
|
27
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
28
|
+
spec.require_paths = ['lib']
|
|
29
|
+
|
|
30
|
+
# Uncomment to register a new dependency of your gem
|
|
31
|
+
spec.add_dependency 'marc', '>= 1.0.0', '< 2.0'
|
|
32
|
+
spec.add_dependency 'rexml', '>= 3.0.0', '< 4.0'
|
|
33
|
+
spec.add_dependency 'zeitwerk', '>= 2.0.0', '< 3.0'
|
|
34
|
+
|
|
35
|
+
# For more information and examples about making a new gem, check out our
|
|
36
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
|
37
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
38
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'marc'
|
|
4
|
+
require 'stringio'
|
|
5
|
+
|
|
6
|
+
class Evergreen
|
|
7
|
+
# A bibliographic record (title)
|
|
8
|
+
class BibRecord < IDLObject
|
|
9
|
+
include Mixins::AnonymousPcrud
|
|
10
|
+
def initialize(id:, configuration:, idl:)
|
|
11
|
+
@id = id
|
|
12
|
+
@configuration = configuration
|
|
13
|
+
super(idl)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# rubocop:disable Naming/MemoizedInstanceVariableName
|
|
17
|
+
def to_marc
|
|
18
|
+
@marc ||= MARC::XMLReader.new(StringIO.new(get('marc'))).first
|
|
19
|
+
end
|
|
20
|
+
# rubocop:enable Naming/MemoizedInstanceVariableName
|
|
21
|
+
|
|
22
|
+
def holdings
|
|
23
|
+
payload = OpenSRF::ClassAndData.new(klass: 'osrfMessage', data: {
|
|
24
|
+
'method' => 'open-ils.cat.asset.copy_tree.global.retrieve',
|
|
25
|
+
'params' => ['', @id.to_s]
|
|
26
|
+
}).to_h
|
|
27
|
+
OpenSRF::HTTPTranslatorRequest.new(payload: payload, configuration: @configuration,
|
|
28
|
+
service: 'open-ils.cat').response
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def tcn
|
|
32
|
+
get 'tcn_value'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def idl_class
|
|
38
|
+
'bre'
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Evergreen
|
|
4
|
+
# Configuration for an instance of Evergreen
|
|
5
|
+
class Configuration
|
|
6
|
+
attr_reader :host, :default_username, :default_password, :read_only
|
|
7
|
+
|
|
8
|
+
def initialize(config_hash)
|
|
9
|
+
@read_only = true
|
|
10
|
+
config_hash.each_pair do |key, value|
|
|
11
|
+
instance_variable_set("@#{key}", value)
|
|
12
|
+
end
|
|
13
|
+
configuration_complete
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def configuration_complete
|
|
19
|
+
check_required_fields
|
|
20
|
+
freeze
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def check_required_fields
|
|
24
|
+
raise ArgumentError, 'you must supply a host' if field_is_empty :host
|
|
25
|
+
return unless !@read_only && (field_is_empty(:default_username) || field_is_empty(:default_password))
|
|
26
|
+
|
|
27
|
+
raise ArgumentError, 'you must supply default credentials unless you are in read-only mode'
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def field_is_empty(field_name)
|
|
31
|
+
field_value = instance_variable_get("@#{field_name}")
|
|
32
|
+
field_value.nil? || field_value.empty?
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Evergreen
|
|
4
|
+
# This class provides an interface for making
|
|
5
|
+
# requests to the Evergreen server
|
|
6
|
+
class Connection
|
|
7
|
+
include Mixins::RetrievalMethods
|
|
8
|
+
attr_reader :configuration
|
|
9
|
+
|
|
10
|
+
def initialize(configuration:)
|
|
11
|
+
@configuration = configuration
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def close; end
|
|
15
|
+
|
|
16
|
+
def idl
|
|
17
|
+
@idl ||= IDL.new(@configuration)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open-uri'
|
|
4
|
+
require 'rexml/parsers/sax2parser'
|
|
5
|
+
require 'rexml/sax2listener'
|
|
6
|
+
|
|
7
|
+
class Evergreen
|
|
8
|
+
# Evergreen's fieldmapper IDL
|
|
9
|
+
class IDL
|
|
10
|
+
attr_reader :fields
|
|
11
|
+
|
|
12
|
+
def initialize(configuration)
|
|
13
|
+
@configuration = configuration
|
|
14
|
+
@handler = IDLSaxHandler.new
|
|
15
|
+
fetch
|
|
16
|
+
freeze
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def [](key)
|
|
20
|
+
fields[key]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def fetch
|
|
26
|
+
URI.open("https://#{@configuration.host}/reports/fm_IDL.xml") do |file|
|
|
27
|
+
@fields = IDLSaxParser.new(file).parse
|
|
28
|
+
end
|
|
29
|
+
rescue Errno::ECONNREFUSED, OpenURI::HTTPError
|
|
30
|
+
raise Evergreen::ConnectionError
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# A wrapper around the SAX parser
|
|
34
|
+
class IDLSaxParser
|
|
35
|
+
def initialize(file)
|
|
36
|
+
@parser = REXML::Parsers::SAX2Parser.new(file)
|
|
37
|
+
@handler = IDLSaxHandler.new
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def parse
|
|
41
|
+
@parser.listen(@handler)
|
|
42
|
+
@parser.parse
|
|
43
|
+
@handler.idl_fields
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# A SAX parsing handler
|
|
48
|
+
class IDLSaxHandler
|
|
49
|
+
include REXML::SAX2Listener
|
|
50
|
+
attr_reader :idl_fields
|
|
51
|
+
|
|
52
|
+
def initialize
|
|
53
|
+
@idl_fields = {}
|
|
54
|
+
@current_class = nil
|
|
55
|
+
super
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Callback for when we hit an XML attribute
|
|
59
|
+
def start_element(_uri, _localname, _qname, attributes)
|
|
60
|
+
if attributes.key? 'id'
|
|
61
|
+
# We found a class ID!
|
|
62
|
+
@current_class = attributes['id']
|
|
63
|
+
@idl_fields[@current_class] = []
|
|
64
|
+
elsif attributes.key? 'name'
|
|
65
|
+
# We found the name of a field!
|
|
66
|
+
@idl_fields[@current_class].push(attributes['name'])
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Evergreen
|
|
4
|
+
# A base class for any object represented in
|
|
5
|
+
# Evergreen's IDL
|
|
6
|
+
class IDLObject
|
|
7
|
+
def initialize(idl)
|
|
8
|
+
@idl = idl
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def idl_fields
|
|
12
|
+
@idl[idl_class]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def get(field_name)
|
|
16
|
+
data[idl_fields.index(field_name)]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
# This should be overriden by subclasses
|
|
22
|
+
def idl_class
|
|
23
|
+
'acp'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def data; end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Evergreen
|
|
4
|
+
module Mixins
|
|
5
|
+
# This read-only API is available
|
|
6
|
+
# without any credentials
|
|
7
|
+
module AnonymousPcrud
|
|
8
|
+
def data
|
|
9
|
+
return @data if @data
|
|
10
|
+
return unless @id && @configuration && idl_class && idl_fields
|
|
11
|
+
|
|
12
|
+
payload = OpenSRF::ClassAndData.new(klass: 'osrfMessage', data: {
|
|
13
|
+
'method' => "open-ils.pcrud.retrieve.#{idl_class}",
|
|
14
|
+
'params' => ['ANONYMOUS', @id.to_s]
|
|
15
|
+
}).to_h
|
|
16
|
+
response = OpenSRF::HTTPTranslatorRequest.new(payload: payload, configuration: @configuration,
|
|
17
|
+
service: 'open-ils.pcrud').response
|
|
18
|
+
@data = OpenSRF::ClassAndData.parse(response['content']).data
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Evergreen
|
|
4
|
+
module Mixins
|
|
5
|
+
# Convenience methods to search for data
|
|
6
|
+
# and initialize objects based on the results
|
|
7
|
+
module RetrievalMethods
|
|
8
|
+
def get_bib_record(id)
|
|
9
|
+
Evergreen::BibRecord.new(id: id, configuration: configuration, idl: idl)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
data/lib/evergreen.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'zeitwerk'
|
|
4
|
+
loader = Zeitwerk::Loader.for_gem
|
|
5
|
+
loader.inflector.inflect(
|
|
6
|
+
'http_translator_request' => 'HTTPTranslatorRequest',
|
|
7
|
+
'idl' => 'IDL',
|
|
8
|
+
'idl_object' => 'IDLObject',
|
|
9
|
+
'opensrf' => 'OpenSRF',
|
|
10
|
+
'json' => 'JSON'
|
|
11
|
+
)
|
|
12
|
+
loader.setup
|
|
13
|
+
|
|
14
|
+
# The main class provided by this gem
|
|
15
|
+
class Evergreen
|
|
16
|
+
def initialize(config_hash)
|
|
17
|
+
connection = Connection.new(configuration: Configuration.new(config_hash))
|
|
18
|
+
yield connection
|
|
19
|
+
connection.close
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# An error when we can't connect to the Evergreen server
|
|
23
|
+
class ConnectionError < StandardError; end
|
|
24
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenSRF
|
|
4
|
+
# OpenSRF requests and responses often
|
|
5
|
+
# take the following form
|
|
6
|
+
# {"__c": "clasname", "__p": ["all", "the", "data"]}
|
|
7
|
+
class ClassAndData
|
|
8
|
+
attr_reader :klass, :data
|
|
9
|
+
|
|
10
|
+
def initialize(klass:, data:)
|
|
11
|
+
@klass = klass
|
|
12
|
+
@data = data
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.parse(hash, path = [])
|
|
16
|
+
small_json = new(klass: hash['__c'], data: hash['__p'])
|
|
17
|
+
return small_json if path.empty?
|
|
18
|
+
|
|
19
|
+
key = path.shift
|
|
20
|
+
parse(small_json.data[key], path)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def to_h
|
|
24
|
+
{ '__c' => @klass,
|
|
25
|
+
'__p' => @data }
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'uri'
|
|
6
|
+
|
|
7
|
+
module OpenSRF
|
|
8
|
+
# A stateful endpoint, described in
|
|
9
|
+
# this documentation:
|
|
10
|
+
# https://docs.evergreen-ils.org/eg/docs/latest/integrations/web_services.html#_http_translator
|
|
11
|
+
class HTTPTranslatorRequest
|
|
12
|
+
def initialize(configuration:, service:, payload:)
|
|
13
|
+
@configuration = configuration
|
|
14
|
+
@service = service
|
|
15
|
+
@payload = payload
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def response
|
|
19
|
+
raw = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
|
|
20
|
+
http.request(request)
|
|
21
|
+
end
|
|
22
|
+
OpenSRF::ClassAndData.parse(::JSON.parse(raw.body).first, ['payload']).data
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def request
|
|
28
|
+
request = Net::HTTP::Post.new(uri)
|
|
29
|
+
request['X-Opensrf-Service'] = @service
|
|
30
|
+
request.body = "osrf-msg=#{osrf_message.to_json}"
|
|
31
|
+
request
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def osrf_message
|
|
35
|
+
[
|
|
36
|
+
OpenSRF::ClassAndData.new(klass: 'osrfMessage', data: {
|
|
37
|
+
'threadTrace' => 0,
|
|
38
|
+
'locale' => 'en-CA',
|
|
39
|
+
'type' => 'REQUEST',
|
|
40
|
+
'payload' => @payload
|
|
41
|
+
}).to_h
|
|
42
|
+
]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def req_options
|
|
46
|
+
{ use_ssl: true }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def uri
|
|
50
|
+
URI.parse("https://#{@configuration.host}/osrf-http-translator")
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class Evergreen
|
|
2
|
+
# Configuration for an instance of Evergreen
|
|
3
|
+
class Configuration
|
|
4
|
+
attr_accessor host: untyped
|
|
5
|
+
|
|
6
|
+
attr_accessor default_username: untyped
|
|
7
|
+
|
|
8
|
+
attr_accessor default_password: untyped
|
|
9
|
+
|
|
10
|
+
attr_accessor read_only: untyped
|
|
11
|
+
|
|
12
|
+
def initialize: (Hash[Symbol, (String | bool)]) -> void
|
|
13
|
+
|
|
14
|
+
def configuration_complete: () -> untyped
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def check_required_fields: () -> (nil | untyped)
|
|
19
|
+
|
|
20
|
+
def field_is_empty: (untyped field_name) -> untyped
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class Evergreen
|
|
2
|
+
# This class provides an interface for making
|
|
3
|
+
# requests to the Evergreen server
|
|
4
|
+
class Connection
|
|
5
|
+
attr_reader configuration: Evergreen::Configuration
|
|
6
|
+
|
|
7
|
+
def initialize: (configuration: untyped) -> void
|
|
8
|
+
|
|
9
|
+
def close: () -> nil
|
|
10
|
+
|
|
11
|
+
def idl: () -> Evergreen::IDL
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
class Evergreen
|
|
2
|
+
# Evergreen's fieldmapper IDL
|
|
3
|
+
class IDL
|
|
4
|
+
attr_reader fields: untyped
|
|
5
|
+
|
|
6
|
+
def initialize: (untyped configuration) -> void
|
|
7
|
+
|
|
8
|
+
def []: (untyped key) -> untyped
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def fetch: () -> untyped
|
|
13
|
+
|
|
14
|
+
# A wrapper around the SAX parser
|
|
15
|
+
class IDLSaxParser
|
|
16
|
+
def initialize: (untyped file) -> void
|
|
17
|
+
|
|
18
|
+
def parse: () -> untyped
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# A SAX parsing handler
|
|
22
|
+
class IDLSaxHandler
|
|
23
|
+
attr_reader idl_fields: untyped
|
|
24
|
+
|
|
25
|
+
def initialize: () -> void
|
|
26
|
+
|
|
27
|
+
# Callback for when we hit an XML attribute
|
|
28
|
+
def start_element: (untyped _uri, untyped _localname, untyped _qname, untyped attributes) -> (untyped | untyped | nil)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# TypeProf 0.21.2
|
|
2
|
+
|
|
3
|
+
# Classes
|
|
4
|
+
module OpenSRF
|
|
5
|
+
class ClassAndData
|
|
6
|
+
attr_reader klass: untyped
|
|
7
|
+
attr_reader data: untyped
|
|
8
|
+
def initialize: (klass: untyped, data: untyped) -> void
|
|
9
|
+
def self.parse: (untyped hash, ?Array[untyped] path) -> ClassAndData
|
|
10
|
+
def to_h: -> Hash[String, untyped]
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module OpenSRF
|
|
2
|
+
# A stateful endpoint, described in
|
|
3
|
+
# this documentation:
|
|
4
|
+
# https://docs.evergreen-ils.org/eg/docs/latest/integrations/web_services.html#_http_translator
|
|
5
|
+
class HTTPTranslatorRequest
|
|
6
|
+
def initialize: (configuration: untyped, service: untyped, payload: untyped) -> void
|
|
7
|
+
|
|
8
|
+
def response: () -> untyped
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def request: () -> untyped
|
|
13
|
+
|
|
14
|
+
def osrf_message: () -> ::Array[untyped]
|
|
15
|
+
|
|
16
|
+
def req_options: () -> { use_ssl: true }
|
|
17
|
+
|
|
18
|
+
def uri: () -> untyped
|
|
19
|
+
end
|
|
20
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: evergreen-ils
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Jane Sandberg
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2023-08-08 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: marc
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 1.0.0
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '2.0'
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: 1.0.0
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.0'
|
|
33
|
+
- !ruby/object:Gem::Dependency
|
|
34
|
+
name: rexml
|
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: 3.0.0
|
|
40
|
+
- - "<"
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '4.0'
|
|
43
|
+
type: :runtime
|
|
44
|
+
prerelease: false
|
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
46
|
+
requirements:
|
|
47
|
+
- - ">="
|
|
48
|
+
- !ruby/object:Gem::Version
|
|
49
|
+
version: 3.0.0
|
|
50
|
+
- - "<"
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '4.0'
|
|
53
|
+
- !ruby/object:Gem::Dependency
|
|
54
|
+
name: zeitwerk
|
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - ">="
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: 2.0.0
|
|
60
|
+
- - "<"
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: '3.0'
|
|
63
|
+
type: :runtime
|
|
64
|
+
prerelease: false
|
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - ">="
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: 2.0.0
|
|
70
|
+
- - "<"
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '3.0'
|
|
73
|
+
description: A gem for interacting with the Evergreen Integrated Library System
|
|
74
|
+
email:
|
|
75
|
+
- sandbergja@gmail.com
|
|
76
|
+
executables: []
|
|
77
|
+
extensions: []
|
|
78
|
+
extra_rdoc_files: []
|
|
79
|
+
files:
|
|
80
|
+
- ".reek.yml"
|
|
81
|
+
- ".rspec"
|
|
82
|
+
- ".rubocop.yml"
|
|
83
|
+
- ".tool-versions"
|
|
84
|
+
- Gemfile
|
|
85
|
+
- Gemfile.lock
|
|
86
|
+
- README.md
|
|
87
|
+
- Rakefile
|
|
88
|
+
- Steepfile
|
|
89
|
+
- evergreen-ils.gemspec
|
|
90
|
+
- lib/evergreen.rb
|
|
91
|
+
- lib/evergreen/bib_record.rb
|
|
92
|
+
- lib/evergreen/configuration.rb
|
|
93
|
+
- lib/evergreen/connection.rb
|
|
94
|
+
- lib/evergreen/idl.rb
|
|
95
|
+
- lib/evergreen/idl_object.rb
|
|
96
|
+
- lib/evergreen/mixins/anonymous_pcrud.rb
|
|
97
|
+
- lib/evergreen/mixins/retrieval_methods.rb
|
|
98
|
+
- lib/evergreen/version.rb
|
|
99
|
+
- lib/opensrf/class_and_data.rb
|
|
100
|
+
- lib/opensrf/http_translator_request.rb
|
|
101
|
+
- sig/evergreen/configuration.rbs
|
|
102
|
+
- sig/evergreen/connection.rbs
|
|
103
|
+
- sig/evergreen/idl.rbs
|
|
104
|
+
- sig/evergreen/idl_object.rbs
|
|
105
|
+
- sig/evergreen/mixins/retrieval_methods.rb
|
|
106
|
+
- sig/evergreen/version.rbs
|
|
107
|
+
- sig/opensrf/class_and_data.rbs
|
|
108
|
+
- sig/opensrf/http_translator_request.rbs
|
|
109
|
+
homepage: https://github.com/sandbergja/evergreen-rb
|
|
110
|
+
licenses: []
|
|
111
|
+
metadata:
|
|
112
|
+
homepage_uri: https://github.com/sandbergja/evergreen-rb
|
|
113
|
+
source_code_uri: https://github.com/sandbergja/evergreen-rb
|
|
114
|
+
rubygems_mfa_required: 'true'
|
|
115
|
+
post_install_message:
|
|
116
|
+
rdoc_options: []
|
|
117
|
+
require_paths:
|
|
118
|
+
- lib
|
|
119
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - ">="
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: 3.0.0
|
|
124
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
125
|
+
requirements:
|
|
126
|
+
- - ">="
|
|
127
|
+
- !ruby/object:Gem::Version
|
|
128
|
+
version: '0'
|
|
129
|
+
requirements: []
|
|
130
|
+
rubygems_version: 3.4.6
|
|
131
|
+
signing_key:
|
|
132
|
+
specification_version: 4
|
|
133
|
+
summary: Evergreen ILS
|
|
134
|
+
test_files: []
|