seeing_is_believing 2.2.0 → 3.0.0.beta.1
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/.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
|