pry 0.10.0.pre2-universal-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (131) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +702 -0
  3. data/LICENSE +25 -0
  4. data/README.md +406 -0
  5. data/bin/pry +16 -0
  6. data/lib/pry.rb +161 -0
  7. data/lib/pry/cli.rb +220 -0
  8. data/lib/pry/code.rb +346 -0
  9. data/lib/pry/code/code_file.rb +103 -0
  10. data/lib/pry/code/code_range.rb +71 -0
  11. data/lib/pry/code/loc.rb +92 -0
  12. data/lib/pry/code_object.rb +172 -0
  13. data/lib/pry/color_printer.rb +55 -0
  14. data/lib/pry/command.rb +692 -0
  15. data/lib/pry/command_set.rb +443 -0
  16. data/lib/pry/commands.rb +6 -0
  17. data/lib/pry/commands/amend_line.rb +99 -0
  18. data/lib/pry/commands/bang.rb +20 -0
  19. data/lib/pry/commands/bang_pry.rb +17 -0
  20. data/lib/pry/commands/cat.rb +62 -0
  21. data/lib/pry/commands/cat/abstract_formatter.rb +27 -0
  22. data/lib/pry/commands/cat/exception_formatter.rb +77 -0
  23. data/lib/pry/commands/cat/file_formatter.rb +67 -0
  24. data/lib/pry/commands/cat/input_expression_formatter.rb +43 -0
  25. data/lib/pry/commands/cd.rb +41 -0
  26. data/lib/pry/commands/change_inspector.rb +27 -0
  27. data/lib/pry/commands/change_prompt.rb +26 -0
  28. data/lib/pry/commands/code_collector.rb +165 -0
  29. data/lib/pry/commands/disable_pry.rb +27 -0
  30. data/lib/pry/commands/disabled_commands.rb +2 -0
  31. data/lib/pry/commands/easter_eggs.rb +112 -0
  32. data/lib/pry/commands/edit.rb +195 -0
  33. data/lib/pry/commands/edit/exception_patcher.rb +25 -0
  34. data/lib/pry/commands/edit/file_and_line_locator.rb +36 -0
  35. data/lib/pry/commands/exit.rb +42 -0
  36. data/lib/pry/commands/exit_all.rb +29 -0
  37. data/lib/pry/commands/exit_program.rb +23 -0
  38. data/lib/pry/commands/find_method.rb +193 -0
  39. data/lib/pry/commands/fix_indent.rb +19 -0
  40. data/lib/pry/commands/gem_cd.rb +26 -0
  41. data/lib/pry/commands/gem_install.rb +32 -0
  42. data/lib/pry/commands/gem_list.rb +33 -0
  43. data/lib/pry/commands/gem_open.rb +29 -0
  44. data/lib/pry/commands/gist.rb +101 -0
  45. data/lib/pry/commands/help.rb +164 -0
  46. data/lib/pry/commands/hist.rb +180 -0
  47. data/lib/pry/commands/import_set.rb +22 -0
  48. data/lib/pry/commands/install_command.rb +53 -0
  49. data/lib/pry/commands/jump_to.rb +29 -0
  50. data/lib/pry/commands/list_inspectors.rb +35 -0
  51. data/lib/pry/commands/list_prompts.rb +35 -0
  52. data/lib/pry/commands/ls.rb +114 -0
  53. data/lib/pry/commands/ls/constants.rb +47 -0
  54. data/lib/pry/commands/ls/formatter.rb +49 -0
  55. data/lib/pry/commands/ls/globals.rb +48 -0
  56. data/lib/pry/commands/ls/grep.rb +21 -0
  57. data/lib/pry/commands/ls/instance_vars.rb +39 -0
  58. data/lib/pry/commands/ls/interrogatable.rb +18 -0
  59. data/lib/pry/commands/ls/jruby_hacks.rb +49 -0
  60. data/lib/pry/commands/ls/local_names.rb +35 -0
  61. data/lib/pry/commands/ls/local_vars.rb +39 -0
  62. data/lib/pry/commands/ls/ls_entity.rb +70 -0
  63. data/lib/pry/commands/ls/methods.rb +57 -0
  64. data/lib/pry/commands/ls/methods_helper.rb +46 -0
  65. data/lib/pry/commands/ls/self_methods.rb +32 -0
  66. data/lib/pry/commands/nesting.rb +25 -0
  67. data/lib/pry/commands/play.rb +103 -0
  68. data/lib/pry/commands/pry_backtrace.rb +25 -0
  69. data/lib/pry/commands/pry_version.rb +17 -0
  70. data/lib/pry/commands/raise_up.rb +32 -0
  71. data/lib/pry/commands/reload_code.rb +62 -0
  72. data/lib/pry/commands/reset.rb +18 -0
  73. data/lib/pry/commands/ri.rb +60 -0
  74. data/lib/pry/commands/save_file.rb +61 -0
  75. data/lib/pry/commands/shell_command.rb +48 -0
  76. data/lib/pry/commands/shell_mode.rb +25 -0
  77. data/lib/pry/commands/show_doc.rb +83 -0
  78. data/lib/pry/commands/show_info.rb +195 -0
  79. data/lib/pry/commands/show_input.rb +17 -0
  80. data/lib/pry/commands/show_source.rb +50 -0
  81. data/lib/pry/commands/simple_prompt.rb +22 -0
  82. data/lib/pry/commands/stat.rb +40 -0
  83. data/lib/pry/commands/switch_to.rb +23 -0
  84. data/lib/pry/commands/toggle_color.rb +24 -0
  85. data/lib/pry/commands/watch_expression.rb +105 -0
  86. data/lib/pry/commands/watch_expression/expression.rb +38 -0
  87. data/lib/pry/commands/whereami.rb +190 -0
  88. data/lib/pry/commands/wtf.rb +57 -0
  89. data/lib/pry/config.rb +24 -0
  90. data/lib/pry/config/behavior.rb +139 -0
  91. data/lib/pry/config/convenience.rb +26 -0
  92. data/lib/pry/config/default.rb +165 -0
  93. data/lib/pry/core_extensions.rb +131 -0
  94. data/lib/pry/editor.rb +133 -0
  95. data/lib/pry/exceptions.rb +77 -0
  96. data/lib/pry/helpers.rb +5 -0
  97. data/lib/pry/helpers/base_helpers.rb +113 -0
  98. data/lib/pry/helpers/command_helpers.rb +156 -0
  99. data/lib/pry/helpers/documentation_helpers.rb +75 -0
  100. data/lib/pry/helpers/options_helpers.rb +27 -0
  101. data/lib/pry/helpers/table.rb +109 -0
  102. data/lib/pry/helpers/text.rb +107 -0
  103. data/lib/pry/history.rb +125 -0
  104. data/lib/pry/history_array.rb +121 -0
  105. data/lib/pry/hooks.rb +230 -0
  106. data/lib/pry/indent.rb +406 -0
  107. data/lib/pry/input_completer.rb +242 -0
  108. data/lib/pry/input_lock.rb +132 -0
  109. data/lib/pry/inspector.rb +27 -0
  110. data/lib/pry/last_exception.rb +61 -0
  111. data/lib/pry/method.rb +546 -0
  112. data/lib/pry/method/disowned.rb +53 -0
  113. data/lib/pry/method/patcher.rb +125 -0
  114. data/lib/pry/method/weird_method_locator.rb +186 -0
  115. data/lib/pry/module_candidate.rb +136 -0
  116. data/lib/pry/object_path.rb +82 -0
  117. data/lib/pry/output.rb +50 -0
  118. data/lib/pry/pager.rb +234 -0
  119. data/lib/pry/plugins.rb +103 -0
  120. data/lib/pry/prompt.rb +26 -0
  121. data/lib/pry/pry_class.rb +375 -0
  122. data/lib/pry/pry_instance.rb +654 -0
  123. data/lib/pry/rbx_path.rb +22 -0
  124. data/lib/pry/repl.rb +202 -0
  125. data/lib/pry/repl_file_loader.rb +74 -0
  126. data/lib/pry/rubygem.rb +82 -0
  127. data/lib/pry/terminal.rb +79 -0
  128. data/lib/pry/test/helper.rb +170 -0
  129. data/lib/pry/version.rb +3 -0
  130. data/lib/pry/wrapped_module.rb +373 -0
  131. metadata +248 -0
