Module: Pry::Helpers::CommandHelpers

Includes:
OptionsHelpers
Included in:
Command
Defined in:
lib/pry/helpers/command_helpers.rb

Class Method Summary collapse

Instance Method Summary collapse

Methods included from OptionsHelpers

#method_object, method_object, method_options, #method_options

Class Method Details

.absolute_index_number(line_number, array_length) ⇒ Object



245
246
247
248
249
250
251
# File 'lib/pry/helpers/command_helpers.rb', line 245

def absolute_index_number(line_number, array_length)
  if line_number >= 0
    line_number
  else
    [array_length + line_number, 0].max
  end
end

.absolute_index_range(range_or_number, array_length) ⇒ Object



253
254
255
256
257
258
259
260
261
262
263
# File 'lib/pry/helpers/command_helpers.rb', line 253

def absolute_index_range(range_or_number, array_length)
  case range_or_number
  when Range
    a = absolute_index_number(range_or_number.begin, array_length)
    b = absolute_index_number(range_or_number.end, array_length)
  else
    a = b = absolute_index_number(range_or_number, array_length)
  end

  Range.new(a, b)
end

.blocking_flag_for_editor(block) ⇒ Object

Some editors that run outside the terminal allow you to control whether or not to block the process from which they were launched (in this case, Pry). For those editors, return the flag that produces the desired behavior.



117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/pry/helpers/command_helpers.rb', line 117

def blocking_flag_for_editor(block)
  case editor_name
  when /^emacsclient/
    '--no-wait' unless block
  when /^[gm]vim/
    '--nofork' if block
  when /^jedit/
    '-wait' if block
  when /^mate/, /^subl/
    '-w' if block
  end
end

.command_error(message, omit_help, klass = CommandError) ⇒ Object

Raises:

  • (klass)


71
72
73
74
# File 'lib/pry/helpers/command_helpers.rb', line 71

def command_error(message, omit_help, klass=CommandError)
  message += " Type `#{command_name} --help` for help." unless omit_help
  raise klass, message
end

.editor_nameObject

Get the name of the binary that Pry.config.editor points to.

This is useful for deciding which flags we pass to the editor as we can just use the program's name and ignore any absolute paths.

Examples:

Pry.config.editor="/home/conrad/bin/textmate -w"
editor_name
# => textmate


170
171
172
# File 'lib/pry/helpers/command_helpers.rb', line 170

def editor_name
  File.basename(Pry.config.editor).split(" ").first
end

.file_and_line_from_binding(target) ⇒ Array

Return the file and line for a Binding.

Parameters:

  • target (Binding)

    The binding

Returns:

  • (Array)

    The file and line



29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/pry/helpers/command_helpers.rb', line 29

def file_and_line_from_binding(target)
  file = target.eval('__FILE__')
  line_num = target.eval('__LINE__')
  if rbx?
    if !target.instance_variable_defined?(:@__actual_file__)
      target.instance_variable_set(:@__actual_file__, RbxPath.convert_path_to_full(target.variables.method.file.to_s))
    end
    file = target.instance_variable_get(:@__actual_file__).to_s
  end

  [file, line_num]
end

.get_method_or_raise(name, target, opts = {}, omit_help = false) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/pry/helpers/command_helpers.rb', line 48

def get_method_or_raise(name, target, opts={}, omit_help=false)
  meth = Pry::Method.from_str(name, target, opts)

  if name && !meth
    command_error("The method '#{name}' could not be found.", omit_help)
  end

  (opts[:super] || 0).times do
    if meth.super
      meth = meth.super
    else
      command_error("'#{meth.name_with_owner}' has no super method.", omit_help)
    end
  end

  if !meth || (!name && internal_binding?(target))
    command_error("No method name given, and context is not a method.", omit_help, NonMethodContextError)
  end

  set_file_and_dir_locals(meth.source_file)
  meth
end

.internal_binding?(target) ⇒ Boolean

Returns:

  • (Boolean)


42
43
44
45
46
# File 'lib/pry/helpers/command_helpers.rb', line 42

