pry-moves 0.1.9 → 1.0.0

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/Gemfile.lock +6 -4
  4. data/README.md +28 -8
  5. data/lib/commands/debug.rb +17 -0
  6. data/lib/commands/finish.rb +26 -0
  7. data/lib/commands/goto.rb +19 -0
  8. data/lib/commands/iterate.rb +22 -0
  9. data/lib/commands/next.rb +37 -0
  10. data/lib/commands/next_breakpoint.rb +22 -0
  11. data/lib/commands/step.rb +83 -0
  12. data/lib/commands/trace_command.rb +87 -0
  13. data/lib/commands/trace_helpers.rb +49 -0
  14. data/lib/commands/traced_method.rb +71 -0
  15. data/lib/debug_sugar.rb +72 -0
  16. data/lib/pry-moves/add_suffix.rb +88 -0
  17. data/lib/pry-moves/backtrace.rb +70 -46
  18. data/lib/pry-moves/bindings_stack.rb +97 -0
  19. data/lib/pry-moves/commands.rb +50 -7
  20. data/lib/pry-moves/formatter.rb +74 -0
  21. data/lib/pry-moves/painter.rb +5 -0
  22. data/lib/pry-moves/pry_ext.rb +64 -16
  23. data/lib/pry-moves/pry_wrapper.rb +30 -17
  24. data/lib/pry-moves/restartable.rb +38 -0
  25. data/lib/pry-moves/version.rb +1 -1
  26. data/lib/pry-moves/watch.rb +3 -0
  27. data/lib/pry-moves.rb +65 -4
  28. data/lib/pry-stack_explorer/VERSION +2 -0
  29. data/lib/pry-stack_explorer/frame_manager.rb +6 -9
  30. data/lib/pry-stack_explorer/pry-stack_explorer.rb +3 -17
  31. data/lib/pry-stack_explorer/{commands.rb → stack_commands.rb} +10 -6
  32. data/lib/pry-stack_explorer/when_started_hook.rb +17 -63
  33. data/playground/Gemfile.lock +9 -9
  34. data/playground/README.md +1 -0
  35. data/playground/playground.rb +94 -9
  36. data/playground/sand.rb +46 -12
  37. data/playground/test.rb +5 -1
  38. data/playground/test.sh +1 -0
  39. data/pry-moves.gemspec +3 -2
  40. data/publish.sh +3 -0
  41. data/spec/backtrace_spec.rb +11 -13
  42. data/spec/blocks_spec.rb +41 -8
  43. data/spec/commands_spec.rb +26 -25
  44. data/spec/pry_debugger.rb +5 -1
  45. data/spec/redirection_spec.rb +7 -0
  46. data/spec/spec_helper.rb +9 -4
  47. data/spec/step_spec.rb +51 -0
  48. metadata +43 -10
  49. data/lib/pry-moves/helpers.rb +0 -50
  50. data/lib/pry-moves/trace_commands.rb +0 -105
  51. data/lib/pry-moves/tracer.rb +0 -169
@@ -0,0 +1,38 @@
1
+ module PryMoves::Restartable
2
+
3
+ attr_accessor :restart_requested, :reload_requested,
4
+ :reload_rake_tasks
5
+
6
+ def restartable
7
+ trigger :new_run
8
+ yield
9
+ re_execution
10
+ rescue PryMoves::Restart
11
+ self.restart_requested = false
12
+ PryMoves.reset
13
+ trigger :restart
14
+ retry
15
+ rescue PryMoves::Reload
16
+ puts "🔮 try to use @ with reload"
17
+ exit 3
18
+ end
19
+
20
+ def re_execution
21
+ raise PryMoves::Restart if restart_requested
22
+ raise PryMoves::Reload if reload_requested
23
+ end
24
+
25
+
26
+ end
27
+
28
+ class PryMoves::Restart < RuntimeError
29
+ end
30
+ class PryMoves::Reload < RuntimeError
31
+ end
32
+ RSpec::Support::AllExceptionsExceptOnesWeMustNotRescue::AVOID_RESCUING.concat [PryMoves::Restart, PryMoves::Reload] if defined? RSpec
33
+
34
+ Pry.config.hooks.add_hook(:after_eval, :exit_on_re_execution) do |_, _, _pry_|
35
+ if PryMoves.restart_requested or PryMoves.reload_requested
36
+ Pry.run_command 'exit-all'
37
+ end
38
+ end
@@ -1,3 +1,3 @@
1
1
  module PryMoves
