seeing_is_believing 3.6.1 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -5
  3. data/Rakefile +2 -0
  4. data/Readme.md +1 -1
  5. data/appveyor.yml +3 -5
  6. data/features/regression.feature +7 -0
  7. data/features/xmpfilter-style.feature +1 -0
  8. data/lib/seeing_is_believing.rb +2 -1
  9. data/lib/seeing_is_believing/binary.rb +1 -1
  10. data/lib/seeing_is_believing/binary/annotate_marked_lines.rb +1 -0
  11. data/lib/seeing_is_believing/binary/comment_lines.rb +1 -1
  12. data/lib/seeing_is_believing/binary/commentable_lines.rb +7 -7
  13. data/lib/seeing_is_believing/binary/config.rb +1 -3
  14. data/lib/seeing_is_believing/binary/remove_annotations.rb +1 -1
  15. data/lib/seeing_is_believing/code.rb +3 -3
  16. data/lib/seeing_is_believing/evaluate_by_moving_files.rb +30 -32
  17. data/lib/seeing_is_believing/event_stream/consumer.rb +2 -0
  18. data/lib/seeing_is_believing/event_stream/events.rb +7 -0
  19. data/lib/seeing_is_believing/event_stream/handlers/record_exit_events.rb +3 -3
  20. data/lib/seeing_is_believing/event_stream/handlers/update_result.rb +2 -1
  21. data/lib/seeing_is_believing/event_stream/producer.rb +14 -0
  22. data/lib/seeing_is_believing/safe.rb +1 -7
  23. data/lib/seeing_is_believing/swap_files.rb +90 -0
  24. data/lib/seeing_is_believing/the_matrix.rb +17 -4
  25. data/lib/seeing_is_believing/version.rb +1 -1
  26. data/lib/seeing_is_believing/wrap_expressions.rb +22 -8
  27. data/lib/seeing_is_believing/wrap_expressions_with_inspect.rb +3 -0
  28. data/seeing_is_believing.gemspec +4 -5
  29. data/spec/binary/config_spec.rb +2 -2
  30. data/spec/evaluate_by_moving_files_spec.rb +18 -17
  31. data/spec/event_stream_spec.rb +1 -0
  32. data/spec/seeing_is_believing_spec.rb +99 -24
  33. data/spec/sib_spec_helpers/version.rb +17 -0
  34. data/spec/spec_helper.rb +2 -18
  35. data/spec/wrap_expressions_spec.rb +9 -3
  36. metadata +26 -12
  37. data/lib/seeing_is_believing/backup_file.rb +0 -50
  38. data/lib/seeing_is_believing/customize_pp.rb +0 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: abaf2ec44d75ad4ca500c71bf90128baea06be5c0bd61fef5a0de81a402fef49
4
- data.tar.gz: b487790b90fb61b27e77fee26622c8210a0b6e435c2084205bb791bcf3e28c08
3
+ metadata.gz: d4cc5ca0f30eacf4bb2729e295c557de2dcc20acb856389dda16f776bb0df2ad
4
+ data.tar.gz: e5711ae5d11fff1d65259beb1f65f9bbf6fba5e2d1494e22394d652fac26dfa6
5
5
  SHA512:
6
- metadata.gz: '03996ee758737b276089eec6037a0c999678097759fc1dac9a1a338eae62327cb5d76253e0c491ae77a7e0852edb104da629e59db1c60228c43076d100ae8215'
7
- data.tar.gz: 915f7e561d0bcb18f863c068e72305fa445f1b4f5f9d390843478a4ddb662cf8c38af388d5cc8da5f9ddaeeba1e569ad803c5d5c5b913d92bc6642e60155484e
6
+ metadata.gz: c00e05b913ad9122b4f2a76146c447868cec4b53dad4f8cc71ee6ef8e0607005c9b6cf8c31bd5e439096ea52ee445f71ff06be44872b65b4e6ce00a3931cfe64
7
+ data.tar.gz: 155ee68e5acaaf7b9d6e813bb3f1710b759c9b093a467c8aeb9d84f4df298716ab1cfba2cab504b75c9b98aad8f6d0d6146ae13be35db1f26dbacfa70e035e14
@@ -1,11 +1,8 @@
1
1
  language: ruby
