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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/Changelog.md +33 -0
  4. data/bin/seeing_is_believing +1 -1
  5. data/features/errors.feature +3 -3
  6. data/features/examples.feature +6 -6
  7. data/features/flags.feature +51 -196
  8. data/features/regression.feature +12 -3
  9. data/features/safe.feature +33 -0
  10. data/features/support/env.rb +20 -0
  11. data/features/xmpfilter-style.feature +156 -0
  12. data/lib/seeing_is_believing.rb +17 -35
  13. data/lib/seeing_is_believing/binary.rb +81 -176
  14. data/lib/seeing_is_believing/binary/align_chunk.rb +5 -7
  15. data/lib/seeing_is_believing/binary/align_file.rb +4 -5
  16. data/lib/seeing_is_believing/binary/align_line.rb +4 -4
  17. data/lib/seeing_is_believing/binary/annotate_end_of_file.rb +60 -0
  18. data/lib/seeing_is_believing/binary/annotate_every_line.rb +64 -0
  19. data/lib/seeing_is_believing/binary/annotate_xmpfilter_style.rb +133 -0
  20. data/lib/seeing_is_believing/binary/comment_formatter.rb +19 -5
  21. data/lib/seeing_is_believing/binary/comment_lines.rb +1 -1
  22. data/lib/seeing_is_believing/binary/commentable_lines.rb +1 -1
  23. data/lib/seeing_is_believing/binary/interpret_flags.rb +149 -0
  24. data/lib/seeing_is_believing/binary/parse_args.rb +96 -104
  25. data/lib/seeing_is_believing/binary/remove_annotations.rb +95 -0
  26. data/lib/seeing_is_believing/binary/rewrite_comments.rb +8 -30
  27. data/lib/seeing_is_believing/code.rb +99 -0
  28. data/lib/seeing_is_believing/evaluate_by_moving_files.rb +27 -19
  29. data/lib/seeing_is_believing/evaluate_with_eval_in.rb +27 -0
  30. data/lib/seeing_is_believing/event_stream/consumer.rb +111 -0
  31. data/lib/seeing_is_believing/event_stream/events.rb +16 -0
  32. data/lib/seeing_is_believing/event_stream/producer.rb +106 -0
  33. data/lib/seeing_is_believing/event_stream/update_result.rb +21 -0
  34. data/lib/seeing_is_believing/inspect_expressions.rb +24 -0
  35. data/lib/seeing_is_believing/parser_helpers.rb +1 -11
  36. data/lib/seeing_is_believing/result.rb +14 -56
  37. data/lib/seeing_is_believing/the_matrix.rb +14 -14
  38. data/lib/seeing_is_believing/version.rb +1 -1
  39. data/lib/seeing_is_believing/wrap_expressions.rb +32 -9
  40. data/seeing_is_believing.gemspec +7 -7
  41. data/spec/binary/comment_formatter_spec.rb +169 -18
  42. data/spec/binary/comment_lines_spec.rb +1 -1
  43. data/spec/binary/interpret_flags_spec.rb +307 -0
  44. data/spec/binary/parse_args_spec.rb +93 -91
  45. data/spec/binary/{clean_body_spec.rb → remove_annotations_spec.rb} +29 -22
  46. data/spec/binary/rewrite_comments_spec.rb +13 -13
  47. data/spec/code_spec.rb +49 -0
  48. data/spec/debugger_spec.rb +1 -1
  49. data/spec/evaluate_by_moving_files_spec.rb +7 -3
  50. data/spec/event_stream_spec.rb +390 -0
  51. data/spec/hard_core_ensure_spec.rb +1 -1
  52. data/spec/seeing_is_believing_spec.rb +137 -40
  53. data/spec/spec_helper.rb +3 -3
  54. data/spec/wrap_expressions_spec.rb +48 -35
  55. metadata +58 -35
  56. data/lib/seeing_is_believing/binary/add_annotations.rb +0 -144
  57. data/lib/seeing_is_believing/binary/clean_body.rb +0 -95
  58. data/lib/seeing_is_believing/has_exception.rb +0 -27
  59. data/lib/seeing_is_believing/line.rb +0 -90
  60. data/spec/line_spec.rb +0 -86
@@ -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
@@ -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
+ """
@@ -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
- WrapExpressions.call "#{@program}\n",
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
- def number_of_captures_as_str
83
- return 'Float::INFINITY' if @number_of_captures == Float::INFINITY
84
- @number_of_captures.inspect
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/add_annotations'
4
- require 'seeing_is_believing/binary/clean_body'
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
- class Binary
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
- VALUE_MARKER = "# => "
15
- EXCEPTION_MARKER = "# ~> "
16
- STDOUT_MARKER = "# >> "
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
- def initialize(argv, stdin, stdout, stderr)
29
- self.argv = argv
30
- self.stdin = stdin
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
- attr_accessor :flags, :interpolated_program
56
-
57
- def parse_flags
58
- self.flags = ParseArgs.call argv, stdout
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
- def invalid_syntax?
106
- !!syntax_error_notice
107
- end
26
+ if options.print_version?
27
+ stdout.puts SeeingIsBelieving::VERSION
28
+ return SUCCESS_STATUS
29
+ end
108
30
 
109
- def print_syntax_error
110
- stderr.puts syntax_error_notice
111
- end
31
+ if options.provided_filename_dne?
32
+ stderr.puts "#{options.filename} does not exist!"
33
+ return NONDISPLAYABLE_ERROR_STATUS
34
+ end
112
35
 
113
- def evaluate_program
114
- self.interpolated_program = annotator.call
115
- rescue Timeout::Error
116
- self.timeout_error = true
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
- def program_timedout?
122
- timeout_error
123
- end
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
- def print_timeout_error
126
- stderr.puts "Timeout Error after #{@flags[:timeout]} seconds!"
127
- end
47
+ results, program_timedout, unexpected_exception =
48
+ evaluate_program(options.prepared_body, options.lib_options)
128
49
 
129
- def something_blew_up?
130
- !!unexpected_exception
131
- end
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
- else
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
- def print_program
146
- stdout.print interpolated_program
147
- end
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
- def body
150
- @body ||= (flags[:program] || (file_is_on_stdin? && stdin.read) || File.read(flags[:filename]))
151
- end
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
- def annotator
154
- @annotator ||= AddAnnotations.new body, flags.merge(stdin: (file_is_on_stdin? ? '' : stdin))
155
- end
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
- def results
158
- annotator.results
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
- def file_is_on_stdin?
162
- flags[:filename].nil? && flags[:program].nil?
163
- end
89
+ private
164
90
 
165
- def syntax_error_notice
166
- @error_notice = begin
167
- out, err, syntax_status = Open3.capture3 flags[:shebang], '-c', stdin_data: body
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
- rescue Errno::EPIPE
175
- nil
176
- end
99
+ rescue Errno::EPIPE
100
+ return nil
177
101
  end
178
102
 
179
- def print_result_as_json
180
- require 'json'
181
- stdout.puts JSON.dump result_as_data_structure
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
- results = annotator.results
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: (if results.has_exception?
190
- { line_number_in_this_file: results.each_with_line_number.find { |line_number, result| result.has_exception? }.first,
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