2
- VERSION = '0.1.9'
2
+ VERSION = '1.0.0'
3
3
  end
@@ -1,4 +1,5 @@
1
1
  require 'singleton'
2
+ require 'set'
2
3
 
3
4
  class PryMoves::Watch
4
5
 
@@ -44,6 +45,8 @@ class PryMoves::Watch
44
45
  "\033[1m#{cmd}\033[0m: #{format binding_.eval(cmd)}"
45
46
  rescue NameError
46
47
  "\033[1m#{cmd}\033[0m: <undefined>"
48
+ rescue => e
49
+ "\033[1m#{cmd}\033[0m: <#{e}>"
47
50
  end
48
51
 
49
52
  def format(text)
data/lib/pry-moves.rb CHANGED
@@ -1,17 +1,30 @@
1
1
  require 'pry' unless defined? Pry
2
2
 
3
3
  require 'pry-moves/version'
4
- require 'pry-moves/trace_commands'
5
- require 'pry-moves/tracer'
6
4
  require 'pry-moves/pry_ext'
7
5
  require 'pry-moves/commands'
6
+ require 'pry-moves/add_suffix'
8
7
  require 'pry-moves/pry_wrapper'
8
+ require 'pry-moves/bindings_stack'
9
+ require 'pry-moves/formatter'
9
10
  require 'pry-moves/backtrace'
10
11
  require 'pry-moves/watch'
11
- require 'pry-moves/helpers'
12
12
  require 'pry-moves/painter'
13
+ require 'pry-moves/restartable'
14
+
15
+ require 'commands/traced_method'
16
+ require 'commands/trace_helpers'
17
+ require 'commands/trace_command'
18
+ require 'commands/debug'
19
+ require 'commands/finish'
20
+ require 'commands/goto'
21
+ require 'commands/iterate'
22
+ require 'commands/next'
23
+ require 'commands/next_breakpoint'
24
+ require 'commands/step'
13
25
 
14
26
  require 'pry-stack_explorer/pry-stack_explorer'
27
+ require 'debug_sugar'
15
28
 
16
29
  # Optionally load pry-remote monkey patches
17
30
  require 'pry-moves/pry_remote_ext' if defined? PryRemote
@@ -20,8 +33,30 @@ module PryMoves
20
33
  TRACE_IGNORE_FILES = Dir[File.join(File.dirname(__FILE__), '**', '*.rb')].map { |f| File.expand_path(f) }
21
34
 
22
35
  extend self
36
+ extend PryMoves::Restartable
37
+
38
+ attr_accessor :is_open, :trace,
39
+ :stop_on_breakpoints, :launched_specs_examples, :debug_called_times
40
+
41
+ def reset
42
+ self.launched_specs_examples = 0
43
+ self.stop_on_breakpoints = true
44
+ self.debug_called_times = 0
45
+ end
23
46
 
24
- attr_accessor :is_open
47
+ def debug(message = nil, at: nil)
48
+ pry_moves_stack_root = true
49
+ PryMoves.re_execution
50
+ if PryMoves.stop_on_breakpoints
51
+ if at
52
+ self.debug_called_times += 1
53
+ return unless self.debug_called_times == at
54
+ end
55
+ PryMoves.messages << message if message
56
+ binding.pry
57
+ PryMoves.re_execution
58
+ end
59
+ end
25
60
 
26
61
  # Checks that a binding is in a local file context. Extracted from
27
62
  # https://github.com/pry/pry/blob/master/lib/pry/default_commands/context.rb
@@ -34,9 +69,18 @@ module PryMoves
34
69
  @semaphore ||= Mutex.new
35
70
  end
36
71
 
72
+ def messages
73
+ @messages ||= []
74
+ end
75
+
76
+ def add_command(command, &block)
77
+ Pry.commands.block_command command, "", &block
78
+ end
79
+
37
80
  def locked?
38
81
  semaphore.locked?
39
82
  end
83
+ alias tracing? locked?
40
84
 
41
85
  def lock