def internal_binding?(target)
  m = target.eval("__method__").to_s
  # class_eval is here because of http://jira.codehaus.org/browse/JRUBY-6753
  ["__binding__", "__pry__", "class_eval"].include?(m)
end

.invoke_editor(file, line, reloading) ⇒ Object

Raises:



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/pry/helpers/command_helpers.rb', line 88

def invoke_editor(file, line, reloading)
  raise CommandError, "Please set Pry.config.editor or export $VISUAL or $EDITOR" unless Pry.config.editor
  if Pry.config.editor.respond_to?(:call)
    args = [file, line, reloading][0...(Pry.config.editor.arity)]
    editor_invocation = Pry.config.editor.call(*args)
  else
    editor_invocation = "#{Pry.config.editor} #{blocking_flag_for_editor(reloading)} #{start_line_syntax_for_editor(file, line)}"
  end
  return nil unless editor_invocation

  if jruby?
    begin
      require 'spoon'
      pid = Spoon.spawnp(*editor_invocation.split)
      Process.waitpid(pid)
    rescue FFI::NotFoundError
      system(editor_invocation)
    end
  else
    # Note we dont want to use Pry.config.system here as that
    # may be invoked non-interactively (i.e via Open4), whereas we want to
    # ensure the editor is always interactive
    system(editor_invocation) or raise CommandError, "`#{editor_invocation}` gave exit status: #{$?.exitstatus}"
  end
end

.make_header(meth, content = meth.source) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
# File 'lib/pry/helpers/command_helpers.rb', line 76

def make_header(meth, content=meth.source)
  header = "\n#{Pry::Helpers::Text.bold('From:')} #{meth.source_file} "

  if meth.source_type == :c
    header << "(C Method):\n"
  else
    header << "@ line #{meth.source_line}:\n"
  end

  header << "#{Pry::Helpers::Text.bold("Number of lines:")} #{content.each_line.count.to_s}\n"
end

.one_index_number(line_number) ⇒ Object



223
224
225
226
227
228
229
# File 'lib/pry/helpers/command_helpers.rb', line 223

def one_index_number(line_number)
  if line_number > 0
    line_number - 1
  else
    line_number
  end
end

.one_index_range(range) ⇒ Object

convert a 1-index range to a 0-indexed one



232
233
234
# File 'lib/pry/helpers/command_helpers.rb', line 232

def one_index_range(range)
  Range.new(one_index_number(range.begin), one_index_number(range.end))
end

.one_index_range_or_number(range_or_number) ⇒ Object



236
237
238
239
240
241
242
243
# File 'lib/pry/helpers/command_helpers.rb', line 236

def one_index_range_or_number(range_or_number)
  case range_or_number
  when Range
    one_index_range(range_or_number)
  else
    one_index_number(range_or_number)
  end
end

.render_output(str, opts = {}) ⇒ Object



18
19
20
21
22
23
24
# File 'lib/pry/helpers/command_helpers.rb', line 18

def render_output(str, opts={})
  if opts[:flood]
    output.puts str
  else
    stagger_output str
  end
end

.restrict_to_lines(content, lines) ⇒ String

Restrict a string to the given range of lines (1-indexed)

Parameters:

  • content (String)

    The string.

  • lines (Range, Fixnum)

    The line(s) to restrict it to.

Returns:

  • (String)

    The resulting string.



218
219
220
221
# File 'lib/pry/helpers/command_helpers.rb', line 218

def restrict_to_lines(content, lines)
  line_range = one_index_range_or_number(lines)
  Array(content.lines.to_a[line_range]).join
end

.start_line_syntax_for_editor(file_name, line_number) ⇒ Object

Return the syntax for a given editor for starting the editor and moving to a particular line within that file



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/pry/helpers/command_helpers.rb', line 132