@@ -0,0 +1,121 @@
1
+ class Pry
2
+ # A history array is an array to which you can only add elements. Older
3
+ # entries are removed progressively, so that the array never contains more than
4
+ # N elements.
5
+ #
6
+ # History arrays are used by Pry to store the output of the last commands.
7
+ #
8
+ # @example
9
+ # ary = Pry::HistoryArray.new 10
10
+ # ary << 1 << 2 << 3
11
+ # ary[0] # => 1
12
+ # ary[1] # => 2
13
+ # 10.times { |n| ary << n }
14
+ # ary[0] # => nil
15
+ # ary[-1] # => 9
16
+ class HistoryArray
17
+ include Enumerable
18
+
19
+ # @param [Integer] size Maximum amount of objects in the array
20
+ def initialize(size)
21
+ @max_size = size
22
+
23
+ @hash = {}
24
+ @count = 0
25
+ end
26
+
27
+ # Pushes an object at the end of the array
28
+ # @param [Object] value Object to be added
29
+ def <<(value)
30
+ @hash[@count] = value
31
+
32
+ if @hash.size > max_size
33
+ @hash.delete(@count - max_size)
34
+ end
35
+
36
+ @count += 1
37
+
38
+ self
39
+ end
40
+
41
+ # @overload [](index)
42
+ # @param [Integer] index Index of the item to access.
43
+ # @return [Object, nil] Item at that index or nil if it has been removed.
44
+ # @overload [](index, size)
45
+ # @param [Integer] index Index of the first item to access.
46
+ # @param [Integer] size Amount of items to access
47
+ # @return [Array, nil] The selected items. Nil if index is greater than
48
+ # the size of the array.
49
+ # @overload [](range)
50
+ # @param [Range<Integer>] range Range of indices to access.
51
+ # @return [Array, nil] The selected items. Nil if index is greater than
52
+ # the size of the array.
53
+ def [](index_or_range, size = nil)
54
+ if index_or_range.is_a? Integer
55
+ index = convert_index(index_or_range)
56
+
57
+ if size
58
+ end_index = index + size
59
+ index > @count ? nil : (index...[end_index, @count].min).map do |n|
60
+ @hash[n]
61
+ end
62
+ else
63
+ @hash[index]
64
+ end
65
+ else
66
+ range = convert_range(index_or_range)
67
+ range.begin > @count ? nil : range.map { |n| @hash[n] }
68
+ end
69
+ end
70
+
71
+ # @return [Integer] Amount of objects in the array
72
+ def size
73
+ @count
74
+ end
75
+ alias count size
76
+ alias length size
77
+
78
+ def empty?
79
+ size == 0
80
+ end
81
+
82
+ def each
83
+ ((@count - size)...@count).each do |n|
84
+ yield @hash[n]
85
+ end
86
+ end
87
+
88
+ def to_a
89
+ ((@count - size)...@count).map { |n| @hash[n] }
90
+ end
91
+
92
+ # Returns [Hash] copy of the internal @hash history
93
+ def to_h
94
+ @hash.dup
95
+ end
96
+
97
+ def pop!
98
+ @hash.delete @count - 1
99
+ @count -= 1
100
+ end
101
+
102
+ def inspect
103
+ "#<#{self.class} size=#{size} first=#{@count - size} max_size=#{max_size}>"
104
+ end
105
+
106
+ # @return [Integer] Maximum amount of objects in the array
107
+ attr_reader :max_size
108
+
109
+ private
110
+ def convert_index(n)
111
+ n >= 0 ? n : @count + n
112
+ end
113
+
114
+ def convert_range(range)
115
+ end_index = convert_index(range.end)
116
+ end_index += 1 unless range.exclude_end?
117
+
118
+ Range.new(convert_index(range.begin), [end_index, @count].min, true)
119
+ end
120
+ end
121
+ end
data/lib/pry/hooks.rb ADDED
@@ -0,0 +1,230 @@
1
+ class Pry
2
+
3
+ # Implements a hooks system for Pry. A hook is a callable that is
4
+ # associated with an event. A number of events are currently
5
+ # provided by Pry, these include: `:when_started`, `:before_session`, `:after_session`.
6
+ # A hook must have a name, and is connected with an event by the
7
+ # `Pry::Hooks#add_hook` method.
8
+ # @example Adding a hook for the `:before_session` event.
9
+ # Pry.config.hooks.add_hook(:before_session, :say_hi) do
10
+ # puts "hello"
11
+ # end
12
+ class Hooks
13
+
14
+ # Converts a hash to a `Pry::Hooks` instance. All hooks defined
15
+ # this way are anonymous. This functionality is primarily to
16
+ # provide backwards-compatibility with the old hash-based hook
17
+ # system in Pry versions < 0.9.8
18
+ # @param [Hash] hash The hash to convert to `Pry::Hooks`.
19
+ # @return [Pry::Hooks] The resulting `Pry::Hooks` instance.
20
+ def self.from_hash(hash)
21
+ return hash if hash.instance_of?(self)
22
+ instance = new
23
+ hash.each do |k, v|
24
+ instance.add_hook(k, nil, v)
25
+ end
26
+ instance
27
+ end
28
+
29
+ def initialize
30
+ @hooks = {}
31
+ end
32
+
33
+ # Ensure that duplicates have their @hooks object
34
+ def initialize_copy(orig)
35
+ hooks_dup = @hooks.dup
36
+ @hooks.each do |k, v|
37
+ hooks_dup[k] = v.dup
38
+ end
39
+
40
+ @hooks = hooks_dup
41
+ end
42
+
43
+ def hooks
44
+ @hooks
45
+ end
46
+ protected :hooks
47
+
48
+ def errors
49
+ @errors ||= []
50
+ end
51
+
52
+ # Destructively merge the contents of two `Pry:Hooks` instances.
53
+ # @param [Pry::Hooks] other The `Pry::Hooks` instance to merge
54
+ # @return [Pry:Hooks] Returns the receiver.
55
+ # @example
56
+ # hooks = Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
57
+ # Pry::Hooks.new.merge!(hooks)
58
+ def merge!(other)
59
+ @hooks.merge!(other.dup.hooks) do |key, v1, v2|
60
+ merge_arrays(v1, v2)
61
+ end
62
+
63
+ self
64
+ end
65
+
66
+ def merge_arrays(array1, array2)
67
+ uniq_keeping_last(array1 + array2, &:first)
68
+ end
69
+ private :merge_arrays
70
+
71
+ def uniq_keeping_last(input, &block)
72
+ hash, output = {}, []
73
+ input.reverse.each{ |i| hash[block[i]] ||= (output.unshift i) }
74
+ output
75
+ end
76
+ private :uniq_keeping_last
77
+
78
+ # Return a new `Pry::Hooks` instance containing a merge of the contents of two `Pry:Hooks` instances,
79
+ # @param [Pry::Hooks] other The `Pry::Hooks` instance to merge
80
+ # @return [Pry::Hooks] The new hash.
81
+ # @example
82
+ # hooks = Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
83
+ # Pry::Hooks.new.merge(hooks)
84
+ def merge(other)
85
+ self.dup.tap do |v|
86
+ v.merge!(other)
87
+ end
88
+ end
89
+
90
+ # Add a new hook to be executed for the `name` even.
91
+ # @param [Symbol] event_name The name of the event.
92
+ # @param [Symbol] hook_name The name of the hook.
93
+ # @param [#call] callable The callable.
94
+ # @yield The block to use as the callable (if `callable` parameter not provided)
95
+ # @return [Pry:Hooks] Returns the receiver.
96
+ # @example
97
+ # Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
98
+ def add_hook(event_name, hook_name, callable=nil, &block)
99
+ @hooks[event_name] ||= []
100
+
101
+ # do not allow duplicates, but allow multiple `nil` hooks
102
+ # (anonymous hooks)
103
+ if hook_exists?(event_name, hook_name) && !hook_name.nil?
104
+ raise ArgumentError, "Hook with name '#{hook_name}' already defined!"
105
+ end
106
+
107
+ if !block && !callable
108
+ raise ArgumentError, "Must provide a block or callable."
109
+ end
110
+
111
+ # ensure we only have one anonymous hook
112
+ @hooks[event_name].delete_if { |h, k| h.nil? } if hook_name.nil?
113
+
114
+ if block
115
+ @hooks[event_name] << [hook_name, block]
116
+ elsif callable
117
+ @hooks[event_name] << [hook_name, callable]
118
+ end
119
+
120
+ self
121
+ end
122
+
123
+ # Execute the list of hooks for the `event_name` event.
124
+ # @param [Symbol] event_name The name of the event.
125
+ # @param [Array] args The arguments to pass to each hook function.
126
+ # @return [Object] The return value of the last executed hook.
127
+ # @example
128
+ # my_hooks = Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
129
+ # my_hooks.exec_hook(:before_session) #=> OUTPUT: "hi!"
130
+ def exec_hook(event_name, *args, &block)
131
+ @hooks[event_name] ||= []
132
+
133
+ @hooks[event_name].map do |hook_name, callable|
134
+ begin
135
+ callable.call(*args, &block)
136
+ rescue RescuableException => e
137
+ errors << e
138
+ e
139
+ end
140
+ end.last
141
+ end
142
+
143
+ # Return the number of hook functions registered for the `event_name` event.
144
+ # @param [Symbol] event_name The name of the event.
145
+ # @return [Fixnum] The number of hook functions for `event_name`.
146
+ # @example
147
+ # my_hooks = Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
148
+ # my_hooks.count(:before_session) #=> 1
149
+ def hook_count(event_name)
150
+ @hooks[event_name] ||= []
151
+ @hooks[event_name].size
152
+ end
153
+
154
+ # Return a specific hook for a given event.
155
+ # @param [Symbol] event_name The name of the event.
156
+ # @param [Symbol] hook_name The name of the hook
157
+ # @return [#call] The requested hook.
158
+ # @example
159
+ # my_hooks = Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
160
+ # my_hooks.get_hook(:before_session, :say_hi).call #=> "hi!"
161
+ def get_hook(event_name, hook_name)
162
+ @hooks[event_name] ||= []
163
+ hook = @hooks[event_name].find { |current_hook_name, callable| current_hook_name == hook_name }
164
+ hook.last if hook
165
+ end
166
+
167
+ # Return the hash of hook names / hook functions for a
168
+ # given event. (Note that modifying the returned hash does not
169
+ # alter the hooks, use add_hook/delete_hook for that).
170
+ # @param [Symbol] event_name The name of the event.
171
+ # @return [Hash] The hash of hook names / hook functions.
172
+ # @example
173
+ # my_hooks = Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
174
+ # my_hooks.get_hooks(:before_session) #=> {:say_hi=>#<Proc:0x00000101645e18@(pry):9>}
175
+ def get_hooks(event_name)
176
+ @hooks[event_name] ||= []
177
+ Hash[@hooks[event_name]]
178
+ end
179
+
180
+ # Delete a hook for an event.
181
+ # @param [Symbol] event_name The name of the event.
182
+ # @param [Symbol] hook_name The name of the hook.
183
+ # to delete.
184
+ # @return [#call] The deleted hook.
185
+ # @example
186
+ # my_hooks = Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
187
+ # my_hooks.delete_hook(:before_session, :say_hi)
188
+ def delete_hook(event_name, hook_name)
189
+ @hooks[event_name] ||= []
190
+ deleted_callable = nil
191
+
192
+ @hooks[event_name].delete_if do |current_hook_name, callable|
193
+ if current_hook_name == hook_name
194
+ deleted_callable = callable
195
+ true
196
+ else
197
+ false
198
+ end
199
+ end
200
+ deleted_callable
201
+ end
202
+
203
+ # Clear all hooks functions for a given event.
204
+ # @param [String] event_name The name of the event.
205
+ # @example
206
+ # my_hooks = Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
207
+ # my_hooks.delete_hook(:before_session)
208
+ def delete_hooks(event_name)
209
+ @hooks[event_name] = []
210
+ end
211
+
212
+ alias_method :clear, :delete_hooks
213
+
214
+ # Remove all events and hooks, clearing out the Pry::Hooks
215
+ # instance completely.
216
+ # @example
217
+ # my_hooks = Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
218
+ # my_hooks.clear_all
219
+ def clear_all
220
+ @hooks = {}
221
+ end
222
+
223
+ # @param [Symbol] event_name Name of the event.
224
+ # @param [Symbol] hook_name Name of the hook.
225
+ # @return [Boolean] Whether the hook by the name `hook_name`
226
+ def hook_exists?(event_name, hook_name)
227
+ !!(@hooks[event_name] && @hooks[event_name].find { |name, _| name == hook_name })
228
+ end
229
+ end
230
+ end
data/lib/pry/indent.rb ADDED
@@ -0,0 +1,406 @@
1
+ require 'coderay'
2
+
3
+ class Pry
4
+ ##
5
+ # Pry::Indent is a class that can be used to indent a number of lines
6
+ # containing Ruby code similar as to how IRB does it (but better). The class
7
+ # works by tokenizing a string using CodeRay and then looping over those
8
+ # tokens. Based on the tokens in a line of code that line (or the next one)
9
+ # will be indented or un-indented by correctly.
10
+ #
11
+ class Indent
12
+ include Helpers::BaseHelpers
13
+
14
+ # Raised if {#module_nesting} would not work.
15
+ class UnparseableNestingError < StandardError; end
16
+
17
+ # @return [String] String containing the spaces to be inserted before the next line.
18
+ attr_reader :indent_level
19
+
20
+ # @return [Array<String>] The stack of open tokens.
21
+ attr_reader :stack
22
+
23
+ # The amount of spaces to insert for each indent level.
24
+ SPACES = ' '
25
+
26
+ # Hash containing all the tokens that should increase the indentation
27
+ # level. The keys of this hash are open tokens, the values the matching
28
+ # tokens that should prevent a line from being indented if they appear on
29
+ # the same line.
30
+ OPEN_TOKENS = {
31
+ 'def' => 'end',
32
+ 'class' => 'end',
33
+ 'module' => 'end',
34
+ 'do' => 'end',
35
+ 'if' => 'end',
36
+ 'unless' => 'end',
37
+ 'while' => 'end',
38
+ 'until' => 'end',
39
+ 'for' => 'end',
40
+ 'case' => 'end',
41
+ 'begin' => 'end',
42
+ '[' => ']',
43
+ '{' => '}',
44
+ '(' => ')'
45
+ }
46
+
47
+ # Which tokens can either be open tokens, or appear as modifiers on
48
+ # a single-line.
49
+ SINGLELINE_TOKENS = %w(if while until unless rescue)
50
+
51
+ # Which tokens can be followed by an optional "do" keyword.
52
+ OPTIONAL_DO_TOKENS = %w(for while until)
53
+
54
+ # Collection of token types that should be ignored. Without this list
55
+ # keywords such as "class" inside strings would cause the code to be
56
+ # indented incorrectly.
57
+ #
58
+ # :pre_constant and :preserved_constant are the CodeRay 0.9.8 and 1.0.0
59
+ # classifications of "true", "false", and "nil".
60
+ IGNORE_TOKENS = [:space, :content, :string, :method, :ident,
61
+ :constant, :pre_constant, :predefined_constant]
62
+
63
+ # Tokens that indicate the end of a statement (i.e. that, if they appear
64
+ # directly before an "if" indicates that that if applies to the same line,
65
+ # not the next line)
66
+ #
67
+ # :reserved and :keywords are the CodeRay 0.9.8 and 1.0.0 respectively
68
+ # classifications of "super", "next", "return", etc.
69
+ STATEMENT_END_TOKENS = IGNORE_TOKENS + [:regexp, :integer, :float, :keyword,
70
+ :delimiter, :reserved]
71
+
72
+ # Collection of tokens that should appear dedented even though they
73
+ # don't affect the surrounding code.
74
+ MIDWAY_TOKENS = %w(when else elsif ensure rescue)
75
+
76
+ # Clean the indentation of a fragment of ruby.
77
+ #
78
+ # @param [String] str
79
+ # @return [String]
80
+ def self.indent(str)
81
+ new.indent(str)
82
+ end
83
+
84
+ # Get the module nesting at the given point in the given string.
85
+ #
86
+ # NOTE If the line specified contains a method definition, then the nesting
87
+ # at the start of the method definition is used. Otherwise the nesting from
88
+ # the end of the line is used.
89
+ #
90
+ # @param [String] str The ruby code to analyze
91
+ # @param [Fixnum] line_number The line number (starting from 1)
92
+ # @return [Array<String>]
93
+ def self.nesting_at(str, line_number)
94
+ indent = new
95
+ lines = str.split("\n")
96
+ n = line_number - 1
97
+ to_indent = lines[0...n] << (lines[n] || "").split("def").first(1)
98
+ indent.indent(to_indent.join("\n") << "\n")
99
+ indent.module_nesting
100
+ end
101
+
102
+ def initialize
103
+ reset
104
+ end
105
+
106
+ # reset internal state
107
+ def reset
108
+ @stack = []
109
+ @indent_level = ''
110
+ @heredoc_queue = []
111
+ @close_heredocs = {}
112
+ @string_start = nil
113
+ @awaiting_class = false
114
+ @module_nesting = []
115
+ self
116
+ end
117
+
118
+ # Indents a string and returns it. This string can either be a single line
119
+ # or multiple ones.
120
+ #
121
+ # @example
122
+ # str = <<TXT
123
+ # class User
124
+ # attr_accessor :name
125
+ # end
126
+ # TXT
127
+ #
128
+ # # This would result in the following being displayed:
129
+ # #
130
+ # # class User
131
+ # # attr_accessor :name
132
+ # # end
133
+ # #
134
+ # puts Pry::Indent.new.indent(str)
135
+ #
136
+ # @param [String] input The input string to indent.
137
+ # @return [String] The indented version of +input+.
138
+ #
139
+ def indent(input)
140
+ output = ''
141
+ prefix = indent_level
142
+
143
+ input.lines.each do |line|
144
+
145
+ if in_string?
146
+ tokens = tokenize("#{open_delimiters_line}\n#{line}")
147
+ tokens = tokens.drop_while{ |token, type| !(String === token && token.include?("\n")) }
148
+ previously_in_string = true
149
+ else
150
+ tokens = tokenize(line)
151
+ previously_in_string = false
152
+ end
153
+
154
+ before, after = indentation_delta(tokens)
155
+
156
+ before.times{ prefix.sub! SPACES, '' }
157
+ new_prefix = prefix + SPACES * after
158
+
159
+ line = prefix + line.lstrip unless previously_in_string
160
+
161
+ output += line
162
+
163
+ prefix = new_prefix
164
+ end
165
+
166
+ @indent_level = prefix
167
+
168
+ return output
169
+ end
170
+
171
+ # Get the indentation for the start of the next line.
172
+ #
173
+ # This is what's used between the prompt and the cursor in pry.
174
+ #
175
+ # @return String The correct number of spaces
176
+ #
177
+ def current_prefix
178
+ in_string? ? '' : indent_level
179
+ end
180
+
181
+ # Get the change in indentation indicated by the line.
182
+ #
183
+ # By convention, you remove indent from the line containing end tokens,
184
+ # but add indent to the line *after* that which contains the start tokens.
185
+ #
186
+ # This method returns a pair, where the first number is the number of closings
187
+ # on this line (i.e. the number of indents to remove before the line) and the
188
+ # second is the number of openings (i.e. the number of indents to add after
189
+ # this line)
190
+ #
191
+ # @param [Array] tokens A list of tokens to scan.
192
+ # @return [Array[Integer]]
193
+ #
194
+ def indentation_delta(tokens)
195
+
196
+ # We need to keep track of whether we've seen a "for" on this line because
197
+ # if the line ends with "do" then that "do" should be discounted (i.e. we're
198
+ # only opening one level not two) To do this robustly we want to keep track
199
+ # of the indent level at which we saw the for, so we can differentiate
200
+ # between "for x in [1,2,3] do" and "for x in ([1,2,3].map do" properly
201
+ seen_for_at = []
202
+
203
+ # When deciding whether an "if" token is the start of a multiline statement,
204
+ # or just the middle of a single-line if statement, we just look at the
205
+ # preceding token, which is tracked here.
206
+ last_token, last_kind = [nil, nil]
207
+
208
+ # delta keeps track of the total difference from the start of each line after
209
+ # the given token, 0 is just the level at which the current line started for
210
+ # reference.
211
+ remove_before, add_after = [0, 0]
212
+
213
+ # If the list of tokens contains a matching closing token the line should
214
+ # not be indented (and thus we should return true).
215
+ tokens.each do |token, kind|
216
+ is_singleline_if = (SINGLELINE_TOKENS.include?(token)) && end_of_statement?(last_token, last_kind)
217
+ is_optional_do = (token == "do" && seen_for_at.include?(add_after - 1))
218
+
219
+ last_token, last_kind = token, kind unless kind == :space
220
+ next if IGNORE_TOKENS.include?(kind)
221
+
222
+ track_module_nesting(token, kind)
223
+
224
+ seen_for_at << add_after if OPTIONAL_DO_TOKENS.include?(token)
225
+
226
+ if kind == :delimiter
227
+ track_delimiter(token)
228
+ elsif OPEN_TOKENS.keys.include?(token) && !is_optional_do && !is_singleline_if
229
+ @stack << token
230
+ add_after += 1
231
+ elsif token == OPEN_TOKENS[@stack.last]
232
+ popped = @stack.pop
233
+ track_module_nesting_end(popped)
234
+ if add_after == 0
235
+ remove_before += 1
236
+ else
237
+ add_after -= 1
238
+ end
239
+ elsif MIDWAY_TOKENS.include?(token)
240
+ if add_after == 0
241
+ remove_before += 1
242
+ add_after += 1
243
+ end
244
+ end
245
+ end
246
+
247
+ return [remove_before, add_after]
248
+ end
249
+
250
+ # If the code just before an "if" or "while" token on a line looks like the end of a statement,
251
+ # then we want to treat that "if" as a singleline, not multiline statement.
252
+ def end_of_statement?(last_token, last_kind)
253
+ (last_token =~ /^[)\]}\/]$/ || STATEMENT_END_TOKENS.include?(last_kind))
254
+ end
255
+
256
+ # Are we currently in the middle of a string literal.
257
+ #
258
+ # This is used to determine whether to re-indent a given line, we mustn't re-indent
259
+ # within string literals because to do so would actually change the value of the
260
+ # String!
261
+ #
262
+ # @return Boolean
263
+ def in_string?
264
+ !open_delimiters.empty?
265
+ end
266
+
267
+ # Given a string of Ruby code, use CodeRay to export the tokens.
268
+ #
269
+ # @param [String] string The Ruby to lex
270
+ # @return [Array] An Array of pairs of [token_value, token_type]
271
+ def tokenize(string)
272
+ tokens = CodeRay.scan(string, :ruby)
273
+ tokens = tokens.tokens.each_slice(2) if tokens.respond_to?(:tokens) # Coderay 1.0.0
274
+ tokens.to_a
275
+ end
276
+
277
+ # Update the internal state about what kind of strings are open.
278
+ #
279
+ # Most of the complication here comes from the fact that HEREDOCs can be nested. For
280
+ # normal strings (which can't be nested) we assume that CodeRay correctly pairs
281
+ # open-and-close delimiters so we don't bother checking what they are.
282
+ #
283
+ # @param [String] token The token (of type :delimiter)
284
+ def track_delimiter(token)
285
+ case token
286
+ when /^<<-(["'`]?)(.*)\\1/
287
+ @heredoc_queue << token
288
+ @close_heredocs[token] = /^\s*$2/
289
+ when @close_heredocs[@heredoc_queue.first]
290
+ @heredoc_queue.shift
291
+ else
292
+ if @string_start
293
+ @string_start = nil
294
+ else
295
+ @string_start = token
296
+ end
297
+ end
298
+ end
299
+
300
+ # All the open delimiters, in the order that they first appeared.
301
+ #
302
+ # @return [String]
303
+ def open_delimiters
304
+ @heredoc_queue + [@string_start].compact
305
+ end
306
+
307
+ # Return a string which restores the CodeRay string status to the correct value by
308
+ # opening HEREDOCs and strings.
309
+ #
310
+ # @return String
311
+ def open_delimiters_line
312
+ "puts #{open_delimiters.join(", ")}"
313
+ end
314
+
315
+ # Update the internal state relating to module nesting.
316
+ #
317
+ # It's responsible for adding to the @module_nesting array, which looks
318
+ # something like:
319
+ #
320
+ # [ ["class", "Foo"], ["module", "Bar::Baz"], ["class <<", "self"] ]
321
+ #
322
+ # A nil value in the @module_nesting array happens in two places: either
323
+ # when @awaiting_class is true and we're still waiting for the string to
324
+ # fill that space, or when a parse was rejected.
325
+ #
326
+ # At the moment this function is quite restricted about what formats it will
327
+ # parse, for example we disallow expressions after the class keyword. This
328
+ # could maybe be improved in the future.
329
+ #
330
+ # @param [String] token a token from Coderay
331
+ # @param [Symbol] kind the kind of that token
332
+ def track_module_nesting(token, kind)
333
+ if kind == :keyword && (token == "class" || token == "module")
334
+ @module_nesting << [token, nil]
335
+ @awaiting_class = true
336
+ elsif @awaiting_class
337
+ if kind == :operator && token == "<<" && @module_nesting.last[0] == "class"
338
+ @module_nesting.last[0] = "class <<"
339
+ @awaiting_class = true
340
+ elsif kind == :class && token =~ /\A(self|[A-Z:][A-Za-z0-9_:]*)\z/
341
+ @module_nesting.last[1] = token if kind == :class
342
+ @awaiting_class = false
343
+ else
344
+ # leave @module_nesting[-1]
345
+ @awaiting_class = false
346
+ end
347
+ end
348
+ end
349
+
350
+ # Update the internal state relating to module nesting on 'end'.
351
+ #
352
+ # If the current 'end' pairs up with a class or a module then we should
353
+ # pop an array off of @module_nesting
354
+ #
355
+ # @param [String] token a token from Coderay
356
+ # @param [Symbol] kind the kind of that token
357
+ def track_module_nesting_end(token, kind=:keyword)
358
+ if kind == :keyword && (token == "class" || token == "module")
359
+ @module_nesting.pop
360
+ end
361
+ end
362
+
363
+ # Return a list of strings which can be used to re-construct the Module.nesting at
364
+ # the current point in the file.
365
+ #
366
+ # Returns nil if the syntax of the file was not recognizable.
367
+ #
368
+ # @return [Array<String>]
369
+ def module_nesting
370
+ @module_nesting.map do |(kind, token)|
371
+ raise UnparseableNestingError, @module_nesting.inspect if token.nil?
372
+
373
+ "#{kind} #{token}"
374
+ end
375
+ end
376
+
377
+ # Return a string which, when printed, will rewrite the previous line with
378
+ # the correct indentation. Mostly useful for fixing 'end'.
379
+ #
380
+ # @param [String] prompt The user's prompt
381
+ # @param [String] code The code the user just typed in.
382
+ # @param [Fixnum] overhang (0) The number of chars to erase afterwards (i.e.,
383
+ # the difference in length between the old line and the new one).
384
+ # @return [String]
385
+ def correct_indentation(prompt, code, overhang=0)
386
+ prompt = prompt.delete("\001\002")
387
+ line_to_measure = Pry::Helpers::Text.strip_color(prompt) << code
388
+ whitespace = ' ' * overhang
389
+
390
+ _, cols = Terminal.screen_size
391
+
392
+ cols = cols.to_i
393
+ lines = (cols != 0 ? (line_to_measure.length / cols + 1) : 1).to_i
394
+
395
+ if Pry::Helpers::BaseHelpers.windows_ansi?
396
+ move_up = "\e[#{lines}F"
397
+ move_down = "\e[#{lines}E"
398
+ else
399
+ move_up = "\e[#{lines}A\e[0G"
400
+ move_down = "\e[#{lines}B\e[0G"
401
+ end
402
+
403
+ "#{move_up}#{prompt}#{colorize_code(code)}#{whitespace}#{move_down}"
404
+ end
405
+ end
406
+ end