42
86
  semaphore.lock unless semaphore.locked?
@@ -57,6 +101,23 @@ module PryMoves
57
101
  true
58
102
  end
59
103
 
104
+ def trigger(event)
105
+ triggers[event].each &:call
106
+ end
107
+
108
+ def triggers
109
+ @triggers ||= Hash.new do |hash, key|
110
+ hash[key] = []
111
+ end
112
+ end
113
+
114
+ def on(trigger, &block)
115
+ triggers[trigger] << block
116
+ end
117
+
60
118
  # Reference to currently running pry-remote server. Used by the tracer.
61
119
  attr_accessor :current_remote_server
62
120
  end
121
+
122
+ PryMoves.reset
123
+ PryMoves.trace = true if ENV['TRACE_MOVES']
@@ -0,0 +1,2 @@
1
+ Forked and modified copy of pry-stack_explorer v0.4.9.
2
+ Latest upstream merged: v0.4.9.3
@@ -33,13 +33,6 @@ module PryStackExplorer
33
33
  @prior_backtrace = _pry_.backtrace
34
34
  end
35
35
 
36
- def filter_bindings(vapid_frames: false)
37
- bindings.reject do |binding|
38
- !vapid_frames and
39
- binding.local_variable_defined?(:vapid_frame)
40
- end
41
- end
42
-
43
36
  # Iterate over all frames
44
37
  def each(&block)
45
38
  bindings.each(&block)
@@ -61,14 +54,18 @@ module PryStackExplorer
61
54
  # @param [Fixnum] index The index.
62
55
  def set_binding_index_safely(index)
63
56
  if index > bindings.size - 1
64
- raise Pry::CommandError, "At top of stack, cannot go further"
57
+ raise Pry::CommandError, "Shouldn't happen: At top of stack, cannot go further"
65
58
  elsif index < 0
66
- raise Pry::CommandError, "At bottom of stack, cannot go further"
59
+ raise Pry::CommandError, "Shouldn't happen: At bottom of stack, cannot go further"
67
60
  else
68
61
  self.binding_index = index
69
62
  end
70
63
  end
71
64
 
65
+ def goto_index index
66
+ change_frame_to bindings.index {|b| b.index == index }
67
+ end
68
+
72
69
  # Change active frame to the one indexed by `index`.
73
70
  # Note that indexing base is `0`
74
71
  # @param [Fixnum] index The index of the frame.
@@ -1,7 +1,7 @@
1
1
  # pry-stack_explorer.rb
2
2
  # (C) John Mair (banisterfiend); MIT license
3
3
 
4
- require "pry-stack_explorer/commands"
4
+ require "pry-stack_explorer/stack_commands"
5
5
  require "pry-stack_explorer/frame_manager"
6
6
  require "pry-stack_explorer/when_started_hook"
7
7
  require "binding_of_caller"
@@ -108,7 +108,7 @@ module PryStackExplorer
108
108
  (b1.eval('self').equal?(b2.eval('self'))) &&
109
109
  (b1.eval('__method__') == b2.eval('__method__')) &&
110
110
  (b1.eval('local_variables').map { |v| b1.eval("#{v}") }.equal?(
111
- b2.eval('local_variables').map { |v| b2.eval("#{v}") }))
111
+ b2.eval('local_variables').map { |v| b2.eval("#{v}") }))
112
112
  end
113
113
  end
114
114
  end
@@ -117,23 +117,9 @@ Pry.config.hooks.add_hook(:after_session, :delete_frame_manager) do |_, _, _pry_
117
117
  PryStackExplorer.clear_frame_managers(_pry_)
118
118
  end
119
119
 
120
+ # Can be moved to start_with_pry_nav to isolate from other use cases of Pry
120
121
  Pry.config.hooks.add_hook(:when_started, :save_caller_bindings, PryStackExplorer::WhenStartedHook.new)
121
122
 
122
123
  # Import the StackExplorer commands
123
124
  Pry.config.commands.import PryStackExplorer::Commands
124
125
 
