bento_search 0.9.0 → 1.0.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.
- data/README.md +47 -14
- data/app/item_decorators/bento_search/standard_decorator.rb +30 -12
- data/app/models/bento_search/link.rb +4 -0
- data/app/models/bento_search/multi_searcher.rb +3 -1
- data/app/models/bento_search/registrar.rb +31 -2
- data/app/models/bento_search/result_item.rb +52 -14
- data/app/models/bento_search/search_engine.rb +18 -12
- data/app/search_engines/bento_search/google_books_engine.rb +9 -2
- data/app/search_engines/bento_search/google_site_search_engine.rb +1 -1
- data/app/search_engines/bento_search/summon_engine.rb +13 -5
- data/app/search_engines/bento_search/worldcat_sru_dc_engine.rb +1 -1
- data/app/views/bento_search/_atom_item.atom.builder +135 -0
- data/app/views/bento_search/atom_results.atom.builder +61 -0
- data/lib/bento_search.rb +4 -2
- data/lib/bento_search/version.rb +1 -1
- data/test/dummy/config/routes.rb +3 -1
- data/test/dummy/log/test.log +45768 -0
- data/test/support/atom.xsd.xml +240 -0
- data/test/unit/openurl_creator_test.rb +2 -4
- data/test/unit/register_engine_test.rb +59 -0
- data/test/unit/summon_engine_test.rb +1 -5
- data/test/view/atom_results_test.rb +281 -0
- metadata +11 -5
data/README.md
CHANGED
@@ -2,10 +2,6 @@
|
|
2
2
|
|
3
3
|
[](http://travis-ci.org/jrochkind/bento_search)
|
4
4
|
|
5
|
-
(Fairly robust and stable at this point, but still pre-1.0 release, may
|
6
|
-
be some breaking api changes before 1.0, but probably not too many, it's
|
7
|
-
looking pretty good).
|
8
|
-
|
9
5
|
bento_search provides an abstraction/normalization layer for querying and
|
10
6
|
displaying results from external search engines, in Ruby on Rails. Requires
|
11
7
|
Rails3 and tested only under ruby 1.9.3.
|
@@ -88,14 +84,14 @@ may be required for certain engines.
|
|
88
84
|
results = engine.search("a query")
|
89
85
|
~~~~
|
90
86
|
|
91
|
-
`results` are a BentoSearch::Results object, which acts like an array of
|
92
|
-
BentoSearch::Item objects, along with some meta-information about the
|
87
|
+
`results` are a [BentoSearch::Results](./app/models/bento_search/results.rb) object, which acts like an array of
|
88
|
+
[BentoSearch::Item](./app/models/bento_search/results.rb) objects, along with some meta-information about the
|
93
89
|
search itself (pagination keys, etc). BentoSearch::Results and Item fields
|
94
90
|
are standardized accross engines. BentoSearch::Items provide semantic
|
95
91
|
values (title, author, etc.), as available from the particular engine.
|
96
92
|
|
97
93
|
To see which engines come bundled with BentoSearch, and any special
|
98
|
-
engine-specific instructions, look at BentoSearch source in `./app/search_engines`
|
94
|
+
engine-specific instructions, look at BentoSearch source in [`./app/search_engines/bento_search`](./app/search_engines/bento_search)
|
99
95
|
|
100
96
|
### Register engines in global configuration
|
101
97
|
|
@@ -211,7 +207,7 @@ An engine instance advertises it's maximum per-page values.
|
|
211
207
|
bento_search fixes the default per_page at 10.
|
212
208
|
|
213
209
|
For help creating your UI, you can ask a BentoSearch::Results for
|
214
|
-
`results.pagination`, which returns a BentoSearch::Results::Pagination
|
210
|
+
`results.pagination`, which returns a [BentoSearch::Results::Pagination](app/models/bento_search/results/pagination.rb)
|
215
211
|
object which should be suitable for passing to [kaminari](https://github.com/amatsuda/kaminari)
|
216
212
|
`paginate`, or else have convenient methods for roll your own pagination UI.
|
217
213
|
Kaminari's paginate method:
|
@@ -259,7 +255,7 @@ that Celluloid uses multi-threading in such a way that you might need
|
|
259
255
|
to turn Rails config.cache_classes=true even in development.
|
260
256
|
|
261
257
|
|
262
|
-
For more info, see BentoSearch::MultiSearcher.
|
258
|
+
For more info, see [BentoSearch::MultiSearcher](./app/models/bento_search/multi_searcher.rb).
|
263
259
|
|
264
260
|
### Delayed results loading via AJAX (actually more like AJAHtml)
|
265
261
|
|
@@ -289,7 +285,8 @@ link resolver.
|
|
289
285
|
BentoSearch::Items can have a main link associated with them (generally
|
290
286
|
hyperlinked from title), as well as a list of additional links. Most engines
|
291
287
|
do not provide additional links by default, custom local Decorators would
|
292
|
-
be used to add them. See wiki
|
288
|
+
be used to add them. See [wiki on display cusotmization](https://github.com/jrochkind/bento_search/wiki/Customizing-Results-Display)
|
289
|
+
for more info on decorators, and [BentoSearch::Link](app/models/bento_search/link.rb)
|
293
290
|
for fields.
|
294
291
|
|
295
292
|
### OpenURL and metadata
|
@@ -307,8 +304,8 @@ documented at ResultItem#format). As well as how well the #to_openurl routine
|
|
307
304
|
handles all edge cases (OpenURL can be weird). As edge cases are discovered, they
|
308
305
|
can be solved.
|
309
306
|
|
310
|
-
See `./app/item_decorators/bento_search/openurl_add_other_link.rb`
|
311
|
-
of using item decorators to add a link to your openurl resover to an item when
|
307
|
+
See [`./app/item_decorators/bento_search/openurl_add_other_link.rb`](./app/item_decorators/bento_search/openurl_add_other_link.rb)
|
308
|
+
for an example of using item decorators to add a link to your openurl resover to an item when
|
312
309
|
displayed.
|
313
310
|
|
314
311
|
### Exporting (eg as RIS) and get by unique_id
|
@@ -322,8 +319,34 @@ the RIS format, suitable for import into EndNote, Refworks, etc.
|
|
322
319
|
|
323
320
|
Accomodating actual exports into the transactional flow of a web app can be
|
324
321
|
tricky, and often requires use of the `result_item#unique_id` and
|
325
|
-
`engine.get( unique_id )` features. See the wiki
|
322
|
+
`engine.get( unique_id )` features. See the wiki on [exports and #unique_id](https://github.com/jrochkind/bento_search/wiki/Exports-and-the-get-by-unique_id-feature)
|
323
|
+
|
324
|
+
### Machine-readable serialization in Atom
|
325
|
+
|
326
|
+
Translation of any BentoSearch::Results to an Atom response that is enhanced to
|
327
|
+
include nearly all the elements of each BentoSearch::ResultItem, so can serves
|
328
|
+
well as machine-readable api response in general, not just for Atom feed readers.
|
329
|
+
|
330
|
+
You can use the [`bento_search/atom_results`](./app/views/bento_search/atom_results.atom.builder) view template, perhaps
|
331
|
+
in your action method like so:
|
332
|
+
|
333
|
+
~~~ruby
|
334
|
+
# ...
|
335
|
+
respond_to do |format|
|
336
|
+
format.html # default view
|
337
|
+
format.atom do
|
338
|
+
render( :template => "bento_search/atom_results",
|
339
|
+
:locals => {
|
340
|
+
:atom_results => @results,
|
341
|
+
:feed_name => "Acme results",
|
342
|
+
:feed_author_name => "MyCorp"
|
343
|
+
}
|
344
|
+
)
|
345
|
+
end
|
346
|
+
~~~
|
326
347
|
|
348
|
+
There are additional details that might matter to you, for more info see the
|
349
|
+
[wiki page](https://github.com/jrochkind/bento_search/wiki/Machine-Readable-Serialization-With-Atom)
|
327
350
|
|
328
351
|
## Planned Features
|
329
352
|
|
@@ -340,7 +363,17 @@ Probably:
|
|
340
363
|
a normalized cross-engine way.
|
341
364
|
|
342
365
|
Other needs or suggestions?
|
343
|
-
|
366
|
+
|
367
|
+
## Backwards compat
|
368
|
+
|
369
|
+
We are going to try to be strictly backwards compatible with all post 1.0
|
370
|
+
releases that do not increment the major version number (semantic versioning).
|
371
|
+
|
372
|
+
As a general rule, we're going to let our tests enforce this -- if a test has
|
373
|
+
to be changed to pass with new code, that's a very strong sign that it is
|
374
|
+
not a backwards-compat change, and you should think _very_ carefully to
|
375
|
+
be sure it is an exception to this rule before changing any existing tests
|
376
|
+
for new functionality.
|
344
377
|
|
345
378
|
## Developing
|
346
379
|
|
@@ -87,18 +87,14 @@ module BentoSearch
|
|
87
87
|
self.any_present?(:source_title, :publisher, :start_page)
|
88
88
|
end
|
89
89
|
|
90
|
-
#
|
90
|
+
# Mix-in a default missing title marker for empty titles
|
91
|
+
# (Used to combine title and subtitle when those were different fields)
|
91
92
|
def complete_title
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
if t.blank?
|
98
|
-
t = I18n.translate("bento_search.missing_title")
|
99
|
-
end
|
100
|
-
|
101
|
-
return t
|
93
|
+
if self.title.present?
|
94
|
+
self.title
|
95
|
+
else
|
96
|
+
I18n.translate("bento_search.missing_title")
|
97
|
+
end
|
102
98
|
end
|
103
99
|
|
104
100
|
|
@@ -125,7 +121,7 @@ module BentoSearch
|
|
125
121
|
return result_elements.join(", ").html_safe
|
126
122
|
end
|
127
123
|
|
128
|
-
|
124
|
+
# A display method, this is like #langauge_str, but will be nil if
|
129
125
|
# the language_code matches the current default locale, used
|
130
126
|
# for printing language only when not "English" normally.
|
131
127
|
#
|
@@ -152,6 +148,28 @@ module BentoSearch
|
|
152
148
|
|
153
149
|
return value.blank? ? nil : value
|
154
150
|
end
|
151
|
+
|
152
|
+
# A unique opaque identifier for a record may sometimes be
|
153
|
+
# required, for instance in Atom.
|
154
|
+
#
|
155
|
+
# We here provide a really dumb implementation, if and only if
|
156
|
+
# the result has an engine_id and unique_id available, (and
|
157
|
+
# a #root_url is available) by basically concatenating them to
|
158
|
+
# app base url.
|
159
|
+
#
|
160
|
+
# That's pretty lame, probably not resolvable, but best we
|
161
|
+
# can do without knowing details of host app. You may want
|
162
|
+
# to over-ride this in a decorator to do something more valid
|
163
|
+
# in an app-specific way.
|
164
|
+
#
|
165
|
+
# yes uri_identifier is like PIN number, deal with it.
|
166
|
+
def uri_identifier
|
167
|
+
if self.engine_id.present? && self.unique_id.present? && _h.respond_to?(:root_url)
|
168
|
+
"#{_h.root_url.chomp("/")}/bento_search_opaque_id/#{CGI.escape self.engine_id}/#{CGI.escape self.unique_id}"
|
169
|
+
else
|
170
|
+
nil
|
171
|
+
end
|
172
|
+
end
|
155
173
|
|
156
174
|
|
157
175
|
###################
|
@@ -13,6 +13,10 @@ module BentoSearch
|
|
13
13
|
# http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#linkTypes
|
14
14
|
attr_accessor :rel
|
15
15
|
|
16
|
+
# MIME content type may be used for both HMTL links and Atom
|
17
|
+
# link 'type' attribute
|
18
|
+
attr_accessor :type
|
19
|
+
|
16
20
|
# Array of strings, used for CSS classes on this link, possibly
|
17
21
|
# for custom styles/images etc. May be used in non-html link
|
18
22
|
# contexts too.
|
@@ -102,8 +102,10 @@ class BentoSearch::MultiSearcher
|
|
102
102
|
Rails.logger.error("\nBentoSearch:MultiSearcher caught exception: #{e}\n#{e.backtrace.join(" \n")}")
|
103
103
|
# Make a fake results with caught exception.
|
104
104
|
@results = BentoSearch::Results.new
|
105
|
+
self.engine.fill_in_search_metadata_for(@results, self.engine.normalized_search_arguments(search_args))
|
106
|
+
|
105
107
|
@results.error ||= {}
|
106
|
-
@results.error["exception"] = e
|
108
|
+
@results.error["exception"] = e
|
107
109
|
end
|
108
110
|
end
|
109
111
|
|
@@ -24,13 +24,42 @@ class BentoSearch::Registrar
|
|
24
24
|
#
|
25
25
|
# The first parameter identifier, eg "gbs", may be used in some
|
26
26
|
# URLs, for AJAX etc.
|
27
|
-
|
28
|
-
|
27
|
+
#
|
28
|
+
# You can also pass in a hash or hash-like object (including
|
29
|
+
# a configuration object returned by a prior register_engine)
|
30
|
+
# instead of or in addition to the block 'dsl' -- this can be used
|
31
|
+
# to base one configuration off another, with changes:
|
32
|
+
#
|
33
|
+
# BentoSearch.register_engine("original", {
|
34
|
+
# :engine => "Something",
|
35
|
+
# :title => "Original",
|
36
|
+
# :shared => "shared"
|
37
|
+
# })
|
38
|
+
#
|
39
|
+
# BentoSearch.register_engine("derived") do |conf|
|
40
|
+
# conf.title = "Derived"
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# Above would not change 'shared' in 'original', but would
|
44
|
+
# over-ride 'title' in 'derived', without changing 'title' in
|
45
|
+
# 'original'.
|
46
|
+
def register_engine(id, conf_data = nil, &block)
|
47
|
+
conf = Confstruct::Configuration.new
|
48
|
+
|
49
|
+
# Make sure we make a deep_copy so any changes don't mutate
|
50
|
+
# the original. Confstruct can be unpredictable.
|
51
|
+
if conf_data.present?
|
52
|
+
conf_data = Confstruct::Configuration.new(conf_data).deep_copy
|
53
|
+
end
|
54
|
+
|
55
|
+
conf.configure(conf_data, &block)
|
29
56
|
conf.id = id.to_s
|
30
57
|
|
31
58
|
raise ArgumentError.new("Must supply an `engine` class name") unless conf.engine
|
32
59
|
|
33
60
|
@registered_engine_confs[id] = conf
|
61
|
+
|
62
|
+
return conf
|
34
63
|
end
|
35
64
|
|
36
65
|
# Get a configured SearchEngine, using configuration and engine
|
@@ -44,12 +44,8 @@ module BentoSearch
|
|
44
44
|
# * dc.title
|
45
45
|
# * schema.org CreativeWork: 'name'
|
46
46
|
attr_accessor :title
|
47
|
-
|
48
|
-
|
49
|
-
# May also be nil with subtitle in "title" field after colon.
|
50
|
-
#
|
51
|
-
# *
|
52
|
-
attr_accessor :subtitle
|
47
|
+
# backwards compat, we used to have separate titles and subtitles
|
48
|
+
alias_method :complete_title, :title
|
53
49
|
|
54
50
|
# usually a direct link to the search provider's 'native' page.
|
55
51
|
# Can be changed in actual presentation with a Decorator.
|
@@ -65,12 +61,24 @@ module BentoSearch
|
|
65
61
|
@link_is_fulltext = v
|
66
62
|
end
|
67
63
|
|
68
|
-
#
|
69
|
-
#
|
64
|
+
# Our own INTERNAL controlled vocab for 'format'.
|
65
|
+
#
|
66
|
+
# Important that this be supplied by engine for maximum
|
67
|
+
# success of openurl, ris export, etc.
|
68
|
+
#
|
69
|
+
# This vocab is based on schema.org CreativeWork 'types',
|
70
|
+
# but supplemented with values we needed not present in schema.org.
|
71
|
+
# String values are last part of schema.org URLs, symbol values are custom.
|
72
|
+
#
|
73
|
+
# However, for backwards compat, values that didn't exist in schema.org
|
74
|
+
# when we started but later came to exist -- we still use our string
|
75
|
+
# values. If you actually want a schema.org url, see #schema_org_type_url
|
76
|
+
# which translates as needed.
|
70
77
|
#
|
71
78
|
# schema.org 'type' that's a sub-type of CreativeWork.
|
72
79
|
# should hold a string that, when appended to "http://schema.org/"
|
73
|
-
# is a valid schema.org type uri, that sub-types CreativeWork.
|
80
|
+
# is a valid schema.org type uri, that sub-types CreativeWork. Ones
|
81
|
+
# we have used:
|
74
82
|
# * Article
|
75
83
|
# * Book
|
76
84
|
# * Movie
|
@@ -93,7 +101,27 @@ module BentoSearch
|
|
93
101
|
#
|
94
102
|
# Note: We're re-thinking this, might allow uncontrolled
|
95
103
|
# in here instead.
|
96
|
-
attr_accessor :format
|
104
|
+
attr_accessor :format
|
105
|
+
|
106
|
+
# Translated from internal format vocab at #format. Outputs
|
107
|
+
# eg http://schema.org/Book
|
108
|
+
# Uses the @@format_to_schema_org hash for mapping from
|
109
|
+
# certain internal symbol values to schema org value, where
|
110
|
+
# possible.
|
111
|
+
#
|
112
|
+
# Can return nil if we don't know a schema.org type
|
113
|
+
def schema_org_type_url
|
114
|
+
if format.kind_of? String
|
115
|
+
"http://schema.org/#{format}"
|
116
|
+
elsif mapped = @@format_to_schema_org[format]
|
117
|
+
"http://schema.org/#{mapped}"
|
118
|
+
else
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
end
|
122
|
+
@@format_to_schema_org = {
|
123
|
+
:report => "Article",
|
124
|
+
}
|
97
125
|
|
98
126
|
# uncontrolled presumably english-language format string.
|
99
127
|
# if supplied will be used in display in place of controlled
|
@@ -110,10 +138,10 @@ module BentoSearch
|
|
110
138
|
# Manually set language_str will over-ride display string calculated from
|
111
139
|
# language_code.
|
112
140
|
#
|
113
|
-
# Consumers
|
114
|
-
# either
|
115
|
-
#
|
116
|
-
#
|
141
|
+
# Consumers that want a language code can use #language_iso_639_1 or
|
142
|
+
# #language_iso_639_2 (either may be null), or #language_str for uncontrolled
|
143
|
+
# string. If engine just sets one of these, internals take care of filling
|
144
|
+
# out the others. r
|
117
145
|
attr_accessor :language_code
|
118
146
|
attr_writer :language_str
|
119
147
|
def language_str
|
@@ -131,6 +159,16 @@ module BentoSearch
|
|
131
159
|
@language_obj ||= LanguageList::LanguageInfo.find( self.language_code )
|
132
160
|
end
|
133
161
|
|
162
|
+
# Two letter ISO language code, or nil
|
163
|
+
def language_iso_639_1
|
164
|
+
language_obj.try { |l| l.iso_639_1 }
|
165
|
+
end
|
166
|
+
|
167
|
+
# Three letter ISO language code, or nil
|
168
|
+
def language_iso_639_3
|
169
|
+
language_obj.try {|l| l.iso_639_3 }
|
170
|
+
end
|
171
|
+
|
134
172
|
# year published. a ruby int
|
135
173
|
# PART of:.
|
136
174
|
# * schema.org CreativeWork "datePublished", year portion
|
@@ -213,18 +213,11 @@ module BentoSearch
|
|
213
213
|
arguments = normalized_search_arguments(*arguments)
|
214
214
|
|
215
215
|
results = search_implementation(arguments)
|
216
|
-
|
217
|
-
|
218
|
-
# standard result metadata
|
219
|
-
results.start = arguments[:start] || 0
|
220
|
-
results.per_page = arguments[:per_page]
|
221
|
-
|
222
|
-
results.search_args = arguments
|
223
|
-
results.engine_id = configuration.id
|
224
216
|
|
217
|
+
fill_in_search_metadata_for(results, arguments)
|
218
|
+
|
225
219
|
results.timing = (Time.now - start_t)
|
226
220
|
|
227
|
-
results.display_configuration = configuration.for_display
|
228
221
|
results.each do |item|
|
229
222
|
# We copy some configuraton info over to each Item, as a convenience
|
230
223
|
# to display logic that may have decide what to do given only an item,
|
@@ -249,14 +242,27 @@ module BentoSearch
|
|
249
242
|
failed.error ||= {}
|
250
243
|
failed.error[:exception] = e
|
251
244
|
|
252
|
-
failed.search_args = arguments
|
253
|
-
failed.engine_id = configuration.id
|
254
|
-
failed.display_configuration = configuration.for_display
|
255
245
|
failed.timing = (Time.now - start_t)
|
246
|
+
|
247
|
+
fill_in_search_metadata_for(failed, arguments)
|
256
248
|
|
257
249
|
|
258
250
|
return failed
|
259
251
|
end
|
252
|
+
|
253
|
+
# SOME of the elements of Results to be returned that SearchEngine implementation
|
254
|
+
# fills in automatically post-search. Extracted into a method for DRY in
|
255
|
+
# error handling to try to fill these in even in errors. And *possible*
|
256
|
+
# experimental use in other classes for same thing is why method is
|
257
|
+
# public, see MultiSearcher.
|
258
|
+
def fill_in_search_metadata_for(results, normalized_arguments)
|
259
|
+
results.search_args = normalized_arguments
|
260
|
+
results.start = normalized_arguments[:start] || 0
|
261
|
+
results.per_page = normalized_arguments[:per_page]
|
262
|
+
|
263
|
+
results.engine_id = configuration.id
|
264
|
+
results.display_configuration = configuration.for_display
|
265
|
+
end
|
260
266
|
|
261
267
|
|
262
268
|
# Take the arguments passed into #search, which can be flexibly given
|
@@ -113,8 +113,7 @@ module BentoSearch
|
|
113
113
|
|
114
114
|
item.unique_id = item_response["id"]
|
115
115
|
|
116
|
-
item.title = v_info
|
117
|
-
item.subtitle = v_info["subtitle"]
|
116
|
+
item.title = format_title(v_info)
|
118
117
|
item.publisher = v_info["publisher"]
|
119
118
|
# previewLink gives you your search results highlighted, preferable
|
120
119
|
# if it exists.
|
@@ -255,6 +254,14 @@ module BentoSearch
|
|
255
254
|
end
|
256
255
|
return nil
|
257
256
|
end
|
257
|
+
|
258
|
+
def format_title(v_info)
|
259
|
+
title = v_info["title"]
|
260
|
+
if v_info["subtitle"]
|
261
|
+
title = "#{title}: #{v_info["subtitle"]}"
|
262
|
+
end
|
263
|
+
return title
|
264
|
+
end
|
258
265
|
|
259
266
|
end
|
260
267
|
end
|
@@ -121,11 +121,7 @@ class BentoSearch::SummonEngine
|
|
121
121
|
|
122
122
|
item.unique_id = first_if_present doc_hash["ID"]
|
123
123
|
|
124
|
-
item.title =
|
125
|
-
item.custom_data["raw_title"] = handle_highlighting( first_if_present(doc_hash["Title"]) , :strip => true)
|
126
|
-
|
127
|
-
item.subtitle = handle_highlighting( first_if_present doc_hash["Subtitle"] )# TODO is this right?
|
128
|
-
item.custom_data["raw_subtitle"] = handle_highlighting( first_if_present(doc_hash["Subtitle"]), :strip => true )
|
124
|
+
item.title = format_title(doc_hash)
|
129
125
|
|
130
126
|
item.link = doc_hash["link"]
|
131
127
|
# Don't understand difference between hasFullText and
|
@@ -356,6 +352,18 @@ class BentoSearch::SummonEngine
|
|
356
352
|
:strip => options[:strip]
|
357
353
|
)
|
358
354
|
end
|
355
|
+
|
356
|
+
# combine title and subtitle into one title,
|
357
|
+
def format_title(doc_hash)
|
358
|
+
title = first_if_present doc_hash["Title"]
|
359
|
+
subtitle = first_if_present doc_hash["Subtitle"]
|
360
|
+
|
361
|
+
if subtitle.present?
|
362
|
+
title = "#{title}: #{subtitle}"
|
363
|
+
end
|
364
|
+
|
365
|
+
return handle_highlighting( title )
|
366
|
+
end
|
359
367
|
|
360
368
|
def self.required_configuration
|
361
369
|
[:access_id, :secret_key]
|