eval_in 0.1.6 → 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 +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
|