125
- # monkey-patch the whereami command to show some frame information,
126
- # useful for navigating stack.
127
- Pry.config.commands.before_command("whereami") do |num|
128
- if PryStackExplorer.frame_manager(_pry_) && !internal_binding?(target)
129
- bindings = PryStackExplorer.frame_manager(_pry_).bindings
130
- binding_index = PryStackExplorer.frame_manager(_pry_).binding_index
131
-
132
- info = "#{Pry::Helpers::Text.bold('Frame:')} "+
133
- "#{binding_index}/#{bindings.size - 1} "+
134
- "#{bindings[binding_index].frame_type}"
135
-
136
- output.puts "\n"
137
- output.puts info
138
- end
139
- end
@@ -49,7 +49,7 @@ module PryStackExplorer
49
49
  b_self = b.eval('self')
50
50
  type = b.frame_type ? "[#{b.frame_type}]".ljust(9) : ""
51
51
  desc = b.frame_description ? "#{b.frame_description}" : "#{frame_description(b)}"
52
- sig = PryMoves::Helpers.method_signature_with_owner b
52
+ sig = PryMoves::Formatter.new.method_signature b
53
53
 
54
54
  self_clipped = "#{Pry.view_clip(b_self)}"
55
55
  path = "@ #{b.eval('__FILE__')}:#{b.eval('__LINE__')}"
@@ -87,14 +87,18 @@ module PryStackExplorer
87
87
  frame_manager.bindings.index(new_frame)
88
88
  end
89
89
 
90
- def find_frame_by_direction(up_or_down, step_into_vapid: false)
91
- frame_index = find_frame_by_block(up_or_down) do |b|
92
- step_into_vapid or
93
- not b.local_variable_defined?(:vapid_frame)
90
+ def find_frame_by_direction(dir, step_into_vapid: false)
91
+ frame_index = find_frame_by_block(dir) do |b|
92
+ step_into_vapid or
93
+ not frame_manager.bindings.vapid?(b)
94
94
  end
95
95
 
96
+ if !frame_index and !step_into_vapid
97
+ frame_index = find_frame_by_block(dir) {true}
98
+ end
99
+
96
100
  frame_index ||
97
- raise(Pry::CommandError, "At #{up_or_down == :up ? 'top' : 'bottom'} of stack, cannot go further")
101
+ raise(Pry::CommandError, "At #{dir == :up ? 'top' : 'bottom'} of stack, cannot go further")
98
102
  end
99
103
 
100
104
  def move(direction, param)
@@ -2,84 +2,38 @@ module PryStackExplorer
2
2
  class WhenStartedHook
3
3
  include Pry::Helpers::BaseHelpers
4
4
 
5
- def caller_bindings(target)
6
- bindings = binding.callers
7
- pre_callers = Thread.current[:pre_callers]
8
- bindings = bindings + pre_callers if pre_callers
9
- bindings = remove_internal_frames(bindings)
10
- mark_vapid_frames(bindings)
11
- bindings
12
- end
13
-
14
5
  def call(target, options, _pry_)
15
- target ||= _pry_.binding_stack.first if _pry_
6
+ start_from_console = target.eval('__callee__').nil? &&
7
+ target.eval('__FILE__') == '<main>' &&
8
+ target.eval('__LINE__') == 0
9
+ return if start_from_console
10
+
16
11
  options = {
17
- :call_stack => true,
18
- :initial_frame => 0
12
+ call_stack: true
19
13
  }.merge!(options)
20
14
 
21
- return if !options[:call_stack]
15
+ return unless options[:call_stack]
16
+ initial_frame = options[:initial_frame]
22
17
 
23
18
  if options[:call_stack].is_a?(Array)
24
19
  bindings = options[:call_stack]
25
-
26
- if !valid_call_stack?(bindings)
20
+ initial_frame ||= 0
21
+ unless valid_call_stack?(bindings)
27
22
  raise ArgumentError, ":call_stack must be an array of bindings"
28
23
  end
29
24
  else
