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.
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