def start_line_syntax_for_editor(file_name, line_number)
  if windows?
    file_name = file_name.gsub(/\//, '\\')
  end

  # special case for 1st line
  return file_name if line_number <= 1

  case editor_name
  when /^[gm]?vi/, /^emacs/, /^nano/, /^pico/, /^gedit/, /^kate/
    "+#{line_number} #{file_name}"
  when /^mate/, /^geany/
    "-l #{line_number} #{file_name}"
  when /^subl/
    "#{file_name}:#{line_number}"
  when /^uedit32/
    "#{file_name}/#{line_number}"
  when /^jedit/
    "#{file_name} +line:#{line_number}"
  else
    if windows?
      "#{file_name}"
    else
      "+#{line_number} #{file_name}"
    end
  end
end

.temp_file(ext = '.rb') ⇒ String

Open a temp file and yield it to the block, closing it after

Returns:

  • (String)

    The path of the temp file



11
12
13
14
15
16
# File 'lib/pry/helpers/command_helpers.rb', line 11

def temp_file(ext='.rb')
  file = Tempfile.new(['pry', ext])
  yield file
ensure
  file.close(true) if file
end

.unindent(text) ⇒ String

Remove any common leading whitespace from every line in text.

This can be used to make a HEREDOC line up with the left margin, without sacrificing the indentation level of the source code.

e.g. opt.banner unindent <<-USAGE Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "Ut enim ad minim veniam." USAGE

Heavily based on textwrap.dedent from Python, which is: Copyright (C) 1999-2001 Gregory P. Ward. Copyright (C) 2002, 2003 Python Software Foundation. Written by Greg Ward [email protected]

Licensed under http://docs.python.org/license.html From http://hg.python.org/cpython/file/6b9f0a6efaeb/Lib/textwrap.py

Parameters:

  • text (String)

    The text from which to remove indentation

Returns:

  • (String)

    The text with indentation stripped.



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/pry/helpers/command_helpers.rb', line 196

def unindent(text)
  # Empty blank lines
  text = text.sub(/^[ \t]+$/, '')

  # Find the longest common whitespace to all indented lines
  margin = text.scan(/^[ \t]*(?=[^ \t\n])/).inject do |current_margin, next_indent|
    if next_indent.start_with?(current_margin)
      current_margin
    elsif current_margin.start_with?(next_indent)
      next_indent
    else
      ""
    end
  end

  text.gsub(/^#{margin}/, '')
end

Instance Method Details

#absolute_index_number(line_number, array_length) ⇒ Object (private)



245
246
247
248
249
250
251
# File 'lib/pry/helpers/command_helpers.rb', line 245

def absolute_index_number(line_number, array_length)
  if line_number >= 0
    line_number
  else
    [array_length + line_number, 0].max
  end
end

#absolute_index_range(range_or_number, array_length) ⇒ Object (private)



253
254
255
256
257
258
259
260
261
262
263
# File 'lib/pry/helpers/command_helpers.rb', line 253

def absolute_index_range(range_or_number, array_length)
  case range_or_number
  when Range
    a = absolute_index_number(range_or_number.begin, array_length)
    b = absolute_index_number(range_or_number.end, array_length)
  else
    a = b = absolute_index_number(range_or_number, array_length)
  end

  Range.new(a, b)
end

#blocking_flag_for_editor(block) ⇒ Object (private)

Some editors that run outside the terminal allow you to control whether or not to block the process from which they were launched (in this case, Pry). For those editors, return the flag that produces the desired behavior.



117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/pry/helpers/command_helpers.rb', line 117

def blocking_flag_for_editor(block)
  case editor_name
  when /^emacsclient/
    '--no-wait' unless block
  when /^[gm]vim/
    '--nofork' if block
  when /^jedit/
    '-wait' if block
  when /^mate/, /^subl/
    '-w' if block
  end
end

#command_error(message, omit_help, klass = CommandError) ⇒ Object (private)



71
72
73
74
# File 'lib/pry/helpers/command_helpers.rb', line 71

def command_error(message, omit_help, klass=CommandError)
  message += " Type `#{command_name} --help` for help." unless omit_help
  raise klass, message
end

#editor_nameObject (private)

Get the name of the binary that Pry.config.editor points to.

This is useful for deciding which flags we pass to the editor as we can just use the program's name and ignore any absolute paths.

Examples:

Pry.config.editor="/home/conrad/bin/textmate -w"
editor_name
# => textmate


170
171
172
# File 'lib/pry/helpers/command_helpers.rb', line 170

def editor_name
  File.basename(Pry.config.editor).split(" ").first
end

#file_and_line_from_binding(target) ⇒ Array (private)

Return the file and line for a Binding.

Parameters:

  • target (Binding)

    The binding

Returns:

  • (Array)

    The file and line



29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/pry/helpers/command_helpers.rb', line 29

def file_and_line_from_binding(target)
  file = target.eval('__FILE__')
  line_num = target.eval('__LINE__')
  if rbx?
    if !target.instance_variable_defined?(:@__actual_file__)
      target.instance_variable_set(:@__actual_file__, RbxPath.convert_path_to_full(target.variables.method.file.to_s))
    end
    file = target.instance_variable_get(:@__actual_file__).to_s
  end

  [file, line_num]
end

#get_method_or_raise(name, target, opts = {}, omit_help = false) ⇒ Object (private)



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/pry/helpers/command_helpers.rb', line 48

def get_method_or_raise(name, target, opts={}, omit_help=false)
  meth = Pry::Method.from_str(name, target, opts)

  if name && !meth
    command_error("The method '#{name}' could not be found.", omit_help)
  end

  (opts[:super] || 0).times do
    if meth.super
      meth = meth.super
    else
      command_error("'#{meth.name_with_owner}' has no super method.", omit_help)
    end
  end

  if !meth || (!name && internal_binding?(target))
    command_error("No method name given, and context is not a method.", omit_help, NonMethodContextError)
  end

  set_file_and_dir_locals(meth.source_file)
  meth
end

#internal_binding?(target) ⇒ Object (private)



42
43
44
45
46
# File 'lib/pry/helpers/command_helpers.rb', line 42

def internal_binding?(target)
  m = target.eval("__method__").to_s
  # class_eval is here because of http://jira.codehaus.org/browse/JRUBY-6753
  ["__binding__", "__pry__", "class_eval"].include?(m)
end

#invoke_editor(file, line, reloading) ⇒ Object (private)



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/pry/helpers/command_helpers.rb', line 88

def invoke_editor(file, line, reloading)
  raise CommandError, "Please set Pry.config.editor or export $VISUAL or $EDITOR" unless Pry.config.editor
  if Pry.config.editor.respond_to?(:call)
    args = [file, line, reloading][0...(Pry.config.editor.arity)]
    editor_invocation = Pry.config.editor.call(*args)
  else
    editor_invocation = "#{Pry.config.editor} #{blocking_flag_for_editor(reloading)} #{start_line_syntax_for_editor(file, line)}"
  end
  return nil unless editor_invocation

  if jruby?
    begin
      require 'spoon'
      pid = Spoon.spawnp(*editor_invocation.split)
      Process.waitpid(pid)
    rescue FFI::NotFoundError
      system(editor_invocation)
    end
  else
    # Note we dont want to use Pry.config.system here as that
    # may be invoked non-interactively (i.e via Open4), whereas we want to
    # ensure the editor is always interactive
    system(editor_invocation) or raise CommandError, "`#{editor_invocation}` gave exit status: #{$?.exitstatus}"
  end
end

#make_header(meth, content = meth.source) ⇒ Object (private)



76
77
78
79
80
81
82
83
84
85
86
# File 'lib/pry/helpers/command_helpers.rb', line 76

def make_header(meth, content=meth.source)
  header = "\n#{Pry::Helpers::Text.bold('From:')} #{meth.source_file} "

  if meth.source_type == :c
    header << "(C Method):\n"
  else
    header << "@ line #{meth.source_line}:\n"
  end

  header << "#{Pry::Helpers::Text.bold("Number of lines:")} #{content.each_line.count.to_s}\n"
end

#one_index_number(line_number) ⇒ Object (private)



223
224
225
226
227
228
229
# File 'lib/pry/helpers/command_helpers.rb', line 223

def one_index_number(line_number)
  if line_number > 0
    line_number - 1
  else
    line_number
  end
end

#one_index_range(range) ⇒ Object (private)

convert a 1-index range to a 0-indexed one



232
233
234
# File 'lib/pry/helpers/command_helpers.rb', line 232

def one_index_range(range)
  Range.new(one_index_number(range.begin), one_index_number(range.end))
end

#one_index_range_or_number(range_or_number) ⇒ Object (private)



236
237
238
239
240
241
242
243
# File 'lib/pry/helpers/command_helpers.rb', line 236

def one_index_range_or_number(range_or_number)
  case range_or_number
  when Range
    one_index_range(range_or_number)
  else
    one_index_number(range_or_number)
  end
end

#render_output(str, opts = {}) ⇒ Object (private)



18
19
20
21
22
23
24
# File 'lib/pry/helpers/command_helpers.rb', line 18

def render_output(str, opts={})
  if opts[:flood]
    output.puts str
  else
    stagger_output str
  end
end

#restrict_to_lines(content, lines) ⇒ String (private)

Restrict a string to the given range of lines (1-indexed)

Parameters:

  • content (String)

    The string.

  • lines (Range, Fixnum)

    The line(s) to restrict it to.

Returns:

  • (String)

    The resulting string.



218
219
220
221
# File 'lib/pry/helpers/command_helpers.rb', line 218

def restrict_to_lines(content, lines)
  line_range = one_index_range_or_number(lines)
  Array(content.lines.to_a[line_range]).join
end

#start_line_syntax_for_editor(file_name, line_number) ⇒ Object (private)

Return the syntax for a given editor for starting the editor and moving to a particular line within that file



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/pry/helpers/command_helpers.rb', line 132

def start_line_syntax_for_editor(file_name, line_number)
  if windows?
    file_name = file_name.gsub(/\//, '\\')
  end

  # special case for 1st line
  return file_name if line_number <= 1

  case editor_name
  when /^[gm]?vi/, /^emacs/, /^nano/, /^pico/, /^gedit/, /^kate/
    "+#{line_number} #{file_name}"
  when /^mate/, /^geany/
    "-l #{line_number} #{file_name}"
  when /^subl/
    "#{file_name}:#{line_number}"
  when /^uedit32/
    "#{file_name}/#{line_number}"
  when /^jedit/
    "#{file_name} +line:#{line_number}"
  else
    if windows?
      "#{file_name}"
    else
      "+#{line_number} #{file_name}"
    end
  end
end

#temp_file(ext = '.rb') ⇒ String (private)

Open a temp file and yield it to the block, closing it after

Returns:

  • (String)

    The path of the temp file



11
12
13
14
15
16
# File 'lib/pry/helpers/command_helpers.rb', line 11

def temp_file(ext='.rb')
  file = Tempfile.new(['pry', ext])
  yield file
ensure
  file.close(true) if file
end

#unindent(text) ⇒ String (private)

Remove any common leading whitespace from every line in text.

This can be used to make a HEREDOC line up with the left margin, without sacrificing the indentation level of the source code.

e.g. opt.banner unindent <<-USAGE Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "Ut enim ad minim veniam." USAGE

Heavily based on textwrap.dedent from Python, which is: Copyright (C) 1999-2001 Gregory P. Ward. Copyright (C) 2002, 2003 Python Software Foundation. Written by Greg Ward [email protected]

Licensed under http://docs.python.org/license.html From http://hg.python.org/cpython/file/6b9f0a6efaeb/Lib/textwrap.py

Parameters:

  • text (String)

    The text from which to remove indentation

Returns:

  • (String)

    The text with indentation stripped.



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/pry/helpers/command_helpers.rb', line 196

def unindent(text)
  # Empty blank lines
  text = text.sub(/^[ \t]+$/, '')

  # Find the longest common whitespace to all indented lines
  margin = text.scan(/^[ \t]*(?=[^ \t\n])/).inject do |current_margin, next_indent|
    if next_indent.start_with?(current_margin)
      current_margin
    elsif current_margin.start_with?(next_indent)
      next_indent
    else
      ""
    end
  end

  text.gsub(/^#{margin}/, '')
end