30
- bindings = caller_bindings(target)
31
- end
32
-
33
- PryStackExplorer.create_and_push_frame_manager bindings, _pry_, :initial_frame => options[:initial_frame]
34
- end
35
-
36
- private
37
-
38
- def mark_vapid_frames(bindings)
39
- stepped_out = false
40
- actual_file, actual_method = nil, nil
41
-
42
- bindings.each do |binding|
43
- if stepped_out
44
- if actual_file == binding.eval("__FILE__") and actual_method == binding.eval("__method__")
45
- stepped_out = false
46
- else
47
- binding.local_variable_set :vapid_frame, true
48
- end
49
- elsif binding.frame_type == :block
50
- stepped_out = true
51
- actual_file = binding.eval("__FILE__")
52
- actual_method = binding.eval("__method__")
53
- end
54
-
55
- if binding.local_variable_defined? :hide_from_stack
56
- binding.local_variable_set :vapid_frame, true
25
+ bindings = PryMoves::BindingsStack.new
26
+ initial_frame ||= bindings.suggest_initial_frame_index
27
+ # if Thread.current[:pry_moves_debug] and initial_frame > 0
28
+ if initial_frame > 0
29
+ PryMoves.messages << "👽 Frames hidden: #{initial_frame}"
57
30
  end
58
31
  end
59
- end
60
-
61
- # remove internal frames related to setting up the session
62
- def remove_internal_frames(bindings)
63
- i = top_internal_frame_index(bindings)
64
- # DEBUG:
65
- #bindings.each_with_index do |b, index|
66
- # puts "#{index}: #{b.eval("self.class")} #{b.eval("__method__")}"
67
- #end
68
- #puts "FOUND top internal frame: #{bindings.size} => #{i}"
69
32
 
70
- bindings.drop i+1
33
+ PryStackExplorer.create_and_push_frame_manager bindings, _pry_, initial_frame: initial_frame
71
34
  end
72
35
 
73
- def top_internal_frame_index(bindings)
74
- bindings.rindex do |b|
75
- if b.frame_type == :method
76
- self_, method = b.eval("self"), b.eval("__method__")
77
- self_.equal?(Pry) && method == :start ||
78
- self_.class == Binding && method == :pry ||
79
- self_.class == PryMoves::Tracer && method == :tracing_func
80
- end
81
- end
82
- end
36
+ private
83
37
 
84
38
  def valid_call_stack?(bindings)
85
39
  bindings.any? && bindings.all? { |v| v.is_a?(Binding) }
@@ -1,23 +1,23 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- pry-moves (0.1.8)
4
+ pry-moves (1.0.0)
5
5
  binding_of_caller (~> 0.7)
6
- pry (>= 0.9.10, < 0.11.0)
6
+ colorize (~> 0.8)
7
+ pry (>= 0.10.4, < 0.13)
7
8
 
8
9
  GEM
9
10
  remote: https://rubygems.org/
10
11
  specs:
11
12
  binding_of_caller (0.8.0)
12
13
  debug_inspector (>= 0.0.1)
13
- coderay (1.1.1)
14
+ coderay (1.1.3)
15
+ colorize (0.8.1)
14
16
  debug_inspector (0.0.3)
15
- method_source (0.8.2)
16
- pry (0.10.4)
17
+ method_source (0.9.2)
18
+ pry (0.12.2)
17
19
  coderay (~> 1.1.0)
18
- method_source (~> 0.8.1)
19
- slop (~> 3.4)
20
- slop (3.6.0)
20
+ method_source (~> 0.9.0)
21
21
 
22
22
  PLATFORMS
23
23
  ruby
@@ -26,4 +26,4 @@ DEPENDENCIES
26
26
  pry-moves!
27
27
 
28
28
  BUNDLED WITH
29
- 1.16.6
29
+ 1.17.3
data/playground/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  ```
4
4
  be ruby sand.rb
5
+ be ruby test.rb
5
6
  ```
6
7
 
7
8
  ## Conditions to be met
@@ -16,6 +16,23 @@ class Playground
16
16
  binding.pry # step_into stop
17
17
  something_inside # point to step inside
18
18
  end
19
+
20
+ def skip_hidden_impl
21
+ binding.pry # skip_hidden_impl stop
22
+ hidden_self.something_inside # point to step inside
23
+ end
24
+
25
+ def hidden_self
26
+ hide_from_stack = true # hidden_self 1
27
+ self # hidden_self 2
28
+ end
29
+
30
+ def hidden_stop
31
+ hide_from_stack = true
32
+ binding.pry # hidden stop
33
+ dummy = :ok_next # hidden_stop for next
34
+ dummy = :ok_step # hidden_stop for step
35
+ end
19
36
 
