pry--pry/lib/pry/default_commands/input_and_output.rb

477 lines
16 KiB
Ruby
Raw Normal View History

require 'tempfile'
class Pry
module DefaultCommands
InputAndOutput = Pry::CommandSet.new do
command(/\.(.*)/, "All text following a '.' is forwarded to the shell.", :listing => ".<shell command>", :use_prefix => false, :takes_block => true) do |cmd|
if cmd =~ /^cd\s+(.+)/i
dest = $1
begin
Dir.chdir File.expand_path(dest)
rescue Errno::ENOENT
raise CommandError, "No such directory: #{dest}"
end
else
pass_block(cmd)
if command_block
command_block.call `#{cmd}`
else
Pry.config.system.call(output, cmd, _pry_)
end
end
end
command "shell-mode", "Toggle shell mode. Bring in pwd prompt and file completion." do
case _pry_.prompt
when Pry::SHELL_PROMPT
_pry_.pop_prompt
_pry_.custom_completions = Pry::DEFAULT_CUSTOM_COMPLETIONS
else
_pry_.push_prompt Pry::SHELL_PROMPT
_pry_.custom_completions = Pry::FILE_COMPLETIONS
Readline.completion_proc = Pry::InputCompleter.build_completion_proc target,
_pry_.instance_eval(&Pry::FILE_COMPLETIONS)
end
end
alias_command "file-mode", "shell-mode"
2012-06-23 19:51:37 +00:00
create_command "gist", "Gist a method or expression history to github.", :requires_gem => "jist" do
include Pry::Helpers::DocumentationHelpers
banner <<-USAGE
Usage: gist [OPTIONS] [METH]
Gist method (doc or source) or input expression to github.
Ensure the `gist` gem is properly working before use. http://github.com/defunkt/gist for instructions.
e.g: gist -m my_method # gist the method my_method
e.g: gist -d my_method # gist the documentation for my_method
e.g: gist -i 1..10 # gist the input expressions from 1 to 10
e.g: gist -k show-method # gist the command show-method
e.g: gist -c Pry # gist the Pry class
e.g: gist -m hello_world --lines 2..-2 # gist from lines 2 to the second-last of the hello_world method
e.g: gist -m my_method --clip # Copy my_method source to clipboard, do not gist it.
USAGE
command_options :shellwords => false
attr_accessor :content
attr_accessor :code_type
def setup
2012-06-23 19:51:37 +00:00
require 'jist'
self.content = ""
self.code_type = :ruby
end
def options(opt)
2012-05-27 08:36:41 +00:00
opt.on :m, :method, "Gist a method's source.", :argument => true do |meth_name|
meth = get_method_or_raise(meth_name, target, {})
self.content << meth.source << "\n"
self.code_type = meth.source_type
end
2012-05-27 08:36:41 +00:00
opt.on :d, :doc, "Gist a method's documentation.", :argument => true do |meth_name|
meth = get_method_or_raise(meth_name, target, {})
text.no_color do
self.content << process_comment_markup(meth.doc, self.code_type) << "\n"
end
self.code_type = :plain
end
opt.on :k, :command, "Gist a command's source.", :argument => true do |command_name|
command = find_command(command_name)
2012-05-28 23:37:02 +00:00
block = Pry::Method.new(command.block)
self.content << block.source << "\n"
end
opt.on :c, :class, "Gist a class or module's source.", :argument => true do |class_name|
mod = Pry::WrappedModule.from_str(class_name, target)
self.content << mod.source << "\n"
end
opt.on :var, "Gist a variable's content.", :argument => true do |variable_name|
begin
obj = target.eval(variable_name)
rescue Pry::RescuableException
raise CommandError, "Gist failed: Invalid variable name: #{variable_name}"
end
self.content << Pry.config.gist.inspecter.call(obj) << "\n"
end
2012-05-27 08:36:41 +00:00
opt.on :hist, "Gist a range of Readline history lines.", :optional_argument => true, :as => Range, :default => -20..-1 do |range|
h = Pry.history.to_a
self.content << h[one_index_range(convert_to_range(range))].join("\n") << "\n"
end
2012-05-27 08:36:41 +00:00
opt.on :f, :file, "Gist a file.", :argument => true do |file|
self.content << File.read(File.expand_path(file)) << "\n"
end
2012-05-27 08:36:41 +00:00
opt.on :o, :out, "Gist entries from Pry's output result history. Takes an index or range.", :optional_argument => true,
:as => Range, :default => -1 do |range|
range = convert_to_range(range)
range.each do |v|
self.content << Pry.config.gist.inspecter.call(_pry_.output_array[v])
end
self.content << "\n"
end
opt.on :clip, "Copy the selected content to clipboard instead, do NOT gist it.", :default => false
opt.on :p, :public, "Create a public gist (default: false)", :default => false
opt.on :l, :lines, "Only gist a subset of lines from the gistable content.", :optional_argument => true, :as => Range, :default => 1..-1
2012-05-27 08:36:41 +00:00
opt.on :i, :in, "Gist entries from Pry's input expression history. Takes an index or range.", :optional_argument => true,
:as => Range, :default => -1 do |range|
range = convert_to_range(range)
input_expressions = _pry_.input_array[range] || []
Array(input_expressions).each_with_index do |code, index|
corrected_index = index + range.first
if code && code != ""
self.content << code
if code !~ /;\Z/
self.content << "#{comment_expression_result_for_gist(Pry.config.gist.inspecter.call(_pry_.output_array[corrected_index]))}"
end
end
end
end
end
def process
if self.content =~ /\A\s*\z/
raise CommandError, "Found no code to gist."
end
if opts.present?(:clip)
perform_clipboard
else
perform_gist
end
end
# copy content to clipboard instead (only used with --clip flag)
def perform_clipboard
2012-06-23 19:51:37 +00:00
copy(self.content)
output.puts "Copied content to clipboard!"
end
def perform_gist
type_map = { :ruby => "rb", :c => "c", :plain => "plain" }
2012-06-23 19:51:37 +00:00
extname = opts.present?(:file) ? ".#{gist_file_extension(opts[:f])}" : ".#{type_map[self.code_type]}"
2012-06-23 19:51:37 +00:00
if opts.present?(:lines)
self.content = restrict_to_lines(content, opts[:l])
end
2012-06-23 19:51:37 +00:00
response = Jist.gist(self.content, :filename => extname,
:public => !!opts[:p])
if response
copy(response['html_url'])
output.puts "Gist created at #{response['html_url']} and added to clipboard."
end
end
def gist_file_extension(file_name)
file_name.split(".").last
end
def convert_to_range(n)
if !n.is_a?(Range)
(n..n)
else
n
end
end
def comment_expression_result_for_gist(result)
content = ""
result.lines.each_with_index do |line, index|
if index == 0
content << "# => #{line}"
else
content << "# #{line}"
end
end
content
end
2012-06-23 19:51:37 +00:00
# Copy a string to the clipboard.
#
# @param [String] content
#
# @copyright Copyright (c) 2008 Chris Wanstrath (MIT)
# @see https://github.com/defunkt/gist/blob/master/lib/gist.rb#L178
def copy(content)
cmd = case true
when system("type pbcopy > /dev/null 2>&1")
:pbcopy
when system("type xclip > /dev/null 2>&1")
:xclip
when system("type putclip > /dev/null 2>&1")
:putclip
end
if cmd
IO.popen(cmd.to_s, 'r+') { |clip| clip.print content }
end
content
end
end
alias_command "clipit", "gist --clip"
create_command "save-file", "Export to a file using content from the REPL." do
2012-01-21 14:54:45 +00:00
banner <<-USAGE
2012-01-23 03:40:40 +00:00
Usage: save-file [OPTIONS] [FILE]
2012-01-21 14:54:45 +00:00
Save REPL content to a file.
e.g: save-file -m my_method -m my_method2 ./hello.rb
e.g: save-file -i 1..10 ./hello.rb --append
e.g: save-file -k show-method ./my_command.rb
2012-01-21 14:54:45 +00:00
e.g: save-file -f sample_file --lines 2..10 ./output_file.rb
USAGE
attr_accessor :content
attr_accessor :file_name
def setup
self.content = ""
end
def convert_to_range(n)
if !n.is_a?(Range)
(n..n)
else
n
end
end
def options(opt)
2012-05-27 08:36:41 +00:00
opt.on :m, :method, "Save a method's source.", :argument => true do |meth_name|
meth = get_method_or_raise(meth_name, target, {})
self.content << meth.source
end
opt.on :c, :class, "Save a class's source.", :argument => true do |class_name|
mod = Pry::WrappedModule.from_str(class_name, target)
self.content << mod.source
end
opt.on :k, :command, "Save a command's source.", :argument => true do |command_name|
command = find_command(command_name)
2012-05-28 23:37:02 +00:00
block = Pry::Method.new(command.block)
2012-01-23 05:06:33 +00:00
self.content << block.source
end
2012-05-27 08:36:41 +00:00
opt.on :f, :file, "Save a file.", :argument => true do |file|
self.content << File.read(File.expand_path(file))
end
2012-05-27 08:36:41 +00:00
opt.on :l, :lines, "Only save a subset of lines.", :optional_argument => true, :as => Range, :default => 1..-1
opt.on :o, :out, "Save entries from Pry's output result history. Takes an index or range.", :optional_argument => true,
:as => Range, :default => -5..-1 do |range|
range = convert_to_range(range)
range.each do |v|
self.content << Pry.config.gist.inspecter.call(_pry_.output_array[v])
end
self.content << "\n"
end
2012-05-27 08:36:41 +00:00
opt.on :i, :in, "Save entries from Pry's input expression history. Takes an index or range.", :optional_argument => true,
:as => Range, :default => -5..-1 do |range|
input_expressions = _pry_.input_array[range] || []
Array(input_expressions).each { |v| self.content << v }
end
opt.on :a, :append, "Append to the given file instead of overwriting it."
end
def process
if args.empty?
raise CommandError, "Must specify a file name."
end
self.file_name = File.expand_path(args.first)
save_file
end
def save_file
if self.content.empty?
raise CommandError, "Found no code to save."
end
File.open(file_name, mode) do |f|
if opts.present?(:lines)
f.puts restrict_to_lines(content, opts[:l])
else
f.puts content
end
end
end
def mode
if opts.present?(:append)
"a"
else
"w"
end
end
end
create_command "cat", "Show code from a file, Pry's input buffer, or the last exception." do
2012-01-08 06:01:15 +00:00
banner <<-USAGE
Usage: cat FILE
cat --ex [STACK_INDEX]
cat --in [INPUT_INDEX_OR_RANGE]
2012-01-08 06:01:15 +00:00
cat is capable of showing part or all of a source file, the context of the
last exception, or an expression from Pry's input history.
2012-01-08 06:01:15 +00:00
cat --ex defaults to showing the lines surrounding the location of the last
exception. Invoking it more than once travels up the exception's backtrace,
and providing a number shows the context of the given index of the backtrace.
USAGE
2012-01-08 06:01:15 +00:00
def options(opt)
2012-05-27 08:36:41 +00:00
opt.on :ex, "Show the context of the last exception.", :optional_argument => true, :as => Integer
opt.on :i, :in, "Show one or more entries from Pry's expression history.", :optional_argument => true, :as => Range, :default => -5..-1
2012-01-08 06:01:15 +00:00
2012-05-27 08:36:41 +00:00
opt.on :s, :start, "Starting line (defaults to the first line).", :optional_argument => true, :as => Integer
opt.on :e, :end, "Ending line (defaults to the last line).", :optional_argument => true, :as => Integer
2012-01-08 06:01:15 +00:00
opt.on :l, :'line-numbers', "Show line numbers."
2012-05-27 08:36:41 +00:00
opt.on :t, :type, "The file type for syntax highlighting (e.g., 'ruby' or 'python').", :argument => true, :as => Symbol
2012-01-08 06:01:15 +00:00
opt.on :f, :flood, "Do not use a pager to view text longer than one screen."
end
def process
handler = case
when opts.present?(:ex)
method :process_ex
when opts.present?(:in)
method :process_in
else
method :process_file
end
output = handler.call do |code|
2012-01-08 06:01:15 +00:00
code.code_type = opts[:type] || :ruby
2011-11-06 07:59:49 +00:00
code.between(opts[:start] || 1, opts[:end] || -1).
2012-01-08 06:01:15 +00:00
with_line_numbers(opts.present?(:'line-numbers') || opts.present?(:ex))
end
render_output(output, opts)
end
2012-01-08 06:01:15 +00:00
def process_ex
window_size = Pry.config.default_window_size || 5
2012-01-08 06:01:15 +00:00
ex = _pry_.last_exception
2012-01-08 06:01:15 +00:00
raise CommandError, "No exception found." unless ex
if opts[:ex].nil?
bt_index = ex.bt_index
ex.inc_bt_index
else
bt_index = opts[:ex]
end
2012-01-08 06:01:15 +00:00
ex_file, ex_line = ex.bt_source_location_for(bt_index)
raise CommandError, "The given backtrace level is out of bounds." unless ex_file
if RbxPath.is_core_path?(ex_file)
ex_file = RbxPath.convert_path_to_full(ex_file)
end
2012-01-22 00:34:43 +00:00
set_file_and_dir_locals(ex_file)
2012-01-08 06:01:15 +00:00
start_line = ex_line - window_size
start_line = 1 if start_line < 1
end_line = ex_line + window_size
header = unindent <<-HEADER
#{text.bold 'Exception:'} #{ex.class}: #{ex.message}
--
#{text.bold('From:')} #{ex_file} @ line #{ex_line} @ #{text.bold("level: #{bt_index}")} of backtrace (of #{ex.backtrace.size - 1}).
HEADER
code = yield(Pry::Code.from_file(ex_file).
between(start_line, end_line).
with_marker(ex_line))
2012-01-08 06:01:15 +00:00
"#{header}#{code}"
end
2012-01-08 06:01:15 +00:00
def process_in
2011-11-06 07:59:49 +00:00
normalized_range = absolute_index_range(opts[:i], _pry_.input_array.length)
input_items = _pry_.input_array[normalized_range] || []
zipped_items = normalized_range.zip(input_items).reject { |_, s| s.nil? || s == "" }
unless zipped_items.length > 0
raise CommandError, "No expressions found."
end
2012-01-08 06:01:15 +00:00
if zipped_items.length > 1
2011-11-06 07:59:49 +00:00
contents = ""
zipped_items.each do |i, s|
contents << "#{text.bold(i.to_s)}:\n"
2012-01-08 06:01:15 +00:00
contents << yield(Pry::Code(s).with_indentation(2)).to_s
2011-11-06 07:59:49 +00:00
end
else
2012-01-08 06:01:15 +00:00
contents = yield(Pry::Code(zipped_items.first.last))
2011-11-06 07:59:49 +00:00
end
contents
end
2012-01-08 06:01:15 +00:00
def process_file
file_name = args.shift
2012-01-08 06:01:15 +00:00
unless file_name
raise CommandError, "Must provide a filename, --in, or --ex."
end
file_name, line_num = file_name.split(':')
file_name = File.expand_path(file_name)
set_file_and_dir_locals(file_name)
2012-01-08 06:01:15 +00:00
code = yield(Pry::Code.from_file(file_name))
code.code_type = detect_code_type_from_file(file_name)
if line_num
code = code.around(line_num.to_i,
Pry.config.default_window_size || 7)
end
code
end
def detect_code_type_from_file(file_name)
name, ext = File.basename(file_name).split('.', 2)
if ext
case ext
when "py"
:python
when "rb", "gemspec", "rakefile", "ru"
:ruby
when "js"
return :javascript
when "yml"
:yaml
else
ext.to_sym
end
else
case name
when "Rakefile", "Gemfile"
:ruby
else
:plain
end
end
end
end
end
end
end