dor-fetcher 1.1.7 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- data/lib/dor-fetcher.rb +128 -156
- metadata +47 -20
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
ZDFiMDgzZmVjOGJjNmE4MjIzZGQyMzE0ZmM0YjRmMTcxYzA5YjQwNA==
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6727b5b0868d3d588ed20a80df170a3e62eedf5d88dd0aee1b9fcd6104cf4a39
|
4
|
+
data.tar.gz: 6d042f0c65981a841c1950e28b4fd472928a378d78b613ca103a233cc883cf8d
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
ZGViOTdlYjAzMDMyMzAyMWZhODBkNTNmNTZjOGUwNDM1MmMxMDFjNjg2ZjJi
|
11
|
-
YmEzOWE2YWVlNjBhZDhmM2VlMmE2MDQ3OTZlNTVjNmU2NTI5ZTA=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
OGEwZDE2ZGJhNWNjYzA0ODgwOWMzN2RhN2FhNGJlMjIzZjNjZmNmZDJkNTk0
|
14
|
-
NmM1NWJlYzZkY2JhODc0MWFhZWE3ODFhZjRlNTU2NDFlMDY1OTYyMTJhOTM5
|
15
|
-
NzRiMjdiOWYyYzM2NzI5ZWVkMTRlY2U5MjBjMzk2ZDJjMzM4ZGE=
|
6
|
+
metadata.gz: d293c213247d7649ffa0b85605c98ff1240ed2a25e3b847cf2134ceddabaf6b5a0b250936cead592cab1408021b7254216a8b55c24bf58763f68edf08d6bb67d
|
7
|
+
data.tar.gz: 1371ec72bf0c36de3ecfd406333c6861d75b0ebda3da045f0b34cd06f5c2e7f95808870e16f6f4cab248065b2fe256a337a49970825d22658f1d9edb3f7294cb
|
data/lib/dor-fetcher.rb
CHANGED
@@ -3,196 +3,168 @@ require 'json'
|
|
3
3
|
require 'addressable/uri'
|
4
4
|
|
5
5
|
module DorFetcher
|
6
|
-
|
7
6
|
class Client
|
8
|
-
|
9
7
|
@@supported_params = [:first_modified, :last_modified, :count_only, :status]
|
10
|
-
@@count_only_param =
|
8
|
+
@@count_only_param = 'rows=0'
|
11
9
|
@@default_service_url = 'http://127.0.0.1:3000'
|
12
10
|
@@counts_key = 'counts'
|
13
|
-
|
14
|
-
#
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
#
|
20
|
-
|
21
|
-
|
11
|
+
|
12
|
+
attr_reader :service_url # Base URL this instance will run RESTful API calls against
|
13
|
+
|
14
|
+
# Create a new instance of DorFetcher::Client
|
15
|
+
# @param options [Hash]
|
16
|
+
# @option options [String] :service_url base url for API queries. Default: http://127.0.0.1:3000
|
17
|
+
# @option options [Boolean] :skip_heartbeat skip querying :service_url to confirm API is responsive. Default: false
|
18
|
+
# @example
|
19
|
+
# df = DorFetcher::Client.new(:service_url => 'http://SERVICEURL')
|
20
|
+
def initialize(options = {})
|
21
|
+
# TODO: Check for a well formed URL and a 200 from the destination before just accepting this
|
22
22
|
@service_url = options[:service_url] || @@default_service_url
|
23
23
|
@site = RestClient::Resource.new(@service_url)
|
24
|
-
|
25
|
-
if not options[:skip_heartbeat]
|
26
|
-
raise "DorFetcher::Client Error! No response from #{@service_url}" if not self.is_alive?
|
27
|
-
end
|
24
|
+
raise "DorFetcher::Client Error! No response from #{@service_url}" unless options[:skip_heartbeat] || is_alive?
|
28
25
|
end
|
29
|
-
|
26
|
+
|
30
27
|
# Return service info (rails env, version deployed, last restart and last deploy)
|
31
|
-
|
28
|
+
# @return [hash] Hash containing service info
|
32
29
|
def service_info
|
33
30
|
resp = @site['about/version.json'].get
|
34
|
-
|
31
|
+
JSON[resp]
|
35
32
|
end
|
36
|
-
|
37
|
-
#Check to see if the dor-fetcher-service is responding to requests, this is a basic
|
38
|
-
|
33
|
+
|
34
|
+
# Check to see if the dor-fetcher-service is responding to requests, this is a basic heartbeat checker
|
35
|
+
# @return [Boolean] True for a service that responds, False for a service that does not.
|
39
36
|
def is_alive?
|
40
37
|
resp = @site.get
|
41
|
-
|
38
|
+
200.eql?(resp.code) && /PASSED/.match?(resp.body)
|
42
39
|
end
|
43
|
-
|
44
|
-
|
45
|
-
#Get a hash of all members of a collection and the collection itself
|
40
|
+
|
41
|
+
# Get a hash of all members of a collection and the collection itself
|
46
42
|
#
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
#pid/druid, title, date last modified, and count
|
43
|
+
# @param collection [String] we expect pid/druid
|
44
|
+
# @param params [Hash] we expect :count_only or any of @@supported_params
|
45
|
+
# @return [Hash] Hash of all objects in the collection including: pid/druid, title, date last modified, and count
|
51
46
|
def get_collection(collection, params = {})
|
52
|
-
|
53
|
-
end
|
54
|
-
|
55
|
-
#Get the count of the number of items in a collection, including the
|
56
|
-
#collection
|
57
|
-
|
58
|
-
|
59
|
-
#@return [Integer] Number found
|
47
|
+
query_api('collections', collection, params)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Get the count of the number of items in a collection, including the collection object itself
|
51
|
+
# @param collection [String] we expect pid/druid
|
52
|
+
# @param params [Hash] any of @@supported_params
|
53
|
+
# @return [Integer] Number found
|
60
54
|
def get_count_for_collection(collection, params = {})
|
61
|
-
|
55
|
+
query_api('collections', collection, params.merge!(:count_only => true))
|
62
56
|
end
|
63
|
-
|
64
|
-
#Get a Hash of all the collections in the digital repository that are accessioned
|
65
|
-
|
66
|
-
#date last modified, and count
|
57
|
+
|
58
|
+
# Get a Hash of all the collections in the digital repository that are accessioned
|
59
|
+
# @return [Hash] All collections including: pid/druid, title, date last modified, and count
|
67
60
|
def list_all_collections
|
68
|
-
|
61
|
+
query_api('collections', '', {})
|
69
62
|
end
|
70
|
-
|
71
|
-
#Get a Hash of all the collections in the digital repository
|
72
|
-
|
73
|
-
#date last modified, and count
|
63
|
+
|
64
|
+
# Get a Hash of all the collections in the digital repository
|
65
|
+
# @return [Hash] All registered collections including: pid/druid, title, date last modified, and count
|
74
66
|
def list_registered_collections
|
75
|
-
|
67
|
+
query_api('collections', '', :status => 'registered')
|
76
68
|
end
|
77
|
-
|
78
|
-
#Get a Count of all the collections in the digital repository
|
79
|
-
|
69
|
+
|
70
|
+
# Get a Count of all the collections in the digital repository
|
71
|
+
# @return [Integer] Number of all collections
|
80
72
|
def total_collection_count
|
81
|
-
|
82
|
-
end
|
83
|
-
|
84
|
-
#Get the APO and all objects governed by the APO
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
#
|
94
|
-
#
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
#
|
103
|
-
#@return [Hash] Hash of all APOs including pid/druid, title,
|
104
|
-
#date last modified, and count
|
73
|
+
query_api('collections', '', :count_only => true)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Get the APO and all objects governed by the APO
|
77
|
+
# @param apo [String] pid/druid of the APO
|
78
|
+
# @param params [Hash] we expect :count_only or any of @@supported_params
|
79
|
+
# @return [Hash] All objects governed by the APO including: pid/druid, title, date last modified, and count
|
80
|
+
def get_apo(apo, params = {})
|
81
|
+
query_api('apos', apo, params)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Get the count of the number of objects in an APO, including the
|
85
|
+
# APO object itself
|
86
|
+
# @param apo [String] we expect pid/druid
|
87
|
+
# @param params [Hash] we expect :count_only or any of @@supported_params
|
88
|
+
# @return [Integer] Number found
|
89
|
+
def get_count_for_apo(apo, params = {})
|
90
|
+
query_api('apos', apo, params.merge!(:count_only => true))
|
91
|
+
end
|
92
|
+
|
93
|
+
# Get a Hash of all the APOs in the digital repository that are accessioned
|
94
|
+
# @return [Hash] All APOs including: pid/druid, title, date last modified, and count
|
105
95
|
def list_all_apos
|
106
|
-
|
96
|
+
query_api('apos', '', {})
|
107
97
|
end
|
108
98
|
|
109
|
-
#Get a Hash of all the APOs in the digital repository that are registered
|
110
|
-
|
111
|
-
#date last modified, and count
|
99
|
+
# Get a Hash of all the APOs in the digital repository that are registered
|
100
|
+
# @return [Hash] All registered APOs including: pid/druid, title, date last modified, and count
|
112
101
|
def list_registered_apos
|
113
|
-
|
102
|
+
query_api('apos', '', :status => 'registered')
|
114
103
|
end
|
115
|
-
|
116
|
-
#Get a Count of all the APOs in the digital repository
|
117
|
-
|
104
|
+
|
105
|
+
# Get a Count of all the APOs in the digital repository
|
106
|
+
# @return [Integer] Number of all APOs
|
118
107
|
def total_apo_count
|
119
|
-
|
108
|
+
query_api('apos', '', :count_only => true)
|
120
109
|
end
|
121
|
-
|
122
|
-
#
|
123
|
-
#
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
def druid_array(response,params={})
|
110
|
+
|
111
|
+
# Parses full Hash into an array containing only the druids
|
112
|
+
# @param response [Hash] Hash as returned by query_api
|
113
|
+
# @param params [Hash{Symbol=>Boolean}] options
|
114
|
+
# @option params [Boolean] :no_prefix if true (default), remove the 'druid:' prefix on all druids
|
115
|
+
# @return [Array{String}] all druids in the supplied Hash
|
116
|
+
def druid_array(response, params = {})
|
128
117
|
return_list = []
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
druid.gsub!('druid:','') if params[:no_prefix]
|
136
|
-
return_list << druid
|
137
|
-
end
|
138
|
-
end
|
118
|
+
response.each do |key, items|
|
119
|
+
next if key == @@counts_key
|
120
|
+
items.each do |item|
|
121
|
+
next if item['druid'].nil?
|
122
|
+
druid = item['druid'].downcase
|
123
|
+
return_list << (params[:no_prefix] ? druid.gsub('druid:', '') : druid)
|
139
124
|
end
|
140
125
|
end
|
141
|
-
|
142
|
-
end
|
143
|
-
|
144
|
-
|
145
|
-
#
|
146
|
-
|
147
|
-
|
148
|
-
#@param params [Hash] we expect :count_only or any of @@supported_params
|
149
|
-
#@return [Hash] Hash of all objects governed by the APO including
|
150
|
-
#pid/druid, title, date last modified, and count
|
151
|
-
def query_api(base, druid, params)
|
126
|
+
return_list
|
127
|
+
end
|
128
|
+
|
129
|
+
# Synthesize URL from base, druid and params
|
130
|
+
# @see #query_api for args
|
131
|
+
# @return [String] URL
|
132
|
+
def query_url(base, druid, params)
|
152
133
|
url = "#{@site}/#{base}"
|
153
|
-
url +="/#{druid}" unless druid.nil? || druid.empty?
|
154
|
-
url +=
|
134
|
+
url += "/#{druid}" unless druid.nil? || druid.empty?
|
135
|
+
url += add_params(params).to_s unless params.nil? || params.empty?
|
136
|
+
url
|
137
|
+
end
|
138
|
+
|
139
|
+
# Query a RESTful API and return the JSON result as a Hash
|
140
|
+
# @param base [String] The name of controller of the Rails App you wish to route to
|
141
|
+
# @param druid [String] The druid/pid of the object you wish to query, or empty string for no specific druid
|
142
|
+
# @param params [Hash] we expect :count_only or any of @@supported_params
|
143
|
+
# @option params [Hash] :count_only
|
144
|
+
# @return [Hash,Integer] All objects governed by the APO including pid/druid, title, date last modified, and count -- or just the count if :count_only
|
145
|
+
def query_api(base, druid, params)
|
146
|
+
url = query_url(base, druid, params)
|
155
147
|
begin
|
156
|
-
#We
|
157
|
-
resp = RestClient::Request.execute(:method=> :get, :url=>url, :timeout=>
|
158
|
-
rescue
|
159
|
-
|
148
|
+
# We use RestClient::Request.execute here for the longer timeout option
|
149
|
+
resp = RestClient::Request.execute(:method => :get, :url => url, :timeout => 180)
|
150
|
+
rescue RestClient::Exception => e
|
151
|
+
warn "Connection Error with url #{url}: #{e.message}"
|
152
|
+
raise e
|
160
153
|
end
|
161
|
-
|
162
|
-
#RestClient monkey patches its response so it looks like a string, but really isn't.
|
163
|
-
#If you just dd resp.to_i, you'll get the HTML Code, normally 200, not the actually body text you want
|
164
|
-
return resp[0..resp.size].to_i if params[:count_only] == true
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
uri.query_values=input_params
|
177
|
-
qs=uri.query.gsub("count_only=true",@@count_only_param)
|
178
|
-
return "?#{qs}"
|
179
|
-
end
|
180
|
-
|
181
|
-
#Add the parameter so query_api knows only to get a count of the documents in solr
|
182
|
-
#
|
183
|
-
#@param params [Hash] {The existing parameters, eg time and tag}
|
184
|
-
#@return [Hash] the params Hash plus the key/value set :count_only=>true
|
185
|
-
def add_count_only_param(params)
|
186
|
-
params.store(:count_only, true)
|
187
|
-
return params
|
188
|
-
end
|
189
|
-
|
190
|
-
#Get the Base URL this instance will run RESTful API calls against
|
191
|
-
#@return [String] the url
|
192
|
-
def service_url
|
193
|
-
return @service_url
|
194
|
-
end
|
195
|
-
|
154
|
+
|
155
|
+
# RestClient monkey patches its response so it looks like a string, but really isn't.
|
156
|
+
# If you just dd resp.to_i, you'll get the HTML Code, normally 200, not the actually body text you want
|
157
|
+
return resp[0..resp.size].to_i if params[:count_only] == true
|
158
|
+
JSON[resp] # Convert the response JSON to a Ruby Hash
|
159
|
+
end
|
160
|
+
|
161
|
+
# Transform a parameter hash into a RESTful API parameter format
|
162
|
+
# @param input_params [Hash{Symbol=>Object}] The existing parameters, eg time and tag
|
163
|
+
# @return [String] parameters in the Hash now formatted into a RESTful parameter string
|
164
|
+
def add_params(input_params)
|
165
|
+
uri = Addressable::URI.new
|
166
|
+
uri.query_values = input_params.select { |key, _val| @@supported_params.include?(key) }
|
167
|
+
'?' + uri.query.gsub('count_only=true', @@count_only_param)
|
168
|
+
end
|
196
169
|
end
|
197
|
-
|
198
|
-
end
|
170
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dor-fetcher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Carrick Rogers
|
@@ -12,102 +12,130 @@ bindir: bin
|
|
12
12
|
cert_chain: []
|
13
13
|
date: 2014-12-18 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rake
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - ">="
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '0'
|
29
|
+
- !ruby/object:Gem::Dependency
|
30
|
+
name: rubocop
|
31
|
+
requirement: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '0'
|
36
|
+
type: :development
|
37
|
+
prerelease: false
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
15
43
|
- !ruby/object:Gem::Dependency
|
16
44
|
name: rspec
|
17
45
|
requirement: !ruby/object:Gem::Requirement
|
18
46
|
requirements:
|
19
|
-
- -
|
47
|
+
- - ">="
|
20
48
|
- !ruby/object:Gem::Version
|
21
49
|
version: '0'
|
22
50
|
type: :development
|
23
51
|
prerelease: false
|
24
52
|
version_requirements: !ruby/object:Gem::Requirement
|
25
53
|
requirements:
|
26
|
-
- -
|
54
|
+
- - ">="
|
27
55
|
- !ruby/object:Gem::Version
|
28
56
|
version: '0'
|
29
57
|
- !ruby/object:Gem::Dependency
|
30
58
|
name: vcr
|
31
59
|
requirement: !ruby/object:Gem::Requirement
|
32
60
|
requirements:
|
33
|
-
- -
|
61
|
+
- - ">="
|
34
62
|
- !ruby/object:Gem::Version
|
35
63
|
version: '0'
|
36
64
|
type: :development
|
37
65
|
prerelease: false
|
38
66
|
version_requirements: !ruby/object:Gem::Requirement
|
39
67
|
requirements:
|
40
|
-
- -
|
68
|
+
- - ">="
|
41
69
|
- !ruby/object:Gem::Version
|
42
70
|
version: '0'
|
43
71
|
- !ruby/object:Gem::Dependency
|
44
72
|
name: webmock
|
45
73
|
requirement: !ruby/object:Gem::Requirement
|
46
74
|
requirements:
|
47
|
-
- -
|
75
|
+
- - ">="
|
48
76
|
- !ruby/object:Gem::Version
|
49
77
|
version: '0'
|
50
78
|
type: :development
|
51
79
|
prerelease: false
|
52
80
|
version_requirements: !ruby/object:Gem::Requirement
|
53
81
|
requirements:
|
54
|
-
- -
|
82
|
+
- - ">="
|
55
83
|
- !ruby/object:Gem::Version
|
56
84
|
version: '0'
|
57
85
|
- !ruby/object:Gem::Dependency
|
58
86
|
name: yard
|
59
87
|
requirement: !ruby/object:Gem::Requirement
|
60
88
|
requirements:
|
61
|
-
- -
|
89
|
+
- - ">="
|
62
90
|
- !ruby/object:Gem::Version
|
63
91
|
version: '0'
|
64
92
|
type: :development
|
65
93
|
prerelease: false
|
66
94
|
version_requirements: !ruby/object:Gem::Requirement
|
67
95
|
requirements:
|
68
|
-
- -
|
96
|
+
- - ">="
|
69
97
|
- !ruby/object:Gem::Version
|
70
98
|
version: '0'
|
71
99
|
- !ruby/object:Gem::Dependency
|
72
100
|
name: coveralls
|
73
101
|
requirement: !ruby/object:Gem::Requirement
|
74
102
|
requirements:
|
75
|
-
- -
|
103
|
+
- - ">="
|
76
104
|
- !ruby/object:Gem::Version
|
77
105
|
version: '0'
|
78
106
|
type: :development
|
79
107
|
prerelease: false
|
80
108
|
version_requirements: !ruby/object:Gem::Requirement
|
81
109
|
requirements:
|
82
|
-
- -
|
110
|
+
- - ">="
|
83
111
|
- !ruby/object:Gem::Version
|
84
112
|
version: '0'
|
85
113
|
- !ruby/object:Gem::Dependency
|
86
114
|
name: rest-client
|
87
115
|
requirement: !ruby/object:Gem::Requirement
|
88
116
|
requirements:
|
89
|
-
- -
|
117
|
+
- - ">="
|
90
118
|
- !ruby/object:Gem::Version
|
91
119
|
version: '0'
|
92
120
|
type: :runtime
|
93
121
|
prerelease: false
|
94
122
|
version_requirements: !ruby/object:Gem::Requirement
|
95
123
|
requirements:
|
96
|
-
- -
|
124
|
+
- - ">="
|
97
125
|
- !ruby/object:Gem::Version
|
98
126
|
version: '0'
|
99
127
|
- !ruby/object:Gem::Dependency
|
100
128
|
name: addressable
|
101
129
|
requirement: !ruby/object:Gem::Requirement
|
102
130
|
requirements:
|
103
|
-
- -
|
131
|
+
- - ">="
|
104
132
|
- !ruby/object:Gem::Version
|
105
133
|
version: '0'
|
106
134
|
type: :runtime
|
107
135
|
prerelease: false
|
108
136
|
version_requirements: !ruby/object:Gem::Requirement
|
109
137
|
requirements:
|
110
|
-
- -
|
138
|
+
- - ">="
|
111
139
|
- !ruby/object:Gem::Version
|
112
140
|
version: '0'
|
113
141
|
description: Wrapper for the Dor Fetcher Services RESTful API.
|
@@ -120,7 +148,7 @@ extensions: []
|
|
120
148
|
extra_rdoc_files: []
|
121
149
|
files:
|
122
150
|
- lib/dor-fetcher.rb
|
123
|
-
homepage:
|
151
|
+
homepage: https://github.com/sul-dlss/dor-fetcher
|
124
152
|
licenses:
|
125
153
|
- Apache-2.0
|
126
154
|
metadata: {}
|
@@ -130,19 +158,18 @@ require_paths:
|
|
130
158
|
- lib
|
131
159
|
required_ruby_version: !ruby/object:Gem::Requirement
|
132
160
|
requirements:
|
133
|
-
- -
|
161
|
+
- - ">="
|
134
162
|
- !ruby/object:Gem::Version
|
135
163
|
version: '0'
|
136
164
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
137
165
|
requirements:
|
138
|
-
- -
|
166
|
+
- - ">="
|
139
167
|
- !ruby/object:Gem::Version
|
140
168
|
version: '0'
|
141
169
|
requirements: []
|
142
170
|
rubyforge_project:
|
143
|
-
rubygems_version: 2.
|
171
|
+
rubygems_version: 2.7.8
|
144
172
|
signing_key:
|
145
173
|
specification_version: 4
|
146
174
|
summary: DorFetcher Gem
|
147
175
|
test_files: []
|
148
|
-
has_rdoc:
|