20
37
  def continue
21
38
  binding.pry # first stop
@@ -39,6 +56,16 @@ class Playground
39
56
  step_by_name
40
57
  :after_step_by_name # after_step_by_name
41
58
  end
59
+
60
+ def early_return_wrap
61
+ early_return
62
+ :after_return # after early return
63
+ end
64
+
65
+ def early_return
66
+ return true if level_c # at early return
67
+ dummy = 1
68
+ end
42
69
 
43
70
  def level_a
44
71
  level_b # inside of level_a
@@ -52,7 +79,7 @@ class Playground
52
79
  def level_c(param = nil)
53
80
  binding.pry # stop in level_c
54
81
  self
55
- end
82
+ end # exit from level_c
56
83
 
57
84
  def nested_block(early_return: false)
58
85
  binding.pry # stop in nested_block
@@ -62,7 +89,7 @@ class Playground
62
89
  end
63
90
  :after_block # after block
64
91
  end
65
-
92
+
66
93
  def native_block(early_return: false)
67
94
  binding.pry # stop in native_block
68
95
  2.times do |i| # iterator line
@@ -72,25 +99,83 @@ class Playground
72
99
  :after_block # after block
73
100
  end
74
101
 
102
+ def one_line_in_block
103
+ binding.pry # stop in one_line_in_block
104
+ iterator do |i| # iterator line
105
+ dummy = 1 # inside block
106
+ end
107
+ :after_block # after block
108
+ end
109
+
110
+ def one_line_block
111
+ binding.pry # stop in one_line_block
112
+ iterator { |i| dummy = 1 } # iterator line
113
+ :after_block # after block
114
+ end
115
+
116
+ def parentheses_in_loop
117
+ binding.pry # stop in parentheses_in_loop
118
+ i = 2
119
+ while (i = i - 1) > 0 # iterator line
120
+ dummy = 1 # inside block
121
+ end
122
+ :after_block # after block
123
+ end
124
+
75
125
  def zaloop(pass = :root)
76
126
  binding.pry if pass == :root # stop in zaloop
77
- iterator do |i|
127
+ iterator do |i| # iterator line
78
128
  dummy = 1 # inside block
79
129
  zaloop i if pass == :root
130
+ return unless pass == :root # after sub-zaloop
80
131
  end
81
132
  :after_block # after block
82
- end
133
+ end # exit from zaloop
83
134
 
84
135
  def method_with_redirection
85
- debug_redirect = '=level_a' # at method_with_redirection
136
+ debug_redirect = :level_a # at method_with_redirection
86
137
  level_a
87
138
  end
88
139
 
140
+ def instant_redirection
141
+ debug_redirect = '=something_inside'
142
+ binding.pry # at instant_redirection
143
+ something_inside
144
+ end
145
+
89
146
  def redirection_host
90
147
  binding.pry # redirection host
91
148
  method_with_redirection
92
149
  end
93
150
 
151
+ def something_inside
152
+ :something # some internal line
153
+ end
154
+
155
+ def method_with_breakpoints
156
+ binding.pry # method_with_breakpoints host
157
+ dummy = 1 # some internal line
158
+ debug_ # breakpoint
159
+ dummy = 1 # after breakpoint
160
+ dummy = 1 # after after breakpoint
161
+ debug_ # breakpoint 2
162
+ dummy = 1 # after breakpoint 2
163
+ end
164
+
165
+ def skip_test
166
+ binding.pry # stop in skip_test
167
+ skipped_method.not_skipped_method # next step
168
+ end
169
+
170
+ def skipped_method
171
+ pry_moves_skip = true # at skipped_method
172
+ self # at skipped_method
173
+ end
174
+
175
+ def not_skipped_method
176
+ :not_skipped_method # at not_skipped_method
177
+ end
178
+
94
179
  private
95
180
 
96
181
  def iterator
@@ -99,10 +184,10 @@ class Playground
99
184
  yield i
100
185
  :post_yield # post_yield
101
186
  end
102
- end
103
-
104
- def something_inside
105
- :something # some internal line
187
+ end # exit from iterator
188
+
189
+ def debug_
190
+ :something # inside of debug method
106
191
  end
107
192
 
108
193
  end