eval_in 0.1.6 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Readme.md +132 -0
- data/eval_in.gemspec +1 -1
- data/lib/eval_in.rb +5 -168
- data/lib/eval_in/client.rb +77 -0
- data/lib/eval_in/constants.rb +37 -0
- data/lib/eval_in/http.rb +46 -0
- data/lib/eval_in/mock.rb +80 -0
- data/lib/eval_in/result.rb +38 -0
- data/spec/client_spec.rb +195 -0
- data/spec/eval_in_spec.rb +91 -371
- data/spec/http_spec.rb +27 -0
- data/spec/mock_spec.rb +168 -0
- data/spec/result_spec.rb +93 -0
- data/spec/spec_helper.rb +23 -0
- metadata +17 -3
- data/lib/eval_in/version.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9a9e273c4e6a93761fc1f35b47e35242397e8559
|
4
|
+
data.tar.gz: d78b600e459c3adece521d7c2540209e6c138955
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3d6b167a6a021c1208dfac00a9de3078f90ee026bdf51198323afd14f2c2429ae00d49b63556ae1f092261a815852ac36c73bae57828d78bb8ab652fac62693e
|
7
|
+
data.tar.gz: 558dd782b6838e5c74945636bc9721cc684fd9ab3db11613de40f394eee39a6b888269a0c6bb40eb715f4547ebce905243c5dc25c2734fc9ab98369d6cd99ffb
|
data/Readme.md
CHANGED
@@ -153,6 +153,138 @@ What languages can this run?
|
|
153
153
|
</tr>
|
154
154
|
</table>
|
155
155
|
|
156
|
+
|
157
|
+
Mocking for non-prod environments
|
158
|
+
---------------------------------
|
159
|
+
|
160
|
+
### Wiring the mock in
|
161
|
+
|
162
|
+
The mock that is provided will need to be set into place.
|
163
|
+
If the code is directly doing `EvalIn.call(...)`, then it is
|
164
|
+
a hard dependency, and there is no ablity to set the mock into place.
|
165
|
+
|
166
|
+
You will need to structure your code such that it receives the `eval_in`
|
167
|
+
service as an argument, or looks it up in some configuration or something
|
168
|
+
(I strongly prefer the former).
|
169
|
+
This will allow your test environment to give it a mock that tests that it
|
170
|
+
works in all the edge cases, or is just generally benign (doesn't make real http request in tests),
|
171
|
+
your dev environment to give it an `EvalIn` that looks so close to the real one you wouldn't even know,
|
172
|
+
and your prod environment to give it the actual `EvalIn` that actually uses the service.
|
173
|
+
|
174
|
+
If this is still unclear, here is an example:
|
175
|
+
|
176
|
+
```ruby
|
177
|
+
# == before: hard dependency prevents you from giving a mock ==
|
178
|
+
def get_output(code)
|
179
|
+
EvalIn.call(code, language: 'ruby/mri-2.1').output
|
180
|
+
end
|
181
|
+
|
182
|
+
# == after: receive the thing you're talking to ==
|
183
|
+
def get_output(eval_in, code)
|
184
|
+
eval_in.call(code, language: 'ruby/mri-2.1').output
|
185
|
+
end
|
186
|
+
|
187
|
+
# in test, you call like this
|
188
|
+
output = 'the output'
|
189
|
+
assert_equal output, get_output(EvalIn::Mock.new(result: EvalIn::Result.new(output: output)), 'some code')
|
190
|
+
|
191
|
+
# in prod you call like this:
|
192
|
+
get_output(EvalIn, params[:code_sample])
|
193
|
+
|
194
|
+
# in dev you call like this:
|
195
|
+
get_output(EvalIn::Mock.new(languages: {'ruby/mri-2.1' => {program: RbConfig.ruby, args: []}}),
|
196
|
+
params[:code_sample])
|
197
|
+
|
198
|
+
# Now those last_two are probably in the same controller, right?
|
199
|
+
# How do you get it to do the right thing in the right env, then?
|
200
|
+
# well, you pass it to your controller, the same way you passed the `get_output`
|
201
|
+
# for example, you might do this in a rack middleware,
|
202
|
+
# then have it get the service from the `env` hash
|
203
|
+
# or set it in an environment initializer
|
204
|
+
```
|
205
|
+
|
206
|
+
### The provided mock
|
207
|
+
|
208
|
+
For a test or dev env where you don't care about correctness,
|
209
|
+
just that it does something that looks real,
|
210
|
+
you can make a mock that has a `Result`,
|
211
|
+
instantiated with any values you care about.
|
212
|
+
|
213
|
+
```ruby
|
214
|
+
require 'eval_in/mock'
|
215
|
+
eval_in = EvalIn::Mock.new(result: EvalIn::Result.new(code: 'injected code', output: 'the output'))
|
216
|
+
|
217
|
+
eval_in.call('overridden code', language: 'irrelevant')
|
218
|
+
# => #<EvalIn::Result:0x007fb503a7a5e8
|
219
|
+
# @code="injected code",
|
220
|
+
# @exitstatus=-1,
|
221
|
+
# @language="",
|
222
|
+
# @language_friendly="",
|
223
|
+
# @output="the output",
|
224
|
+
# @status="",
|
225
|
+
# @url="">
|
226
|
+
```
|
227
|
+
|
228
|
+
If you want your environment to behave approximately like the real `eval_in`,
|
229
|
+
you can instantiate a mock that knows how to evaluate code locally.
|
230
|
+
This is necessary, because it doesn't know how to execute these languages
|
231
|
+
(eval.in does that, it just knows how to talk to eval.in).
|
232
|
+
So you must provide it with a list of languages and how to execute them
|
233
|
+
|
234
|
+
This is probably idea for a dev environment, the results will be the most realistic.
|
235
|
+
|
236
|
+
**NOTE THAT THIS DOES NOT WORK ON JRUBY**
|
237
|
+
|
238
|
+
```ruby
|
239
|
+
require 'eval_in/mock'
|
240
|
+
|
241
|
+
# a mock that can execute Ruby code and C code
|
242
|
+
eval_in = EvalIn::Mock.new(languages: {
|
243
|
+
'ruby/mri-2.1' => {program: RbConfig.ruby,
|
244
|
+
args: []
|
245
|
+
},
|
246
|
+
'c/gcc-4.9.1' => {program: RbConfig.ruby,
|
247
|
+
args: ['-e',
|
248
|
+
'system "gcc -x c -o /tmp/eval_in_c_example #{ARGV.first}"
|
249
|
+
exec "/tmp/eval_in_c_example"']
|
250
|
+
},
|
251
|
+
})
|
252
|
+
|
253
|
+
eval_in.call 'puts "hello from ruby!"; exit 123', language: 'ruby/mri-2.1'
|
254
|
+
# => #<EvalIn::Result:0x007fb503a7d518
|
255
|
+
# @code="puts \"hello from ruby!\"; exit 123",
|
256
|
+
# @exitstatus=123,
|
257
|
+
# @language="ruby/mri-2.1",
|
258
|
+
# @language_friendly="ruby/mri-2.1",
|
259
|
+
# @output="hello from ruby!\n",
|
260
|
+
# @status="OK (0.072 sec real, 0.085 sec wall, 8 MB, 19 syscalls)",
|
261
|
+
# @url="https://eval.in/207744.json">
|
262
|
+
|
263
|
+
eval_in.call '#include <stdio.h>
|
264
|
+
int main() {
|
265
|
+
puts("hello from c!");
|
266
|
+
}', language: 'c/gcc-4.9.1'
|
267
|
+
# => #<EvalIn::Result:0x007fb503a850b0
|
268
|
+
# @code="#include <stdio.h>\nint main() {\n puts(\"hello from c!\");\n}",
|
269
|
+
# @exitstatus=0,
|
270
|
+
# @language="c/gcc-4.9.1",
|
271
|
+
# @language_friendly="c/gcc-4.9.1",
|
272
|
+
# @output="hello from c!\n",
|
273
|
+
# @status="OK (0.072 sec real, 0.085 sec wall, 8 MB, 19 syscalls)",
|
274
|
+
# @url="https://eval.in/207744.json">
|
275
|
+
```
|
276
|
+
|
277
|
+
You can also provide a callback that will be invoked to handle the request.
|
278
|
+
This is probably ideal for testing more nuanced edge cases that the mock
|
279
|
+
doesn't inherently provide the ability to do.
|
280
|
+
|
281
|
+
```ruby
|
282
|
+
require 'eval_in/mock'
|
283
|
+
eval_in = EvalIn::Mock.new on_call: -> code, options { raise EvalIn::RequestError, 'does my code do the right thing in the event of an exception?' }
|
284
|
+
|
285
|
+
eval_in.call('code', language: 'any') rescue $! # => #<EvalIn::RequestError: does my code do the right thing in the event of an exception?>
|
286
|
+
```
|
287
|
+
|
156
288
|
Attribution
|
157
289
|
-----------
|
158
290
|
|
data/eval_in.gemspec
CHANGED
data/lib/eval_in.rb
CHANGED
@@ -1,80 +1,7 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require 'uri'
|
4
|
-
require 'json'
|
5
|
-
require 'net/http'
|
6
|
-
require 'pathname'
|
7
|
-
require 'eval_in/version'
|
1
|
+
require 'eval_in/http'
|
2
|
+
require 'eval_in/client'
|
8
3
|
|
9
4
|
module EvalIn
|
10
|
-
EvalInError = Class.new StandardError
|
11
|
-
RequestError = Class.new EvalInError
|
12
|
-
ResultNotFound = Class.new EvalInError
|
13
|
-
|
14
|
-
# @example Generated with
|
15
|
-
# nokogiri https://eval.in -e 'puts $_.xpath("//option/@value")'
|
16
|
-
KNOWN_LANGUAGES = %w[
|
17
|
-
c/gcc-4.4.3
|
18
|
-
c/gcc-4.9.1
|
19
|
-
c++/c++11-gcc-4.9.1
|
20
|
-
c++/gcc-4.4.3
|
21
|
-
c++/gcc-4.9.1
|
22
|
-
coffeescript/node-0.10.29-coffee-1.7.1
|
23
|
-
fortran/f95-4.4.3
|
24
|
-
haskell/hugs98-sep-2006
|
25
|
-
io/io-20131204
|
26
|
-
javascript/node-0.10.29
|
27
|
-
lua/lua-5.1.5
|
28
|
-
lua/lua-5.2.3
|
29
|
-
ocaml/ocaml-4.01.0
|
30
|
-
php/php-5.5.14
|
31
|
-
pascal/fpc-2.6.4
|
32
|
-
perl/perl-5.20.0
|
33
|
-
python/cpython-2.7.8
|
34
|
-
python/cpython-3.4.1
|
35
|
-
ruby/mri-1.0
|
36
|
-
ruby/mri-1.8.7
|
37
|
-
ruby/mri-1.9.3
|
38
|
-
ruby/mri-2.0.0
|
39
|
-
ruby/mri-2.1
|
40
|
-
slash/slash-head
|
41
|
-
assembly/nasm-2.07
|
42
|
-
]
|
43
|
-
|
44
|
-
# The data structure containing the final result
|
45
|
-
# its attributes default to null-objects for their given type
|
46
|
-
class Result
|
47
|
-
@attribute_names = [:exitstatus, :language, :language_friendly, :code, :output, :status, :url].freeze
|
48
|
-
attr_accessor *@attribute_names
|
49
|
-
class << self
|
50
|
-
attr_reader :attribute_names
|
51
|
-
end
|
52
|
-
|
53
|
-
def initialize(attributes={})
|
54
|
-
attributes = attributes.dup
|
55
|
-
self.exitstatus = attributes.delete(:exitstatus) || -1
|
56
|
-
self.language = attributes.delete(:language) || ""
|
57
|
-
self.language_friendly = attributes.delete(:language_friendly) || ""
|
58
|
-
self.code = attributes.delete(:code) || ""
|
59
|
-
self.output = attributes.delete(:output) || ""
|
60
|
-
self.status = attributes.delete(:status) || ""
|
61
|
-
self.url = attributes.delete(:url) || ""
|
62
|
-
stderr = attributes.delete(:stderr) || $stderr
|
63
|
-
stderr.puts "Unexpected attributes! #{attributes.keys.inspect}" if attributes.any?
|
64
|
-
end
|
65
|
-
|
66
|
-
# Returns representation of the result built out of JSON primitives (hash, string, int)
|
67
|
-
def as_json
|
68
|
-
self.class.attribute_names.each_with_object Hash.new do |name, attributes|
|
69
|
-
attributes[name.to_s] = public_send name
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def to_json
|
74
|
-
JSON.dump(as_json)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
5
|
# @param code [String] the code to evaluate.
|
79
6
|
# @option options [String] :language Mandatory, a language recognized by eval.in, such as any value in {KNOWN_LANGUAGES}.
|
80
7
|
# @option options [String] :url Override the url to post the code to
|
@@ -86,7 +13,7 @@ module EvalIn
|
|
86
13
|
# result = EvalIn.call 'puts "hello, #{gets}"', stdin: 'world', language: "ruby/mri-2.1"
|
87
14
|
# result.output # => "hello, world\n"
|
88
15
|
def self.call(code, options={})
|
89
|
-
url = post_code(code, options)
|
16
|
+
url = Client.post_code(code, options)
|
90
17
|
fetch_result url, options
|
91
18
|
end
|
92
19
|
|
@@ -97,97 +24,7 @@ module EvalIn
|
|
97
24
|
# result = EvalIn.fetch_result "https://eval.in/147.json"
|
98
25
|
# result.output # => "Hello Charlie! "
|
99
26
|
def self.fetch_result(raw_url, options={})
|
100
|
-
raw_json_url = jsonify_url(raw_url)
|
101
|
-
build_result fetch_result_json(raw_json_url, options)
|
102
|
-
end
|
103
|
-
|
104
|
-
# @api private
|
105
|
-
def self.post_code(code, options)
|
106
|
-
url = options.fetch(:url, "https://eval.in/")
|
107
|
-
input = options.fetch(:stdin, "")
|
108
|
-
language = options.fetch(:language) { raise ArgumentError, ":language is mandatory, but options only has #{options.keys.inspect}" }
|
109
|
-
form_data = {"utf8" => "√", "code" => code, "execute" => "on", "lang" => language, "input" => input}
|
110
|
-
|
111
|
-
result = post_request url, form_data, user_agent_for(options[:context])
|
112
|
-
|
113
|
-
if result.code == '302'
|
114
|
-
jsonify_url result['location']
|
115
|
-
elsif KNOWN_LANGUAGES.include? language
|
116
|
-
raise RequestError, "There was an unexpected error, we got back a response code of #{result.code}"
|
117
|
-
else
|
118
|
-
raise RequestError, "Perhaps language is wrong, you provided: #{language.inspect}\n"\
|
119
|
-
"Known languages are: #{KNOWN_LANGUAGES.inspect}"
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
# @api private
|
124
|
-
def self.fetch_result_json(raw_url, options={})
|
125
|
-
result = get_request raw_url, user_agent_for(options[:context])
|
126
|
-
return JSON.parse(result.body).merge('url' => raw_url) if result.body
|
127
|
-
raise ResultNotFound, "No json at #{raw_url.inspect}"
|
128
|
-
end
|
129
|
-
|
130
|
-
# @api private
|
131
|
-
def self.build_result(response_json)
|
132
|
-
exitstatus = case response_json['status']
|
133
|
-
when nil then nil # let it choose default
|
134
|
-
when /status (\d+)$/ then $1.to_i
|
135
|
-
when /^Forbidden/ then 1
|
136
|
-
else 0
|
137
|
-
end
|
138
|
-
|
139
|
-
Result.new exitstatus: exitstatus,
|
140
|
-
language: response_json['lang'],
|
141
|
-
language_friendly: response_json['lang_friendly'],
|
142
|
-
code: response_json['code'],
|
143
|
-
output: response_json['output'],
|
144
|
-
status: response_json['status'],
|
145
|
-
url: response_json['url']
|
146
|
-
end
|
147
|
-
|
148
|
-
# @api private
|
149
|
-
def self.jsonify_url(url)
|
150
|
-
uri = URI(url)
|
151
|
-
uri.path = Pathname.new(uri.path).sub_ext('.json').to_s
|
152
|
-
uri.to_s
|
153
|
-
end
|
154
|
-
|
155
|
-
# @private
|
156
|
-
def self.user_agent_for(context)
|
157
|
-
'http://rubygems.org/gems/eval_in'.tap do |agent|
|
158
|
-
context && agent.concat(" (#{context})")
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
# @api private
|
163
|
-
# Can't just use Net::HTTP.get, b/c it doesn't use ssl on 1.9.3
|
164
|
-
# https://github.com/ruby/ruby/blob/v2_1_2/lib/net/http.rb#L478-479
|
165
|
-
# https://github.com/ruby/ruby/blob/v1_9_3_547/lib/net/http.rb#L454
|
166
|
-
def self.get_request(raw_url, user_agent)
|
167
|
-
generic_request_for raw_url: raw_url,
|
168
|
-
request_type: Net::HTTP::Get,
|
169
|
-
user_agent: user_agent
|
170
|
-
end
|
171
|
-
|
172
|
-
# @private
|
173
|
-
def self.post_request(raw_url, form_data, user_agent)
|
174
|
-
generic_request_for raw_url: raw_url,
|
175
|
-
request_type: Net::HTTP::Post,
|
176
|
-
user_agent: user_agent,
|
177
|
-
form_data: form_data
|
178
|
-
end
|
179
|
-
|
180
|
-
# @private
|
181
|
-
# stole this out of implementation for post_form https://github.com/ruby/ruby/blob/2afed6eceff2951b949db7ded8167a75b431bad6/lib/net/http.rb#L503
|
182
|
-
# can use this to view the request: http.set_debug_output $stdout
|
183
|
-
def self.generic_request_for(params)
|
184
|
-
uri = URI params.fetch(:raw_url)
|
185
|
-
path = uri.path
|
186
|
-
path = '/' if path.empty?
|
187
|
-
request = params.fetch(:request_type).new(path)
|
188
|
-
request['User-Agent'] = params[:user_agent] if params.key? :user_agent
|
189
|
-
request.form_data = params[:form_data] if params.key? :form_data
|
190
|
-
request.basic_auth uri.user, uri.password if uri.user
|
191
|
-
Net::HTTP.start(uri.hostname, uri.port, use_ssl: (uri.scheme == 'https')) { |http| http.request request }
|
27
|
+
raw_json_url = HTTP.jsonify_url(raw_url)
|
28
|
+
Client.build_result Client.fetch_result_json(raw_json_url, options)
|
192
29
|
end
|
193
30
|
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'eval_in/http'
|
5
|
+
require 'eval_in/result'
|
6
|
+
require 'eval_in/constants'
|
7
|
+
|
8
|
+
module EvalIn
|
9
|
+
|
10
|
+
# Code that does the integration with the actual eval.in service
|
11
|
+
# It is primarily in place to help the toplevel methods wire things together
|
12
|
+
# in the ways that they need to.
|
13
|
+
#
|
14
|
+
# **This should be assumed volatile, and you should avoid depending on it.**
|
15
|
+
module Client
|
16
|
+
extend self
|
17
|
+
|
18
|
+
# @api private
|
19
|
+
def post_code(code, options)
|
20
|
+
url = options.fetch(:url, "https://eval.in/")
|
21
|
+
input = options.fetch(:stdin, "")
|
22
|
+
language = language_or_error_from options
|
23
|
+
form_data = {"utf8" => "√", "code" => code, "execute" => "on", "lang" => language, "input" => input}
|
24
|
+
|
25
|
+
result = HTTP.post_request url, form_data, user_agent_for(options[:context])
|
26
|
+
|
27
|
+
if result.code == '302'
|
28
|
+
HTTP.jsonify_url result['location']
|
29
|
+
elsif KNOWN_LANGUAGES.include? language
|
30
|
+
raise RequestError, "There was an unexpected error, we got back a response code of #{result.code}"
|
31
|
+
else
|
32
|
+
raise RequestError, "Perhaps language is wrong, you provided: #{language.inspect}\n"\
|
33
|
+
"Known languages are: #{KNOWN_LANGUAGES.inspect}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# @api private
|
38
|
+
def fetch_result_json(raw_url, options={})
|
39
|
+
result = HTTP.get_request raw_url, user_agent_for(options[:context])
|
40
|
+
return JSON.parse(result.body).merge('url' => raw_url) if result.body
|
41
|
+
raise ResultNotFound, "No json at #{raw_url.inspect}"
|
42
|
+
end
|
43
|
+
|
44
|
+
# @api private
|
45
|
+
def build_result(response_json)
|
46
|
+
exitstatus = case response_json['status']
|
47
|
+
when nil then nil # let Result choose default
|
48
|
+
when /status (\d+)$/ then $1.to_i
|
49
|
+
when /^Forbidden/ then 1
|
50
|
+
else 0
|
51
|
+
end
|
52
|
+
|
53
|
+
Result.new exitstatus: exitstatus,
|
54
|
+
language: response_json['lang'],
|
55
|
+
language_friendly: response_json['lang_friendly'],
|
56
|
+
code: response_json['code'],
|
57
|
+
output: response_json['output'],
|
58
|
+
status: response_json['status'],
|
59
|
+
url: response_json['url']
|
60
|
+
end
|
61
|
+
|
62
|
+
# @api private
|
63
|
+
def user_agent_for(context)
|
64
|
+
'http://rubygems.org/gems/eval_in'.tap do |agent|
|
65
|
+
agent << " (#{context})" if context
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# @api private
|
70
|
+
def language_or_error_from(options)
|
71
|
+
options.fetch :language do
|
72
|
+
raise ArgumentError, ":language is mandatory, but options only has #{options.keys.inspect}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module EvalIn
|
2
|
+
VERSION = '0.2.0'
|
3
|
+
|
4
|
+
EvalInError = Class.new StandardError
|
5
|
+
RequestError = Class.new EvalInError
|
6
|
+
ResultNotFound = Class.new EvalInError
|
7
|
+
|
8
|
+
# @example Generated with
|
9
|
+
# nokogiri https://eval.in -e 'puts $_.xpath("//option/@value")'
|
10
|
+
KNOWN_LANGUAGES = %w[
|
11
|
+
c/gcc-4.4.3
|
12
|
+
c/gcc-4.9.1
|
13
|
+
c++/c++11-gcc-4.9.1
|
14
|
+
c++/gcc-4.4.3
|
15
|
+
c++/gcc-4.9.1
|
16
|
+
coffeescript/node-0.10.29-coffee-1.7.1
|
17
|
+
fortran/f95-4.4.3
|
18
|
+
haskell/hugs98-sep-2006
|
19
|
+
io/io-20131204
|
20
|
+
javascript/node-0.10.29
|
21
|
+
lua/lua-5.1.5
|
22
|
+
lua/lua-5.2.3
|
23
|
+
ocaml/ocaml-4.01.0
|
24
|
+
php/php-5.5.14
|
25
|
+
pascal/fpc-2.6.4
|
26
|
+
perl/perl-5.20.0
|
27
|
+
python/cpython-2.7.8
|
28
|
+
python/cpython-3.4.1
|
29
|
+
ruby/mri-1.0
|
30
|
+
ruby/mri-1.8.7
|
31
|
+
ruby/mri-1.9.3
|
32
|
+
ruby/mri-2.0.0
|
33
|
+
ruby/mri-2.1
|
34
|
+
slash/slash-head
|
35
|
+
assembly/nasm-2.07
|
36
|
+
]
|
37
|
+
end
|