2
- before_install: gem install bundler
3
2
  install: rake install
4
3
  script: bundle exec rake ci
5
4
  rvm:
6
- - 2.0.0
7
- - 2.1.2
8
- - 2.2.0
9
- - 2.3.1
10
5
  - 2.4.1
11
6
  - 2.5.0
7
+ - 2.6.3
8
+ - 2.7.1
data/Rakefile CHANGED
@@ -42,6 +42,8 @@ task cuke: :bundle do
42
42
  sh 'ruby', '--disable-gem',
43
43
  *require_paths,
44
44
  '-S', 'bundle/bin/cucumber',
45
+ '--quiet', # omit stepdefs, which are usually spammy for me https://twitter.com/josh_cheek/status/1082767053071765504
46
+ '--fail-fast', # stop as soon as we see an error (don't waste CI time, don't make it hard to find error info later)
45
47
  '--tags', '~@not-implemented',
46
48
  '--tags', "~@not-#{RUBY_VERSION}",
47
49
  '--tags', "~@not-#{ruby_version_without_patchlevel}",
data/Readme.md CHANGED
@@ -8,7 +8,7 @@ Evaluates Ruby code, recording the results of each line.
8
8
  Integrates with any extensible editor (I've integrated it with many already, see [the list](https://github.com/JoshCheek/seeing_is_believing/wiki/Editor-Integration)).
9
9
  If you like Swift Playgrounds, you'll like SiB.
10
10
 
11
- ![example](https://s3.amazonaws.com/josh.cheek/images/scratch/sib-example1.gif)
11
+ ![example](docs/example.gif)
12
12
 
13
13
 
14
14
  Helpful links
@@ -26,8 +26,6 @@ test_script:
26
26
 
27
27
  environment:
28
28
  matrix:
29
- - ruby_version: '21'
30
- - ruby_version: '22'
31
- - ruby_version: '23'
32
- # - ruby_version: '24'
33
- # - ruby_version: '25'
29
+ - ruby_version: '24'
30
+ - ruby_version: '25'
31
+ - ruby_version: '26'
@@ -267,6 +267,8 @@ Feature:
267
267
  """
268
268
 
269
269
 
270
+ # ChildProcess doesn't seem to handle this well in Windows. IDK.
271
+ @not-windows
270
272
  Scenario: Long DATA segment in a valid file
271
273
  Given the file "long_valid_data_segment.rb":
272
274
  """
@@ -434,6 +436,9 @@ Feature:
434
436
  """
435
437
 
436
438
 
439
+ # 2.4 and 2.5 don't correclty detect deadlock on Windows: https://bugs.ruby-lang.org/issues/16110
440
+ @not-2.4
441
+ @not-2.5
437
442
  Scenario: Deadlocked
438
443
  Given the file "deadlocked.rb":
439
444
  """
@@ -472,6 +477,8 @@ Feature:
472
477
  # but just generally that it doesn't fkn blow up
473
478
  @not-2.4
474
479
  @not-2.5
480
+ @not-2.6
481
+ @not-2.7
475
482
  Scenario: Old JSON bug
476
483
  Given the file "json_and_encodings.rb":
477
484
  """
@@ -72,6 +72,7 @@ Feature: Xmpfilter style
72
72
  # :wibble=>{:magic_word=>"xyzzy"}}
73
73
  """
74
74
 
75
+ @not-2.3
75
76
  Scenario: --xmpfilter-style, when displayed on the next line, prints the string across multiple lines
76
77
  Given the file "xmpfilter-prev-line-is-multiline-string.rb":
77
78
  """
@@ -43,8 +43,9 @@ class SeeingIsBelieving
43
43
  options.debugger.context("REWRITTEN PROGRAM") { new_program }
44
44
 
45
45
  EvaluateByMovingFiles.call \
46
- new_program,
47
46
  filename,
47
+ @program,
48
+ new_program,
48
49
  event_handler: event_handler(options.debugger, options.event_handler),
49
50
  provided_input: options.stdin,
50
51
  require_files: options.require_files,
@@ -23,7 +23,7 @@ class SeeingIsBelieving
23
23
  end
24
24
 
25
25
  if config.errors.any?
26
- stderr.puts *config.errors, *config.deprecations
26
+ stderr.puts(*config.errors, *config.deprecations)
27
27
  return NONDISPLAYABLE_ERROR_STATUS
28
28
  end
29
29
 
@@ -46,6 +46,7 @@ class SeeingIsBelieving
46
46
  should_pp = false
47
47
  WrapExpressions.call \
48
48
  program,
49
+ before_all: -> { "BEGIN { $SiB.file_loaded };" },
49
50
  before_each: -> line_number {
50
51
  inspect = "$SiB.record_result(:inspect, #{line_number}, ("
51
52
  pp = "$SiB.record_result(:pp, #{line_number}, ("
@@ -17,7 +17,7 @@ class SeeingIsBelieving
17
17
  def call
18
18
  @call ||= begin
19
19
  commentable_lines = CommentableLines.new raw_code
20
- commentable_lines.call.each do |line_number, (index_of_newline, col)|
20
+ commentable_lines.call.each do |line_number, (index_of_newline, _col)|
21
21
  first_index = last_index = index_of_newline
22
22
  first_index -= 1 while first_index > 0 && raw_code[first_index-1] != "\n"
23
23
  comment_text = commenter.call raw_code[first_index...last_index], line_number
@@ -54,13 +54,13 @@ class SeeingIsBelieving
54
54
  def remove_lines_whose_newline_is_escaped(line_num_to_location)
55
55
  ors_indexes = code_obj.indexes_of_ors_at_eol
56
56
  line_num_to_location
57
- .select { |line_number, (index_of_newline, col)|
57
+ .select { |line_number, (index_of_newline, _col)|
58
58
  code[index_of_newline-1] == '\\'
59
59
  }
60
- .reject { |line_number, (index_of_newline, col)|
60
+ .reject { |line_number, (index_of_newline, _col)|
61
61
  ors_indexes.include? index_of_newline
62
62
  }
63
- .each { |line_number, (index_of_newline, col)|
63
+ .each { |line_number, (_index_of_newline, _col)|
64
64
  line_num_to_location.delete line_number
65
65
  }
66
66
  end
@@ -73,8 +73,8 @@ class SeeingIsBelieving
73
73
  begin_pos = comment.location.expression.begin_pos
74
74
  end_pos = comment.location.expression.end_pos
75
75
  range = begin_pos...end_pos
76
- line_num_to_location.select { |line_number, (index_of_newline, col)| range.include? index_of_newline }
77
- .each { |line_number, (index_of_newline, col)| line_num_to_location.delete line_number }
76
+ line_num_to_location.select { |line_number, (index_of_newline, _col)| range.include? index_of_newline }
77
+ .each { |line_number, (_index_of_newline, _col)| line_num_to_location.delete line_number }
78
78
  end
79
79
  end
80
80
  end
@@ -82,8 +82,8 @@ class SeeingIsBelieving
82
82
  def remove_lines_inside_of_strings_and_things(line_num_to_location, ast)
83
83
  invalid_boundaries = ranges_of_atomic_expressions ast, []
84
84
  invalid_boundaries.each do |invalid_boundary|
85
- line_num_to_location.select { |line_number, (index_of_newline, col)| invalid_boundary.include? index_of_newline }
86
- .each { |line_number, (index_of_newline, col)| line_num_to_location.delete line_number }
85
+ line_num_to_location.select { |line_number, (index_of_newline, _col)| invalid_boundary.include? index_of_newline }
86
+ .each { |line_number, (_index_of_newline, _col)| line_num_to_location.delete line_number }
87
87
  end
88
88
  end
89
89
 
@@ -108,7 +108,6 @@ class SeeingIsBelieving
108
108
  self.lib_options.rewrite_code = AnnotateMarkedLines.code_rewriter(markers)
109
109
  self.remove_value_prefixes = false
110
110
  self.lib_options.require_files << 'pp'
111
- self.lib_options.require_files << 'seeing_is_believing/customize_pp'
112
111
 
113
112
  when '--toggle-mark'
114
113
  extract_positive_int_for.call arg do |n|
@@ -313,8 +312,7 @@ class SeeingIsBelieving
313
312
  end
314
313
 
315
314
  def Binary.help_screen(markers)
316
- value = markers[:value][:prefix]
317
- stdout = markers[:stdout][:prefix]
315
+ value = markers[:value][:prefix]
318
316
 
319
317
  <<FLAGS
320
318
  Usage: seeing_is_believing [options] [filename]
@@ -76,7 +76,7 @@ class SeeingIsBelieving
76
76
  [annotation, comment]
77
77
  }
78
78
  .slice_before { |annotation, comment| annotation } # annotations begin chunks
79
- .select { |(annotation, start), *| annotation } # discard chunks not beginning with an annotation (probably can only happens on first comment)
79
+ .select { |(annotation, _start), *| annotation } # discard chunks not beginning with an annotation (probably can only happens on first comment)
80
80
  .map { |(annotation, start), *rest| # end the chunk if the comment doesn't meet nextline criteria
81
81
  nextline_comments = []
82
82
  prev = start
@@ -91,8 +91,8 @@ class SeeingIsBelieving
91
91
  def indexes_of_ors_at_eol
92
92
  Set.new(
93
93
  @tokens.select { |type, *| type == :tGVAR }
94
- .select { |_, (var, range)| var == '$\\'.freeze }
95
- .map { |_, (var, range)| range.end_pos }
94
+ .select { |_, (var, _range)| var == '$\\'.freeze }
95
+ .map { |_, (_var, range)| range.end_pos }
96
96
  )
97
97
  end
98
98
 
@@ -106,7 +106,7 @@ class SeeingIsBelieving
106
106
 
107
107
  def body_range_from_tokens(tokens)
108
108
  return range_for(0, 0) if raw.start_with? "__END__\n"
109
- (name, (_, range)) = tokens.max_by { |name, (data, range)| range.end_pos } ||
109
+ (name, (_, range)) = tokens.max_by { |name, (_data, range)| range.end_pos } ||
110
110
  [nil, [nil, range_for(0, 1)]]
111
111
  end_pos = range.end_pos
112
112
  end_pos += 1 if name == :tCOMMENT || name == :tSTRING_END || name == :tSEMI
@@ -17,8 +17,9 @@ require "childprocess"
17
17
 
18
18
  require 'seeing_is_believing/result'
19
19
  require 'seeing_is_believing/debugger'
20
- require 'seeing_is_believing/backup_file'
20
+ require 'seeing_is_believing/swap_files'
21
21
  require 'seeing_is_believing/event_stream/consumer'
22
+ require 'seeing_is_believing/event_stream/events'
22
23
 
23
24
  ChildProcess.posix_spawn = true # forking locks up for some reason when we run SiB inside of SiB
24
25
 
@@ -28,13 +29,14 @@ class SeeingIsBelieving
28
29
  new(*args).call
29
30
  end
30
31
 
31
- attr_accessor :program, :provided_input, :require_flags, :load_path_flags, :encoding, :timeout_seconds, :debugger, :event_handler, :max_line_captures
32
+ attr_accessor :user_program, :rewritten_program, :provided_input, :require_flags, :load_path_flags, :encoding, :timeout_seconds, :debugger, :event_handler, :max_line_captures
32
33
 
33
- attr_accessor :file_directory, :file_path, :local_cwd, :relative_filename, :backup_filename
34
+ attr_accessor :file_directory, :file_path, :local_cwd, :relative_filename, :backup_path
34
35
 
35
- def initialize(program, file_path, options={})
36
+ def initialize(file_path, user_program, rewritten_program, options={})
36
37
  options = options.dup
37
- self.program = program
38
+ self.user_program = user_program
39
+ self.rewritten_program = rewritten_program
38
40
  self.encoding = options.delete(:encoding) || "u"
39
41
  self.timeout_seconds = options.delete(:timeout_seconds) || 0 # 0 is the new infinity
40
42
  self.provided_input = options.delete(:provided_input) || String.new
@@ -47,26 +49,20 @@ class SeeingIsBelieving
47
49
  self.file_directory = File.dirname file_path
48
50
  file_name = File.basename file_path
49
51
  self.relative_filename = local_cwd ? file_name : file_path
50
- self.backup_filename = File.join file_directory, "seeing_is_believing_backup.#{file_name}"
52
+ self.backup_path = File.join file_directory, "seeing_is_believing_backup.#{file_name}"
51
53
 
52
54
  options.any? && raise(ArgumentError, "Unknown options: #{options.inspect}")
53
55
  end
54
56
 
55
57
  def call
56
- BackupFile.call file_path, backup_filename do
57
- write_program_to_file
58
- evaluate_file
58
+ SwapFiles.call file_path, backup_path, user_program, rewritten_program do |swap_files|
59
+ evaluate_file swap_files
59
60
  end
60
61
  end
61
62
 
62
63
  private
63
64
 
64
- def write_program_to_file
65
- File.open(file_path, 'w', external_encoding: "utf-8") { |f| f.write program.to_s }
66
- end
67
-
68
-
69
- def evaluate_file
65
+ def evaluate_file(swap_files)
70
66
  event_server = TCPServer.new(0) # dynamically allocates an available port
71
67
 
72
68
  # setup streams
@@ -78,7 +74,7 @@ class SeeingIsBelieving
78
74
  [Marshal.dump(
79
75
  event_stream_port: event_server.addr[1],
80
76
  max_line_captures: max_line_captures,
81
- num_lines: program.lines.count,
77
+ num_lines: user_program.lines.count,
82
78
  filename: relative_filename,
83
79
  )].pack('m0')
84
80
 
@@ -113,7 +109,12 @@ class SeeingIsBelieving
113
109
 
114
110
  # set up the event consumer
115
111
  consumer = EventStream::Consumer.new(events: eventstream, stdout: stdout, stderr: stderr)
116
- consumer_thread = Thread.new { consumer.each { |e| event_handler.call e } }
112
+ consumer_thread = Thread.new do
113
+ consumer.each do |e|
114
+ swap_files.show_user_program if e.is_a? SeeingIsBelieving::EventStream::Events::FileLoaded
115
+ event_handler.call e
116
+ end
117
+ end
117
118
 
118
119
  # wait for completion
119
120
  if timeout_seconds == 0
@@ -131,13 +132,19 @@ class SeeingIsBelieving
131
132
  # On Windows, we need to call stop if there is an error since it interrupted
132
133
  # the previos waiting/polling. If we don't call stop, in that situation, it will
133
134
  # leave orphan processes. On Unix, we need to always call stop or it may leave orphans
134
- if ChildProcess.unix?
135
- child.stop
136
- elsif $!
137
- child.stop
138
- consumer.process_exitstatus(child.exit_code)
135
+ begin
136
+ if ChildProcess.unix?
137
+ child.stop
138
+ elsif $!
139
+ child.stop
140
+ consumer.process_exitstatus(child.exit_code)
141
+ end
142
+ child.alive? && child.stop
143
+ rescue ChildProcess::Error
144
+ # On AppVeyor, I keep getting errors
145
+ # The handle is invalid: https://ci.appveyor.com/project/JoshCheek/seeing-is-believing/build/22
146
+ # Access is denied: https://ci.appveyor.com/project/JoshCheek/seeing-is-believing/build/24
139
147
  end
140
- cleanup_run(child)
141
148
  close_streams(stdout, stderr, eventstream, event_server)
142
149
  end
143
150
 
@@ -154,14 +161,5 @@ class SeeingIsBelieving
154
161
  def close_streams(*streams)
155
162
  streams.each { |io| io.close unless io.closed? }
156
163
  end
157
-
158
- # On AppVeyor, I keep getting errors
159
- # The handle is invalid: https://ci.appveyor.com/project/JoshCheek/seeing-is-believing/build/22
160
- # Access is denied: https://ci.appveyor.com/project/JoshCheek/seeing-is-believing/build/24
161
- def cleanup_run(child, *streams)
162
- child.alive? && child.stop
163
- rescue ChildProcess::Error
164
- # noop
165
- end
166
164
  end
167
165
  end
@@ -208,6 +208,8 @@ class SeeingIsBelieving
208
208
  Events::RubyVersion.new(value: shift_string(line))
209
209
  when :filename
210
210
  Events::Filename.new(value: shift_string(line))
211
+ when :file_loaded
212
+ Events::FileLoaded.new
211
213
  when :exec
212
214
  Events::Exec.new(args: shift_string(line))
213
215
  else
@@ -58,6 +58,13 @@ class SeeingIsBelieving
58
58
  attributes :value
59
59
  end
60
60
 
61
+ # For knowing when you can perform certain tasks (eg moving the file back)
62
+ class FileLoaded < Event
63
+ def self.event_name
64
+ :file_loaded
65
+ end
66
+ end
67
+
61
68
  # Number of lines in the program.
62
69
  class NumLines < Event
63
70
  def self.event_name
@@ -7,8 +7,8 @@ class SeeingIsBelieving
7
7
  attr_reader :exitstatus
8
8
  attr_reader :timeout_seconds
9
9
 
10
- def initialize(next_observer)
11
- @next_observer = next_observer
10
+ def initialize(next_handler)
11
+ @next_handler = next_handler
12
12
  end
13
13
 
14
14
  def call(event)
@@ -18,7 +18,7 @@ class SeeingIsBelieving
18
18
  when Events::Timeout
19
19
  @timeout_seconds = event.seconds
20
20
  end
21
- @next_observer.call(event)
21
+ @next_handler.call(event)
22
22
  end
23
23
  end
24
24
  end
@@ -29,7 +29,8 @@ class SeeingIsBelieving
29
29
  Finished,
30
30
  StdoutClosed,
31
31
  StderrClosed,
32
- EventStreamClosed
32
+ EventStreamClosed,
33
+ FileLoaded
33
34
  # no op
34
35
  else raise "Unknown event: #{event.inspect}"
35
36
  end
@@ -7,6 +7,16 @@ using SeeingIsBelieving::Safe
7
7
  class SeeingIsBelieving
8
8
  module EventStream
9
9
  class Producer
10
+
11
+ # Guarding against hostile users (e.g. me) that do ridiculous things like blowing away these constants
12
+ old_w, $-w = $-w, nil # Ruby warns about accessing deprecated constants
13
+ Object.constants.each do |name|
14
+ const_set name, Object.const_get(name)
15
+ end
16
+ $-w = old_w
17
+
18
+ ErrnoEPIPE = Errno::EPIPE # not actually tested, but we can see it is referenced below
19
+
10
20
  module NullQueue
11
21
  extend self
12
22
  Queue.instance_methods(false).each do |name|
@@ -40,6 +50,10 @@ class SeeingIsBelieving
40
50
  queue << "max_line_captures #{max_line_captures}"
41
51
  end
42
52
 
53
+ def file_loaded
54
+ queue << "file_loaded"
55
+ end
56
+
43
57
  StackErrors = [SystemStackError]
44
58
  StackErrors << Java::JavaLang::StackOverflowError if defined?(RUBY_PLATFORM) && RUBY_PLATFORM == 'java'
45
59
  def record_result(type, line_number, value)
@@ -50,13 +50,7 @@ class SeeingIsBelieving
50
50
  alias to_str to_str
51
51
  end
52
52
 
53
- # in 2.4 we should use Integer instead, but it's not obvious to me how
54
- # to detect this. eg defined?(Fixnum) returns "constant". Accessing it
55
- # leads to a warning, but SiB turns warnings off so you don't see it.
56
- # So.... for now, it incidentally doesn't do anything annoying, but would
57
- # be good to figure out something better (eg if we ever wanted to allow the
58
- # user to decide whether warnings should display or not)
59
- refine Fixnum do
53
+ refine Integer do
60
54
  alias to_s to_s
61
55
  alias next next
62
56
  alias < <
@@ -0,0 +1,90 @@
1
+ require 'seeing_is_believing/error'
2
+ require 'seeing_is_believing/hard_core_ensure'
3
+
4
+ class SeeingIsBelieving
5
+ class SwapFiles
6
+ # Might honeslty make more sense to break this out into 2 different classes.
7
+ # We've got to do some confusing state accounting since there are really 2
8
+ # algorithms here:
9
+ #
10
+ # if the file exists:
11
+ # make sure there isn't a backup (could cause the user to lose their file)
12
+ # back the file up
13
+ # write the rewritten code to the file
14
+ # if we are told to show the user program
15
+ # move the backup over the top of the rewritten file
16
+ # do nothing in the ensure block
17
+ # else
18
+ # in the ensure block: move the backup over the top of the rewritten file
19
+ # if the file DNE:
20
+ # write the rewritten code to the file
21
+ # if we are told to show the user program
22
+ # write the user program to the file
23
+ # delete the file in the ensure block
24
+ def self.call(*args, &block)
25
+ new(*args, &block).call
26
+ end
27
+
28
+ def initialize(file_path, backup_path, user_program, rewritten_program, &block)
29
+ self.file_path = file_path
30
+ self.block = block
31
+ self.backup_path = backup_path
32
+ self.user_program = user_program
33
+ self.rewritten_program = rewritten_program
34
+ end
35
+
36
+ def call
37
+ HardCoreEnsure.call \
38
+ code: -> {
39
+ File.exist? backup_path and
40
+ raise TempFileAlreadyExists.new(file_path, backup_path)
41
+
42
+ @has_file = File.exist? file_path
43
+
44
+ if @has_file
45
+ File.rename file_path, backup_path
46
+ @needs_restore = true
47
+ end
48
+
49
+ save_file rewritten_program
50
+
51
+ block.call self
52
+ },
53
+ ensure: -> {
54
+ set_back_to_initial_conditions
55
+ }
56
+ end
57
+
58
+ def show_user_program
59
+ if @needs_restore
60
+ restore
61
+ else
62
+ save_file user_program
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ attr_accessor :block, :file_path, :backup_path, :rewritten_program, :user_program
69
+
70
+ def restore
71
+ File.rename(backup_path, file_path)
72
+ @needs_restore = false
73
+ end
74
+
75
+
76
+ def save_file(program)
77
+ File.open file_path, 'w', external_encoding: "utf-8" do |f|
78
+ f.write program.to_s
79
+ end
80
+ end
81
+
82
+ def set_back_to_initial_conditions
83
+ if @needs_restore
84
+ restore
85
+ elsif !@has_file
86
+ File.delete(file_path)
87
+ end
88
+ end
89
+ end
90
+ end