seeing_is_believing 2.2.0 → 3.0.0.beta.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Changelog.md +33 -0
- data/bin/seeing_is_believing +1 -1
- data/features/errors.feature +3 -3
- data/features/examples.feature +6 -6
- data/features/flags.feature +51 -196
- data/features/regression.feature +12 -3
- data/features/safe.feature +33 -0
- data/features/support/env.rb +20 -0
- data/features/xmpfilter-style.feature +156 -0
- data/lib/seeing_is_believing.rb +17 -35
- data/lib/seeing_is_believing/binary.rb +81 -176
- data/lib/seeing_is_believing/binary/align_chunk.rb +5 -7
- data/lib/seeing_is_believing/binary/align_file.rb +4 -5
- data/lib/seeing_is_believing/binary/align_line.rb +4 -4
- data/lib/seeing_is_believing/binary/annotate_end_of_file.rb +60 -0
- data/lib/seeing_is_believing/binary/annotate_every_line.rb +64 -0
- data/lib/seeing_is_believing/binary/annotate_xmpfilter_style.rb +133 -0
- data/lib/seeing_is_believing/binary/comment_formatter.rb +19 -5
- data/lib/seeing_is_believing/binary/comment_lines.rb +1 -1
- data/lib/seeing_is_believing/binary/commentable_lines.rb +1 -1
- data/lib/seeing_is_believing/binary/interpret_flags.rb +149 -0
- data/lib/seeing_is_believing/binary/parse_args.rb +96 -104
- data/lib/seeing_is_believing/binary/remove_annotations.rb +95 -0
- data/lib/seeing_is_believing/binary/rewrite_comments.rb +8 -30
- data/lib/seeing_is_believing/code.rb +99 -0
- data/lib/seeing_is_believing/evaluate_by_moving_files.rb +27 -19
- data/lib/seeing_is_believing/evaluate_with_eval_in.rb +27 -0
- data/lib/seeing_is_believing/event_stream/consumer.rb +111 -0
- data/lib/seeing_is_believing/event_stream/events.rb +16 -0
- data/lib/seeing_is_believing/event_stream/producer.rb +106 -0
- data/lib/seeing_is_believing/event_stream/update_result.rb +21 -0
- data/lib/seeing_is_believing/inspect_expressions.rb +24 -0
- data/lib/seeing_is_believing/parser_helpers.rb +1 -11
- data/lib/seeing_is_believing/result.rb +14 -56
- data/lib/seeing_is_believing/the_matrix.rb +14 -14
- data/lib/seeing_is_believing/version.rb +1 -1
- data/lib/seeing_is_believing/wrap_expressions.rb +32 -9
- data/seeing_is_believing.gemspec +7 -7
- data/spec/binary/comment_formatter_spec.rb +169 -18
- data/spec/binary/comment_lines_spec.rb +1 -1
- data/spec/binary/interpret_flags_spec.rb +307 -0
- data/spec/binary/parse_args_spec.rb +93 -91
- data/spec/binary/{clean_body_spec.rb → remove_annotations_spec.rb} +29 -22
- data/spec/binary/rewrite_comments_spec.rb +13 -13
- data/spec/code_spec.rb +49 -0
- data/spec/debugger_spec.rb +1 -1
- data/spec/evaluate_by_moving_files_spec.rb +7 -3
- data/spec/event_stream_spec.rb +390 -0
- data/spec/hard_core_ensure_spec.rb +1 -1
- data/spec/seeing_is_believing_spec.rb +137 -40
- data/spec/spec_helper.rb +3 -3
- data/spec/wrap_expressions_spec.rb +48 -35
- metadata +58 -35
- data/lib/seeing_is_believing/binary/add_annotations.rb +0 -144
- data/lib/seeing_is_believing/binary/clean_body.rb +0 -95
- data/lib/seeing_is_believing/has_exception.rb +0 -27
- data/lib/seeing_is_believing/line.rb +0 -90
- data/spec/line_spec.rb +0 -86
data/features/regression.feature
CHANGED
@@ -65,7 +65,7 @@ Feature:
|
|
65
65
|
if true # => true
|
66
66
|
return 1 # => 1
|
67
67
|
end
|
68
|
-
end
|
68
|
+
end # => {{method_result :m}}
|
69
69
|
m # => 1
|
70
70
|
"""
|
71
71
|
|
@@ -153,6 +153,7 @@ Feature:
|
|
153
153
|
'ç' # => "ç"
|
154
154
|
"""
|
155
155
|
|
156
|
+
|
156
157
|
Scenario: Some strings look like comments
|
157
158
|
Given the file "strings_that_look_like_comments.rb":
|
158
159
|
"""
|
@@ -166,6 +167,7 @@ Feature:
|
|
166
167
|
#{2}" # => "1\n 2"
|
167
168
|
"""
|
168
169
|
|
170
|
+
|
169
171
|
Scenario: Kori's bug (Issue #11)
|
170
172
|
Given the file "koris_bug.rb":
|
171
173
|
"""
|
@@ -194,6 +196,7 @@ Feature:
|
|
194
196
|
# ~> koris_bug.rb:5:in `<main>'
|
195
197
|
"""
|
196
198
|
|
199
|
+
|
197
200
|
Scenario: lambda-style fibonacci generator
|
198
201
|
Given the file "lambda_style_fib_gen.rb":
|
199
202
|
"""
|
@@ -220,7 +223,7 @@ Feature:
|
|
220
223
|
class Proc
|
221
224
|
def inspect
|
222
225
|
"<PROC>" # => "<PROC>", "<PROC>", "<PROC>", "<PROC>", "<PROC>", "<PROC>", "<PROC>", "<PROC>", "<PROC>", "<PROC>", "<PROC>", "<PROC>"
|
223
|
-
end
|
226
|
+
end # => {{method_result :inspect}}
|
224
227
|
end
|
225
228
|
|
226
229
|
generic_fib_gen = -> current, prev {
|
@@ -235,6 +238,7 @@ Feature:
|
|
235
238
|
n, fib_gen = fib_gen.() # => [1, <PROC>]
|
236
239
|
"""
|
237
240
|
|
241
|
+
|
238
242
|
Scenario: Repeated invocations
|
239
243
|
When I run "echo 'puts 1' | seeing_is_believing | seeing_is_believing"
|
240
244
|
Then stdout is:
|
@@ -244,6 +248,7 @@ Feature:
|
|
244
248
|
# >> 1
|
245
249
|
"""
|
246
250
|
|
251
|
+
|
247
252
|
Scenario: Heredoc at the end test
|
248
253
|
Given the file "heredoc_at_the_end.rb":
|
249
254
|
"""
|
@@ -261,6 +266,7 @@ Feature:
|
|
261
266
|
# >> omg
|
262
267
|
"""
|
263
268
|
|
269
|
+
|
264
270
|
Scenario: Long DATA segment in a valid file
|
265
271
|
Given the file "long_valid_data_segment.rb":
|
266
272
|
"""
|
@@ -288,6 +294,7 @@ Feature:
|
|
288
294
|
And the exit status is 2
|
289
295
|
And stdout is empty
|
290
296
|
|
297
|
+
|
291
298
|
Scenario: A program using system
|
292
299
|
Given the file "invoking_system.rb":
|
293
300
|
"""
|
@@ -307,6 +314,7 @@ Feature:
|
|
307
314
|
# !> world
|
308
315
|
"""
|
309
316
|
|
317
|
+
|
310
318
|
Scenario: A program overriding stdout/stderr
|
311
319
|
Given the file "black_hole.rb":
|
312
320
|
"""
|
@@ -364,6 +372,7 @@ Feature:
|
|
364
372
|
# ~> incorrect_wrapping.rb:1:in `<main>'
|
365
373
|
"""
|
366
374
|
|
375
|
+
|
367
376
|
Scenario: Can deal with hostile environments
|
368
377
|
Given the file "bang_object.rb":
|
369
378
|
"""
|
@@ -379,6 +388,6 @@ Feature:
|
|
379
388
|
"""
|
380
389
|
class Object
|
381
390
|
def !(a)
|
382
|
-
end
|
391
|
+
end # => {{method_result :!}}
|
383
392
|
end
|
384
393
|
"""
|
@@ -0,0 +1,33 @@
|
|
1
|
+
Feature: Running in safe-mode
|
2
|
+
|
3
|
+
It would be nice to be able to expose SiB via a bot like hubot.
|
4
|
+
To do this, though, you need to be able to trust the code that users submit.
|
5
|
+
I wrote https://github.com/JoshCheek/eval_in to use the https://eval.in
|
6
|
+
website to run code safely. SiB just needs to use it.
|
7
|
+
|
8
|
+
@wip
|
9
|
+
Scenario: Running normal code
|
10
|
+
Given the file 'safe-example1.rb' 'print "hello, #{gets}"'
|
11
|
+
And the stdin content "world"
|
12
|
+
When I run "seeing_is_believing --safe safe-example1.rb"
|
13
|
+
Then stderr is empty
|
14
|
+
And the exit status is 0
|
15
|
+
And stdout is:
|
16
|
+
"""
|
17
|
+
print "hello, #{gets}" # => nil
|
18
|
+
|
19
|
+
# >> hello, world
|
20
|
+
"""
|
21
|
+
|
22
|
+
Scenario: Unsafe code
|
23
|
+
Scenario: With --xmpfilter
|
24
|
+
Scenario: With incompatible options
|
25
|
+
Scenario: With --timeout
|
26
|
+
Scenario: With --as
|
27
|
+
Scenario: With --debug
|
28
|
+
Scenario: Wpper-bound number of captures to prevent it from consuming too many resources
|
29
|
+
--program program # Pass the program to execute as an argument
|
30
|
+
--load-path dir # a dir that should be added to the $LOAD_PATH
|
31
|
+
--require file # additional files to be required before running the program
|
32
|
+
--encoding encoding # sets file encoding, equivalent to Ruby's -Kx (see `man ruby` for valid values)
|
33
|
+
--shebang ruby-executable # if you want SiB to use some ruby other than the one in the path
|
data/features/support/env.rb
CHANGED
@@ -1,6 +1,22 @@
|
|
1
1
|
require_relative '../../lib/seeing_is_believing/version'
|
2
2
|
|
3
3
|
require 'haiti'
|
4
|
+
|
5
|
+
module SiBHelpers
|
6
|
+
def method_result(name)
|
7
|
+
@result = def __some_method__; end
|
8
|
+
if :__some_method__ == @result
|
9
|
+
name.inspect
|
10
|
+
elsif nil == @result
|
11
|
+
nil.inspect
|
12
|
+
else
|
13
|
+
raise "huh? #{@result.inspect}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
World SiBHelpers
|
19
|
+
|
4
20
|
Haiti.configure do |config|
|
5
21
|
config.proving_grounds_dir = File.expand_path '../../../proving_grounds', __FILE__
|
6
22
|
config.bin_dir = File.expand_path '../../../bin', __FILE__
|
@@ -17,3 +33,7 @@ Then 'stdout is the JSON:' do |json|
|
|
17
33
|
actual = JSON.parse(@last_executed.stdout)
|
18
34
|
expect(actual).to eq expected
|
19
35
|
end
|
36
|
+
|
37
|
+
Given %q(the file '$filename' '$body') do |filename, body|
|
38
|
+
Haiti::CommandLineHelpers.write_file filename, eval_curlies(body)
|
39
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
@xmpfilter
|
2
|
+
Feature: Xmpfilter style
|
3
|
+
Support the same (or highly similar) interface as xmpfilter,
|
4
|
+
so that people who use that lib can easily transition to SiB.
|
5
|
+
|
6
|
+
|
7
|
+
Scenario: --xmpfilter-style Generic updating of marked lines
|
8
|
+
Given the file "magic_comments.rb":
|
9
|
+
"""
|
10
|
+
1+1# =>
|
11
|
+
2+2 # => 10
|
12
|
+
"a
|
13
|
+
b" # =>
|
14
|
+
/a
|
15
|
+
b/ # =>
|
16
|
+
1
|
17
|
+
"omg"
|
18
|
+
# =>
|
19
|
+
"omg2"
|
20
|
+
# => "not omg2"
|
21
|
+
"""
|
22
|
+
When I run "seeing_is_believing --xmpfilter-style magic_comments.rb"
|
23
|
+
Then stderr is empty
|
24
|
+
And the exit status is 0
|
25
|
+
And stdout is:
|
26
|
+
"""
|
27
|
+
1+1# => 2
|
28
|
+
2+2 # => 4
|
29
|
+
"a
|
30
|
+
b" # => "a\n b"
|
31
|
+
/a
|
32
|
+
b/ # => /a\n b/
|
33
|
+
1
|
34
|
+
"omg"
|
35
|
+
# => "omg"
|
36
|
+
"omg2"
|
37
|
+
# => "omg2"
|
38
|
+
"""
|
39
|
+
|
40
|
+
|
41
|
+
Scenario: --xmpfilter-style uses pp to inspect annotations whose value comes from the previous line (#44)
|
42
|
+
Given the file "xmpfilter-prev-line1.rb":
|
43
|
+
"""
|
44
|
+
{ foo: 42,
|
45
|
+
bar: {
|
46
|
+
baz: 1,
|
47
|
+
buz: 2,
|
48
|
+
fuz: 3,
|
49
|
+
},
|
50
|
+
wibble: {
|
51
|
+
magic_word: "xyzzy",
|
52
|
+
}
|
53
|
+
} # =>
|
54
|
+
# =>
|
55
|
+
"""
|
56
|
+
When I run "seeing_is_believing --xmpfilter-style xmpfilter-prev-line1.rb"
|
57
|
+
Then stdout is:
|
58
|
+
"""
|
59
|
+
{ foo: 42,
|
60
|
+
bar: {
|
61
|
+
baz: 1,
|
62
|
+
buz: 2,
|
63
|
+
fuz: 3,
|
64
|
+
},
|
65
|
+
wibble: {
|
66
|
+
magic_word: "xyzzy",
|
67
|
+
}
|
68
|
+
} # => {:foo=>42, :bar=>{:baz=>1, :buz=>2, :fuz=>3}, :wibble=>{:magic_word=>"xyzzy"}}
|
69
|
+
# => {:foo=>42,
|
70
|
+
# :bar=>{:baz=>1, :buz=>2, :fuz=>3},
|
71
|
+
# :wibble=>{:magic_word=>"xyzzy"}}
|
72
|
+
"""
|
73
|
+
|
74
|
+
Scenario: --xmpfilter-style overrides previous multiline results
|
75
|
+
Given the file "xmpfilter-prev-line2.rb":
|
76
|
+
"""
|
77
|
+
{foo: 42, bar: {baz: 1, buz: 2, fuz: 3}, wibble: {magic_word: "xyzzy"}}
|
78
|
+
# =>
|
79
|
+
"""
|
80
|
+
When I run "seeing_is_believing --xmpfilter-style xmpfilter-prev-line2.rb | seeing_is_believing --xmpfilter-style"
|
81
|
+
Then stdout is:
|
82
|
+
"""
|
83
|
+
{foo: 42, bar: {baz: 1, buz: 2, fuz: 3}, wibble: {magic_word: "xyzzy"}}
|
84
|
+
# => {:foo=>42,
|
85
|
+
# :bar=>{:baz=>1, :buz=>2, :fuz=>3},
|
86
|
+
# :wibble=>{:magic_word=>"xyzzy"}}
|
87
|
+
"""
|
88
|
+
|
89
|
+
|
90
|
+
Scenario: --xmpfilter-style respects the line formatting (but not currently alignment strategies, it just preserves submitted alignment)
|
91
|
+
Given the file "xmpfilter_line_lengths.rb":
|
92
|
+
"""
|
93
|
+
'1' * 30 # =>
|
94
|
+
# =>
|
95
|
+
"""
|
96
|
+
When I run "seeing_is_believing --xmpfilter-style --line-length 19 xmpfilter_line_lengths.rb"
|
97
|
+
Then stdout is:
|
98
|
+
"""
|
99
|
+
'1' * 30 # => "1...
|
100
|
+
# => "1111111111...
|
101
|
+
"""
|
102
|
+
|
103
|
+
|
104
|
+
@josh1
|
105
|
+
Scenario: Errors on annotated lines
|
106
|
+
Given the file "xmpfilter_error_on_annotated_line.rb":
|
107
|
+
"""
|
108
|
+
raise "omg" # =>
|
109
|
+
"""
|
110
|
+
When I run "seeing_is_believing --xmpfilter-style xmpfilter_error_on_annotated_line.rb"
|
111
|
+
Then stderr is empty
|
112
|
+
And the exit status is 1
|
113
|
+
Then stdout is:
|
114
|
+
"""
|
115
|
+
raise "omg" # => # ~> RuntimeError: ZOMG\n!!!!
|
116
|
+
|
117
|
+
# ~> RuntimeError
|
118
|
+
# ~> omg
|
119
|
+
# ~>
|
120
|
+
# ~> xmpfilter_error_on_annotated_line.rb:1:in `<main>'
|
121
|
+
"""
|
122
|
+
|
123
|
+
|
124
|
+
@josh2
|
125
|
+
Scenario: Errors on unannotated lines
|
126
|
+
Given the file "xmpfilter_error_on_annotated_line.rb":
|
127
|
+
"""
|
128
|
+
raise "omg"
|
129
|
+
"""
|
130
|
+
When I run "seeing_is_believing --xmpfilter-style xmpfilter_error_on_annotated_line.rb"
|
131
|
+
Then stderr is empty
|
132
|
+
And the exit status is 1
|
133
|
+
Then stdout is:
|
134
|
+
"""
|
135
|
+
raise "omg" # =>
|
136
|
+
"""
|
137
|
+
|
138
|
+
|
139
|
+
Scenario: pp output on line with exception
|
140
|
+
|
141
|
+
|
142
|
+
Scenario: Cleaning previous output
|
143
|
+
Given the file "xmpfilter_cleaning.rb":
|
144
|
+
"""
|
145
|
+
1 # => "1...
|
146
|
+
# => "1111111111...
|
147
|
+
# "1111111111...
|
148
|
+
# normal comment
|
149
|
+
# => 123
|
150
|
+
"""
|
151
|
+
When I run "seeing_is_believing --xmpfilter-style --clean xmpfilter_cleaning.rb"
|
152
|
+
Then stdout is:
|
153
|
+
"""
|
154
|
+
1
|
155
|
+
# normal comment
|
156
|
+
"""
|
data/lib/seeing_is_believing.rb
CHANGED
@@ -1,12 +1,11 @@
|
|
1
1
|
require 'stringio'
|
2
2
|
require 'tmpdir'
|
3
|
-
require 'timeout'
|
4
3
|
|
5
4
|
require 'seeing_is_believing/result'
|
6
5
|
require 'seeing_is_believing/version'
|
7
6
|
require 'seeing_is_believing/debugger'
|
7
|
+
require 'seeing_is_believing/inspect_expressions'
|
8
8
|
require 'seeing_is_believing/evaluate_by_moving_files'
|
9
|
-
require 'seeing_is_believing/wrap_expressions'
|
10
9
|
|
11
10
|
class SeeingIsBelieving
|
12
11
|
BLANK_REGEX = /\A\s*\Z/
|
@@ -17,70 +16,53 @@ class SeeingIsBelieving
|
|
17
16
|
|
18
17
|
def initialize(program, options={})
|
19
18
|
@program = program
|
20
|
-
@matrix_filename = options.fetch :matrix_filename, 'seeing_is_believing/the_matrix' # how to hijack the env
|
21
19
|
@filename = options[:filename]
|
22
20
|
@stdin = to_stream options.fetch(:stdin, '')
|
23
|
-
@require = options.fetch :require,
|
21
|
+
@require = options.fetch :require, ['seeing_is_believing/the_matrix']
|
24
22
|
@load_path = options.fetch :load_path, []
|
25
23
|
@encoding = options.fetch :encoding, nil
|
26
24
|
@timeout = options[:timeout]
|
27
25
|
@debugger = options.fetch :debugger, Debugger.new(stream: nil)
|
28
26
|
@ruby_executable = options.fetch :ruby_executable, 'ruby'
|
29
27
|
@number_of_captures = options.fetch :number_of_captures, Float::INFINITY
|
28
|
+
@evaluator = options.fetch :evaluator, EvaluateByMovingFiles
|
29
|
+
@record_expressions = options.fetch :record_expressions, InspectExpressions # TODO: rename to wrap_expressions
|
30
30
|
end
|
31
31
|
|
32
32
|
def call
|
33
33
|
@memoized_result ||= begin
|
34
34
|
new_program = program_that_will_record_expressions
|
35
|
-
debugger.context("TRANSLATED PROGRAM") { new_program }
|
35
|
+
@debugger.context("TRANSLATED PROGRAM") { new_program }
|
36
36
|
result = result_for new_program
|
37
|
-
debugger.context("RESULT") { result.inspect }
|
37
|
+
@debugger.context("RESULT") { result.inspect }
|
38
38
|
result
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
42
|
private
|
43
43
|
|
44
|
-
attr_reader :debugger
|
45
|
-
|
46
44
|
def to_stream(string_or_stream)
|
47
45
|
return string_or_stream if string_or_stream.respond_to? :gets
|
48
46
|
StringIO.new string_or_stream
|
49
47
|
end
|
50
48
|
|
51
49
|
def program_that_will_record_expressions
|
52
|
-
|
53
|
-
before_all: "begin; $SiB.number_of_captures = #{number_of_captures_as_str}; ",
|
54
|
-
after_all: ";rescue Exception;"\
|
55
|
-
"lambda {"\
|
56
|
-
"line_number = $!.backtrace.grep(/\#{__FILE__}/).first[/:\\d+/][1..-1].to_i;"\
|
57
|
-
"$SiB.record_exception line_number, $!;"\
|
58
|
-
"$SiB.exitstatus = 1;"\
|
59
|
-
"$SiB.exitstatus = $!.status if $!.kind_of? SystemExit;"\
|
60
|
-
"}.call;"\
|
61
|
-
"end",
|
62
|
-
before_each: -> line_number { "$SiB.record_result(#{line_number}, (" },
|
63
|
-
after_each: -> line_number { "))" }
|
50
|
+
@record_expressions.call "#{@program.chomp}\n", @number_of_captures
|
64
51
|
end
|
65
52
|
|
66
53
|
def result_for(program)
|
67
54
|
Dir.mktmpdir "seeing_is_believing_temp_dir" do |dir|
|
68
55
|
filename = @filename || File.join(dir, 'program.rb')
|
69
|
-
EvaluateByMovingFiles.call program,
|
70
|
-
filename,
|
71
|
-
input_stream: @stdin,
|
72
|
-
matrix_filename: @matrix_filename,
|
73
|
-
require: @require,
|
74
|
-
load_path: @load_path,
|
75
|
-
encoding: @encoding,
|
76
|
-
timeout: @timeout,
|
77
|
-
ruby_executable: @ruby_executable,
|
78
|
-
debugger: @debugger
|
79
|
-
end
|
80
|
-
end
|
81
56
|
|
82
|
-
|
83
|
-
|
84
|
-
|
57
|
+
@evaluator.call program,
|
58
|
+
filename,
|
59
|
+
input_stream: @stdin,
|
60
|
+
require: @require,
|
61
|
+
load_path: @load_path,
|
62
|
+
encoding: @encoding,
|
63
|
+
timeout: @timeout,
|
64
|
+
ruby_executable: @ruby_executable,
|
65
|
+
debugger: @debugger
|
66
|
+
end
|
85
67
|
end
|
86
68
|
end
|
@@ -1,220 +1,125 @@
|
|
1
1
|
require 'seeing_is_believing'
|
2
2
|
require 'seeing_is_believing/binary/parse_args'
|
3
|
-
require 'seeing_is_believing/binary/
|
4
|
-
require 'seeing_is_believing/binary/
|
5
|
-
require 'timeout'
|
6
|
-
|
3
|
+
require 'seeing_is_believing/binary/interpret_flags'
|
4
|
+
require 'seeing_is_believing/binary/remove_annotations'
|
7
5
|
|
8
6
|
class SeeingIsBelieving
|
9
|
-
|
7
|
+
module Binary
|
10
8
|
SUCCESS_STATUS = 0
|
11
9
|
DISPLAYABLE_ERROR_STATUS = 1 # e.g. there was an error, but the output is legit (we can display exceptions)
|
12
10
|
NONDISPLAYABLE_ERROR_STATUS = 2 # e.g. an error like incorrect invocation or syntax that can't be displayed in the input program
|
13
11
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
STDERR_MARKER = "# !> "
|
18
|
-
|
19
|
-
VALUE_REGEX = /\A#\s*=>/
|
20
|
-
EXCEPTION_REGEX = /\A#\s*~>/
|
21
|
-
STDOUT_REGEX = /\A#\s*>>/
|
22
|
-
STDERR_REGEX = /\A#\s*!>/
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
attr_accessor :argv, :stdin, :stdout, :stderr, :timeout_error, :unexpected_exception
|
12
|
+
def self.call(argv, stdin, stdout, stderr)
|
13
|
+
flags = ParseArgs.call(argv)
|
14
|
+
options = InterpretFlags.new(flags, stdin, stdout)
|
27
15
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
self.stdout = stdout
|
32
|
-
self.stderr = stderr
|
33
|
-
end
|
34
|
-
|
35
|
-
def call
|
36
|
-
@exitstatus ||= begin
|
37
|
-
parse_flags
|
38
|
-
|
39
|
-
if flags_have_errors? then print_errors ; NONDISPLAYABLE_ERROR_STATUS
|
40
|
-
elsif should_print_help? then print_help ; SUCCESS_STATUS
|
41
|
-
elsif should_print_version? then print_version ; SUCCESS_STATUS
|
42
|
-
elsif has_filename? && file_dne? then print_file_dne ; NONDISPLAYABLE_ERROR_STATUS
|
43
|
-
elsif should_clean? then print_cleaned_program ; SUCCESS_STATUS
|
44
|
-
elsif invalid_syntax? then print_syntax_error ; NONDISPLAYABLE_ERROR_STATUS
|
45
|
-
elsif (evaluate_program; program_timedout?) then print_timeout_error ; NONDISPLAYABLE_ERROR_STATUS
|
46
|
-
elsif something_blew_up? then print_unexpected_error ; NONDISPLAYABLE_ERROR_STATUS
|
47
|
-
elsif output_as_json? then print_result_as_json ; SUCCESS_STATUS
|
48
|
-
else print_program ; exit_status
|
49
|
-
end
|
16
|
+
if options.errors.any?
|
17
|
+
stderr.puts options.errors.join("\n")
|
18
|
+
return NONDISPLAYABLE_ERROR_STATUS
|
50
19
|
end
|
51
|
-
end
|
52
|
-
|
53
|
-
private
|
54
20
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
end
|
60
|
-
|
61
|
-
def flags_have_errors?
|
62
|
-
flags[:errors].any?
|
63
|
-
end
|
64
|
-
|
65
|
-
def print_errors
|
66
|
-
stderr.puts flags[:errors].join("\n")
|
67
|
-
end
|
68
|
-
|
69
|
-
def should_print_help?
|
70
|
-
flags[:help]
|
71
|
-
end
|
72
|
-
|
73
|
-
def print_help
|
74
|
-
stdout.puts flags[:help]
|
75
|
-
end
|
76
|
-
|
77
|
-
def should_print_version?
|
78
|
-
flags[:version]
|
79
|
-
end
|
80
|
-
|
81
|
-
def print_version
|
82
|
-
stdout.puts SeeingIsBelieving::VERSION
|
83
|
-
end
|
84
|
-
|
85
|
-
def has_filename?
|
86
|
-
flags[:filename]
|
87
|
-
end
|
88
|
-
|
89
|
-
def file_dne?
|
90
|
-
!File.exist?(flags[:filename])
|
91
|
-
end
|
92
|
-
|
93
|
-
def print_file_dne
|
94
|
-
stderr.puts "#{flags[:filename]} does not exist!"
|
95
|
-
end
|
96
|
-
|
97
|
-
def should_clean?
|
98
|
-
flags[:clean]
|
99
|
-
end
|
100
|
-
|
101
|
-
def print_cleaned_program
|
102
|
-
stdout.print CleanBody.call(body, true)
|
103
|
-
end
|
21
|
+
if options.print_help? # TODO: Should this be first?
|
22
|
+
stdout.puts options.help_screen
|
23
|
+
return SUCCESS_STATUS
|
24
|
+
end
|
104
25
|
|
105
|
-
|
106
|
-
|
107
|
-
|
26
|
+
if options.print_version?
|
27
|
+
stdout.puts SeeingIsBelieving::VERSION
|
28
|
+
return SUCCESS_STATUS
|
29
|
+
end
|
108
30
|
|
109
|
-
|
110
|
-
|
111
|
-
|
31
|
+
if options.provided_filename_dne?
|
32
|
+
stderr.puts "#{options.filename} does not exist!"
|
33
|
+
return NONDISPLAYABLE_ERROR_STATUS
|
34
|
+
end
|
112
35
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
rescue Exception
|
118
|
-
self.unexpected_exception = $!
|
119
|
-
end
|
36
|
+
if options.print_cleaned?
|
37
|
+
stdout.print RemoveAnnotations.call(options.prepared_body, true, options.markers)
|
38
|
+
return SUCCESS_STATUS
|
39
|
+
end
|
120
40
|
|
121
|
-
|
122
|
-
|
123
|
-
|
41
|
+
syntax_error_notice = syntax_error_notice_for(options.body, options.shebang)
|
42
|
+
if syntax_error_notice
|
43
|
+
stderr.puts syntax_error_notice
|
44
|
+
return NONDISPLAYABLE_ERROR_STATUS
|
45
|
+
end
|
124
46
|
|
125
|
-
|
126
|
-
|
127
|
-
end
|
47
|
+
results, program_timedout, unexpected_exception =
|
48
|
+
evaluate_program(options.prepared_body, options.lib_options)
|
128
49
|
|
129
|
-
|
130
|
-
|
131
|
-
|
50
|
+
if program_timedout
|
51
|
+
stderr.puts "Timeout Error after #{options.timeout} seconds!"
|
52
|
+
return NONDISPLAYABLE_ERROR_STATUS
|
53
|
+
end
|
132
54
|
|
133
|
-
def print_unexpected_error
|
134
55
|
if unexpected_exception.kind_of? BugInSib
|
135
56
|
stderr.puts unexpected_exception.message
|
136
|
-
|
137
|
-
stderr.puts unexpected_exception.class, unexpected_exception.message, "", unexpected_exception.backtrace
|
57
|
+
return NONDISPLAYABLE_ERROR_STATUS
|
138
58
|
end
|
139
|
-
end
|
140
|
-
|
141
|
-
def output_as_json?
|
142
|
-
flags[:result_as_json]
|
143
|
-
end
|
144
59
|
|
145
|
-
|
146
|
-
|
147
|
-
|
60
|
+
if unexpected_exception
|
61
|
+
stderr.puts unexpected_exception.class,
|
62
|
+
unexpected_exception.message,
|
63
|
+
"",
|
64
|
+
unexpected_exception.backtrace
|
65
|
+
return NONDISPLAYABLE_ERROR_STATUS
|
66
|
+
end
|
148
67
|
|
149
|
-
|
150
|
-
|
151
|
-
|
68
|
+
if options.result_as_json?
|
69
|
+
require 'json'
|
70
|
+
stdout.puts JSON.dump(result_as_data_structure(results))
|
71
|
+
return SUCCESS_STATUS
|
72
|
+
end
|
152
73
|
|
153
|
-
|
154
|
-
|
155
|
-
|
74
|
+
# TODO: Annoying debugger stuff from annotators can move up to here
|
75
|
+
# or maybe debugging goes to stderr, and we still print this anyway?
|
76
|
+
stdout.print options.annotator.call(options.prepared_body,
|
77
|
+
results,
|
78
|
+
options.annotator_options)
|
156
79
|
|
157
|
-
|
158
|
-
|
80
|
+
if options.inherit_exit_status?
|
81
|
+
results.exitstatus
|
82
|
+
elsif results.has_exception?
|
83
|
+
DISPLAYABLE_ERROR_STATUS
|
84
|
+
else
|
85
|
+
SUCCESS_STATUS
|
86
|
+
end
|
159
87
|
end
|
160
88
|
|
161
|
-
|
162
|
-
flags[:filename].nil? && flags[:program].nil?
|
163
|
-
end
|
89
|
+
private
|
164
90
|
|
165
|
-
def
|
166
|
-
|
167
|
-
|
168
|
-
err unless syntax_status.success?
|
91
|
+
def self.syntax_error_notice_for(body, shebang)
|
92
|
+
out, err, syntax_status = Open3.capture3 shebang, '-c', stdin_data: body
|
93
|
+
return err unless syntax_status.success?
|
169
94
|
|
170
95
|
# The stdin_data may still be getting written when the pipe closes
|
171
96
|
# This is because Ruby will stop reading from stdin if everything left is in the DATA segment, and the data segment is not referenced.
|
172
97
|
# In this case, the Syntax is fine
|
173
98
|
# https://bugs.ruby-lang.org/issues/9583
|
174
|
-
|
175
|
-
|
176
|
-
end
|
99
|
+
rescue Errno::EPIPE
|
100
|
+
return nil
|
177
101
|
end
|
178
102
|
|
179
|
-
def
|
180
|
-
|
181
|
-
|
103
|
+
def self.evaluate_program(body, options)
|
104
|
+
return SeeingIsBelieving.call(body, options), nil, nil
|
105
|
+
rescue Timeout::Error
|
106
|
+
return nil, true, nil
|
107
|
+
rescue Exception
|
108
|
+
return nil, false, $!
|
182
109
|
end
|
183
110
|
|
184
|
-
def result_as_data_structure
|
185
|
-
|
111
|
+
def self.result_as_data_structure(results)
|
112
|
+
exception = results.has_exception? && { line_number_in_this_file: results.exception.line_number,
|
113
|
+
class_name: results.exception.class_name,
|
114
|
+
message: results.exception.message,
|
115
|
+
backtrace: results.exception.backtrace
|
116
|
+
}
|
186
117
|
{ stdout: results.stdout,
|
187
118
|
stderr: results.stderr,
|
188
119
|
exit_status: results.exitstatus,
|
189
|
-
exception:
|
190
|
-
|
191
|
-
class_name: results.exception.class_name,
|
192
|
-
message: results.exception.message,
|
193
|
-
backtrace: results.exception.backtrace,
|
194
|
-
}
|
195
|
-
end),
|
196
|
-
lines: results.each_with_line_number.each_with_object(Hash.new) { |(line_number, result), hash|
|
197
|
-
hash[line_number] = { results: result.to_a,
|
198
|
-
exception: (if result.has_exception?
|
199
|
-
{ class_name: results.exception.class_name,
|
200
|
-
message: results.exception.message,
|
201
|
-
backtrace: results.exception.backtrace,
|
202
|
-
}
|
203
|
-
end)
|
204
|
-
}
|
205
|
-
},
|
120
|
+
exception: exception,
|
121
|
+
lines: results.each.with_object(Hash.new).with_index(1) { |(result, hash), line_number| hash[line_number] = result },
|
206
122
|
}
|
207
123
|
end
|
208
|
-
|
209
|
-
def exit_status
|
210
|
-
if flags[:inherit_exit_status]
|
211
|
-
results.exitstatus
|
212
|
-
elsif results.has_exception?
|
213
|
-
DISPLAYABLE_ERROR_STATUS
|
214
|
-
else
|
215
|
-
SUCCESS_STATUS
|
216
|
-
end
|
217
|
-
end
|
218
|
-
|
219
124
|
end
|
220
125
|
end
|