Module: Pry::DefaultCommands
- Defined in:
- lib/pry/default_commands/ls.rb,
lib/pry/default_commands/cd.rb,
lib/pry/default_commands/gems.rb,
lib/pry/default_commands/help.rb,
lib/pry/default_commands/hist.rb,
lib/pry/default_commands/misc.rb,
lib/pry/default_commands/editing.rb,
lib/pry/default_commands/context.rb,
lib/pry/default_commands/commands.rb,
lib/pry/default_commands/easter_eggs.rb,
lib/pry/default_commands/find_method.rb,
lib/pry/default_commands/introspection.rb,
lib/pry/default_commands/navigating_pry.rb,
lib/pry/default_commands/input_and_output.rb
Defined Under Namespace
Modules: ModuleIntrospectionHelpers
Constant Summary
- Ls =
Pry::CommandSet.new do create_command "ls","Show the list of vars and methods in the current scope.", :shellwords => false, :interpolate => false do group "Context" def (opt) opt. unindent <<-USAGE Usage: ls [-m|-M|-p|-pM] [-q|-v] [-c|-i] [Object] ls [-g] [-l] ls shows you which methods, constants and variables are accessible to Pry. By default it shows you the local variables defined in the current shell, and any public methods or instance variables defined on the current object. The colours used are configurable using Pry.config.ls.*_color, and the separator is Pry.config.ls.separator. Pry.config.ls.ceiling is used to hide methods defined higher up in the inheritance chain, this is by default set to [Object, Module, Class] so that methods defined on all Objects are omitted. The -v flag can be used to ignore this setting and show all methods, while the -q can be used to set the ceiling much lower and show only methods defined on the object or its direct class. USAGE opt.on :m, "methods", "Show public methods defined on the Object (default)" opt.on :M, "instance-methods", "Show methods defined in a Module or Class" opt.on :p, "ppp", "Show public, protected (in yellow) and private (in green) methods" opt.on :q, "quiet", "Show only methods defined on object.singleton_class and object.class" opt.on :v, "verbose", "Show methods and constants on all super-classes (ignores Pry.config.ls.ceiling)" opt.on :g, "globals", "Show global variables, including those builtin to Ruby (in cyan)" opt.on :l, "locals", "Show locals, including those provided by Pry (in red)" opt.on :c, "constants", "Show constants, highlighting classes (in blue), and exceptions (in purple).\n" + " " * 32 + "Constants that are pending autoload? are also shown (in yellow)." opt.on :i, "ivars", "Show instance variables (in blue) and class variables (in bright blue)" opt.on :G, "grep", "Filter output by regular expression", :argument => true if jruby? opt.on :J, "all-java", "Show all the aliases for methods from java (default is to show only prettiest)" end end def process obj = args.empty? ? target_self : target.eval(args.join(" ")) # exclude -q, -v and --grep because they don't specify what the user wants to see. has_opts = (opts.present?(:methods) || opts.present?(:instance-methods') || opts.present?(:ppp) || opts.present?(:globals) || opts.present?(:locals) || opts.present?(:constants) || opts.present?(:ivars)) show_methods = opts.present?(:methods) || opts.present?(:instance-methods') || opts.present?(:ppp) || !has_opts show_self_methods = (!has_opts && Module === obj) show_constants = opts.present?(:constants) || (!has_opts && Module === obj) show_ivars = opts.present?(:ivars) || !has_opts show_locals = opts.present?(:locals) || (!has_opts && args.empty?) grep_regex, grep = [Regexp.new(opts[:G] || "."), lambda{ |x| x.grep(grep_regex) }] raise Pry::CommandError, "-l does not make sense with a specified Object" if opts.present?(:locals) && !args.empty? raise Pry::CommandError, "-g does not make sense with a specified Object" if opts.present?(:globals) && !args.empty? raise Pry::CommandError, "-q does not make sense with -v" if opts.present?(:quiet) && opts.present?(:verbose) raise Pry::CommandError, "-M only makes sense with a Module or a Class" if opts.present?(:instance-methods') && !(Module === obj) raise Pry::CommandError, "-c only makes sense with a Module or a Class" if opts.present?(:constants) && !args.empty? && !(Module === obj) if opts.present?(:globals) output_section("global variables", grep[format_globals(target.eval("global_variables"))]) end if show_constants mod = Module === obj ? obj : Object constants = mod.constants constants -= (mod.ancestors - [mod]).map(&:constants).flatten unless opts.present?(:verbose) output_section("constants", grep[format_constants(mod, constants)]) end if show_methods # methods is a hash {Module/Class => [Pry::Methods]} methods = all_methods(obj).group_by(&:owner) # reverse the resolution order so that the most useful information appears right by the prompt resolution_order(obj).take_while(&below_ceiling(obj)).reverse.each do |klass| methods_here = format_methods((methods[klass] || []).select{ |m| m.name =~ grep_regex }) output_section "#{Pry::WrappedModule.new(klass).method_prefix}methods", methods_here end end if show_self_methods methods = all_methods(obj, true).select{ |m| m.owner == obj && m.name =~ grep_regex } output_section "#{Pry::WrappedModule.new(obj).method_prefix}methods", format_methods(methods) end if show_ivars klass = (Module === obj ? obj : obj.class) ivars = Pry::Method.safe_send(obj, :instance_variables) kvars = Pry::Method.safe_send(klass, :class_variables) output_section("instance variables", format_variables(:instance_var, ivars)) output_section("class variables", format_variables(:class_var, kvars)) end if show_locals output_section("locals", format_locals(grep[target.eval("local_variables")])) end end private # http://ruby.runpaint.org/globals, and running "puts global_variables.inspect". BUILTIN_GLOBALS = %w($" $$ $* $, $-0 $-F $-I $-K $-W $-a $-d $-i $-l $-p $-v $-w $. $/ $\\ $: $; $< $= $> $0 $ARGV $CONSOLE $DEBUG $DEFAULT_INPUT $DEFAULT_OUTPUT $FIELD_SEPARATOR $FILENAME $FS $IGNORECASE $INPUT_LINE_NUMBER $INPUT_RECORD_SEPARATOR $KCODE $LOADED_FEATURES $LOAD_PATH $NR $OFS $ORS $OUTPUT_FIELD_SEPARATOR $OUTPUT_RECORD_SEPARATOR $PID $PROCESS_ID $PROGRAM_NAME $RS $VERBOSE $deferr $defout $stderr $stdin $stdout) # $SAFE and $? are thread-local, the exception stuff only works in a rescue clause, # everything else is basically a local variable with a $ in its name. PSEUDO_GLOBALS = %w($! $' $& $` $@ $? $+ $_ $~ $1 $2 $3 $4 $5 $6 $7 $8 $9 $CHILD_STATUS $SAFE $ERROR_INFO $ERROR_POSITION $LAST_MATCH_INFO $LAST_PAREN_MATCH $LAST_READ_LINE $MATCH $POSTMATCH $PREMATCH) # Get all the methods that we'll want to output def all_methods(obj, instance_methods=false) methods = if instance_methods || opts.present?(:instance-methods') Pry::Method.all_from_class(obj) else Pry::Method.all_from_obj(obj) end if jruby? && !opts.present?(:J) methods = trim_jruby_aliases(methods) end methods.select{ |method| opts.present?(:ppp) || method.visibility == :public } end # JRuby creates lots of aliases for methods imported from java in an attempt to # make life easier for ruby programmers. # (e.g. getFooBar becomes get_foo_bar and foo_bar, and maybe foo_bar? if it # returns a Boolean). # The full transformations are in the assignAliases method of: # https://github.com/jruby/jruby/blob/master/src/org/jruby/javasupport/JavaClass.java # # This has the unfortunate side-effect of making the output of ls even more # incredibly verbose than it normally would be for these objects; and so we filter # out all but the nicest of these aliases here. # # TODO: This is a little bit vague, better heuristics could be used. # JRuby also has a lot of scala-specific logic, which we don't copy. # def trim_jruby_aliases(methods) grouped = methods.group_by do |m| m.name.sub(/\A(is|get|set)(?=[A-Z_])/, '').gsub(/[_?=]/, '').downcase end grouped.map do |key, values| values = values.sort_by do |m| rubbishness(m.name) end found = [] values.select do |x| (!found.any?{ |y| x == y }) && found << x end end.flatten(1) end # When removing jruby aliases, we want to keep the alias that is "least rubbish" # according to this metric. def rubbishness(name) name.each_char.map{ |x| case x when /[A-Z]/ 1 when '?', '=', '!' -2 else 0 end }.inject(&:+) + (name.size / 100.0) end def resolution_order(obj) opts.present?(:instance-methods') ? Pry::Method.instance_resolution_order(obj) : Pry::Method.resolution_order(obj) end # Get a lambda that can be used with .take_while to prevent over-eager # traversal of the Object's ancestry graph. def below_ceiling(obj) ceiling = if opts.present?(:quiet) [opts.present?(:instance-methods') ? obj.ancestors[1] : obj.class.ancestors[1]] + Pry.config.ls.ceiling elsif opts.present?(:verbose) [] else Pry.config.ls.ceiling.dup end lambda { |klass| !ceiling.include?(klass) } end # Format and colourise a list of methods. def format_methods(methods) methods.sort_by(&:name).map do |method| if method.name == 'method_missing' color(:method_missing, 'method_missing') elsif method.visibility == :private color(:private_method, method.name) elsif method.visibility == :protected color(:protected_method, method.name) else color(:public_method, method.name) end end end def format_variables(type, vars) vars.sort_by(&:downcase).map{ |var| color(type, var) } end def format_constants(mod, constants) constants.sort_by(&:downcase).map do |name| if const = (!mod.autoload?(name) && (mod.const_get(name) || true) rescue nil) if (const < Exception rescue false) color(:exception_constant, name) elsif (Module === mod.const_get(name) rescue false) color(:class_constant, name) else color(:constant, name) end else color(:unloaded_constant, name) end end end def format_globals(globals) globals.sort_by(&:downcase).map do |name| if PSEUDO_GLOBALS.include?(name) color(:pseudo_global, name) elsif BUILTIN_GLOBALS.include?(name) color(:builtin_global, name) else color(:global_var, name) end end end def format_locals(locals) locals.sort_by(&:downcase).map do |name| if _pry_.sticky_locals.include?(name.to_sym) color(:pry_var, name) else color(:local_var, name) end end end # Add a new section to the output. Outputs nothing if the section would be empty. def output_section(heading, body) return if body.compact.empty? output.puts "#{text.bold(color(:heading, heading))}: #{body.compact.join(Pry.config.ls.separator)}" end # Color output based on config.ls.*_color def color(type, str) text.send(Pry.config.ls.send(:#{type}_color"), str) end end end
- Cd =
Pry::CommandSet.new do create_command "cd" do group "Context" description "Move into a new context (object or scope)." <<-BANNER Usage: cd [OPTIONS] [--help] Move into new context (object or scope). As in unix shells use `cd ..` to go back and `cd /` to return to Pry top-level). Complex syntax (e.g cd ../@x/y) also supported. e.g: `cd @x` e.g: `cd .. e.g: `cd /` https://github.com/pry/pry/wiki/State-navigation#wiki-Changing_scope BANNER def process path = arg_string.split(/\//) stack = _pry_.binding_stack.dup # special case when we only get a single "/", return to root stack = [stack.first] if path.empty? path.each do |context| begin case context.chomp when "" stack = [stack.first] when "::" stack.push(TOPLEVEL_BINDING) when "." next when ".." unless stack.size == 1 stack.pop end else stack.push(Pry.binding_for(stack.last.eval(context))) end rescue RescuableException => e output.puts "Bad object path: #{arg_string.chomp}. Failed trying to resolve: #{context}" output.puts e.inspect return end end _pry_.binding_stack = stack end end end
- Gems =
Pry::CommandSet.new do create_command "gem-install", "Install a gem and refresh the gem cache.", :argument_required => true do |gem| <<-BANNER Usage: gem-install GEM_NAME Installs the given gem and refreshes the gem cache so that you can immediately 'require GEM_FILE' BANNER def setup require 'rubygems/dependency_installer' unless defined? Gem::DependencyInstaller end def process(gem) begin destination = File.writable?(Gem.dir) ? Gem.dir : Gem.user_dir installer = Gem::DependencyInstaller.new :install_dir => destination installer.install gem rescue Errno::EACCES raise CommandError, "Insufficient permissions to install `#{text.green gem}`." rescue Gem::GemNotFoundException raise CommandError, "Gem `#{text.green gem}` not found." else Gem.refresh output.puts "Gem `#{text.green gem}` installed." end end end create_command "gem-cd", "Change working directory to specified gem's directory.", :argument_required => true do |gem| <<-BANNER Usage: gem-cd GEM_NAME Change the current working directory to that in which the given gem is installed. BANNER def process(gem) specs = Gem::Specification.respond_to?(:each) ? Gem::Specification.find_all_by_name(gem) : Gem.source_index.find_name(gem) spec = specs.sort { |a,b| Gem::Version.new(b.version) <=> Gem::Version.new(a.version) }.first if spec Dir.chdir(spec.full_gem_path) output.puts(Dir.pwd) else raise CommandError, "Gem `#{gem}` not found." end end end create_command "gem-list", "List and search installed gems." do |pattern| <<-BANNER Usage: gem-list [REGEX] List all installed gems, when a regex is provided, limit the output to those that match the regex. BANNER def process(pattern=nil) pattern = Regexp.compile(pattern || '') gems = if Gem::Specification.respond_to?(:each) Gem::Specification.select{|spec| spec.name =~ pattern }.group_by(&:name) else Gem.source_index.gems.values.group_by(&:name).select { |gemname, specs| gemname =~ pattern } end gems.each do |gem, specs| specs.sort! do |a,b| Gem::Version.new(b.version) <=> Gem::Version.new(a.version) end versions = specs.each_with_index.map do |spec, index| index == 0 ? text.bright_green(spec.version.to_s) : text.green(spec.version.to_s) end output.puts "#{text.default gem} (#{versions.join ', '})" end end end end
- Help =
Pry::CommandSet.new do create_command "help" do |cmd| description "Show a list of commands. Type `help <foo>` for information about <foo>." <<-BANNER Usage: help [ COMMAND ] With no arguments, help lists all the available commands in the current command-set along with their description. When given a command name as an argument, shows the help for that command. BANNER # We only want to show commands that have descriptions, so that the # easter eggs don't show up. def visible_commands visible = {} commands.each do |key, command| visible[key] = command if command.description && !command.description.empty? end visible end # Get a hash of available commands grouped by the "group" name. def command_groups visible_commands.values.group_by(&:group) end def process if args.empty? display_index(command_groups) else display_search(args.first) end end # Display the index view, with headings and short descriptions per command. # # @param Hash[String => Array[Commands]] def display_index(groups) help_text = [] groups.keys.sort_by(&method(:group_sort_key)).each do |key| commands = groups[key].sort_by{ |command| command.[:listing].to_s } unless commands.empty? help_text << "#{text.bold(key)}\n" + commands.map do |command| " #{command.[:listing].to_s.ljust(18)} #{command.description}" end.join("\n") end end stagger_output(help_text.join("\n\n")) end # Display help for an individual command or group. # # @param String The string to search for. def display_search(search) if command = command_set.find_command_for_help(search) display_command(command) else groups = search_hash(search, command_groups) if groups.size > 0 display_index(groups) return end filtered = search_hash(search, visible_commands) raise CommandError, "No help found for '#{args.first}'" if filtered.empty? if filtered.size == 1 display_command(filtered.values.first) else display_index({"'#{search}' commands" => filtered.values}) end end end # Display help for an individual command. # # @param [Pry::Command] def display_command(command) stagger_output command.new.help end # Find a subset of a hash that matches the user's search term. # # If there's an exact match a Hash of one element will be returned, # otherwise a sub-Hash with every key that matches the search will # be returned. # # @param [String] the search term # @param [Hash] the hash to search def search_hash(search, hash) matching = {} hash.each_pair do |key, value| next unless key.is_a?(String) if normalize(key) == normalize(search) return {key => value} elsif normalize(key).start_with?(normalize(search)) matching[key] = value end end matching end # Clean search terms to make it easier to search group names # # @param String # @return String def normalize(key) key.downcase.gsub(/pry\W+/, '') end def group_sort_key(group_name) [%w(Help Context Editing Introspection Input_and_output Navigating_pry Gems Basic Commands).index(group_name.gsub(' ', '_')) || 99, group_name] end end end
- Hist =
Pry::CommandSet.new do create_command "hist", "Show and replay Readline history. Aliases: history" do group "Editing" <<-USAGE Usage: hist hist --head N hist --tail N hist --show START..END hist --grep PATTERN hist --clear hist --replay START..END hist --save [START..END] FILE USAGE def (opt) opt.on :H, :head, "Display the first N items.", :optional_argument => true, :as => Integer opt.on :T, :tail, "Display the last N items.", :optional_argument => true, :as => Integer opt.on :s, :show, "Show the given range of lines.", :optional_argument => true, :as => Range opt.on :G, :grep, "Show lines matching the given pattern.", :argument => true, :as => String opt.on :c, :clear, "Clear the current session's history." opt.on :r, :replay, "Replay a line or range of lines.", :argument => true, :as => Range opt.on :save, "Save history to a file.", :argument => true, :as => Range opt.on :e, :exclude-pry', "Exclude Pry commands from the history." opt.on :n, :no-numbers', "Omit line numbers." opt.on :f, :flood, "Do not use a pager to view text longer than one screen." end def process @history = Pry::Code(Pry.history.to_a) if opts.present?(:show) @history = @history.between(opts[:show]) end if opts.present?(:grep) @history = @history.grep(opts[:grep]) end @history = case when opts.present?(:head) @history.take_lines(1, opts[:head] || 10) when opts.present?(:tail) @history.take_lines(-(opts[:tail] || 10), opts[:tail] || 10) when opts.present?(:show) @history.between(opts[:show]) else @history end if opts.present?(:exclude-pry') @history = @history.select { |l, ln| !command_set.valid_command?(l) } end if opts.present?(:save) process_save elsif opts.present?(:clear) process_clear elsif opts.present?(:replay) process_replay else process_display end end def process_display unless opts.present?(:no-numbers') @history = @history.with_line_numbers end render_output(@history, opts) end def process_save case opts[:save] when Range @history = @history.between(opts[:save]) unless args.first raise CommandError, "Must provide a file name." end file_name = File.(args.first) when String file_name = File.(opts[:save]) end output.puts "Saving history in #{file_name}..." File.open(file_name, 'w') { |f| f.write(@history.raw) } output.puts "History saved." end def process_clear Pry.history.clear output.puts "History cleared." end def process_replay @history = @history.between(opts[:r]) _pry_.input_stack.push _pry_.input _pry_.input = StringIO.new(@history.raw) # eval_string << "#{@history.raw}\n" # run "show-input" unless _pry_.complete_expression?(eval_string) end end alias_command "history", "hist" end
- Misc =
Pry::CommandSet.new do command "toggle-color", "Toggle syntax highlighting." do Pry.color = !Pry.color output.puts "Syntax highlighting #{Pry.color ? "on" : "off"}" end command "simple-prompt", "Toggle the simple prompt." do case _pry_.prompt when Pry::SIMPLE_PROMPT _pry_.pop_prompt else _pry_.push_prompt Pry::SIMPLE_PROMPT end end command "pry-version", "Show Pry version." do output.puts "Pry version: #{Pry::VERSION} on Ruby #{RUBY_VERSION}." end command "reload-method", "Reload the source file that contains the specified method" do |meth_name| meth = get_method_or_raise(meth_name, target, {}, :omit_help) if meth.source_type == :c raise CommandError, "Can't reload a C method." elsif meth.dynamically_defined? raise CommandError, "Can't reload an eval method." else file_name = meth.source_file load file_name output.puts "Reloaded #{file_name}." end end end
- Editing =
Pry::CommandSet.new do import Hist create_command "!", "Clear the input buffer. Useful if the parsing process goes wrong and you get stuck in the read loop.", :use_prefix => false do def process output.puts "Input buffer cleared!" eval_string.replace("") end end create_command "show-input", "Show the contents of the input buffer for the current multi-line expression." do def process output.puts Code.new(eval_string).with_line_numbers end end create_command "edit" do description "Invoke the default editor on a file." <<-BANNER Usage: edit [--no-reload|--reload] [--line LINE] [--temp|--ex|FILE[:LINE]|--in N] Open a text editor. When no FILE is given, edits the pry input buffer. Ensure Pry.config.editor is set to your editor of choice. e.g: `edit sample.rb` e.g: `edit sample.rb --line 105` e.g: `edit --ex` https://github.com/pry/pry/wiki/Editor-integration#wiki-Edit_command BANNER def (opt) opt.on :e, :ex, "Open the file that raised the most recent exception (_ex_.file)", :optional_argument => true, :as => Integer opt.on :i, :in, "Open a temporary file containing the Nth line of _in_. N may be a range.", :optional_argument => true, :as => Range, :default => -1..-1 opt.on :t, :temp, "Open an empty temporary file" opt.on :l, :line, "Jump to this line in the opened file", :argument => true, :as => Integer opt.on :n, :no-reload", "Don't automatically reload the edited code" opt.on :c, :current", "Open the current __FILE__ and at __LINE__ (as returned by `whereami`)." opt.on :r, :reload, "Reload the edited code immediately (default for ruby files)" end def process if [opts.present?(:ex), opts.present?(:temp), opts.present?(:in), !args.empty?].count(true) > 1 raise CommandError, "Only one of --ex, --temp, --in and FILE may be specified." end if !opts.present?(:ex) && !opts.present?(:current) && args.empty? # edit of local code, eval'd within pry. process_local_edit else # edit of remote code, eval'd at top-level process_remote_edit end end def process_i case opts[:i] when Range (_pry_.input_array[opts[:i]] || []).join when Fixnum _pry_.input_array[opts[:i]] || "" else return output.puts "Not a valid range: #{opts[:i]}" end end def process_local_edit content = case when opts.present?(:temp) "" when opts.present?(:in) process_i when eval_string.strip != "" eval_string else _pry_.input_array.reverse_each.find{ |x| x && x.strip != "" } || "" end line = content.lines.count temp_file do |f| f.puts(content) f.flush reload = !opts.present?(:no-reload') && !Pry.config.disable_auto_reload f.close(false) invoke_editor(f.path, line, reload) if reload silence_warnings do eval_string.replace(File.read(f.path)) end end end end def process_remote_edit if opts.present?(:ex) if _pry_.last_exception.nil? raise CommandError, "No exception found." end ex = _pry_.last_exception bt_index = opts[:ex].to_i ex_file, ex_line = ex.bt_source_location_for(bt_index) if ex_file && RbxPath.is_core_path?(ex_file) file_name = RbxPath.convert_path_to_full(ex_file) else file_name = ex_file end line = ex_line if file_name.nil? raise CommandError, "Exception has no associated file." end if Pry.eval_path == file_name raise CommandError, "Cannot edit exceptions raised in REPL." end elsif opts.present?(:current) file_name = target.eval("__FILE__") line = target.eval("__LINE__") else # break up into file:line file_name = File.(args.first) line = file_name.sub!(/:(\d+)$/, "") ? $1.to_i : 1 end if not_a_real_file?(file_name) raise CommandError, "#{file_name} is not a valid file name, cannot edit!" end line = opts[:l].to_i if opts.present?(:line) reload = opts.present?(:reload) || ((opts.present?(:ex) || file_name.end_with?(".rb")) && !opts.present?(:no-reload')) && !Pry.config.disable_auto_reload invoke_editor(file_name, line, reload) set_file_and_dir_locals(file_name) if reload silence_warnings do TOPLEVEL_BINDING.eval(File.read(file_name), file_name) end end end end create_command "edit-method" do description "Edit the source code for a method." <<-BANNER Usage: edit-method [OPTIONS] [METH] Edit the method METH in an editor. Ensure Pry.config.editor is set to your editor of choice. e.g: `edit-method hello_method` e.g: `edit-method Pry#rep` e.g: `edit-method` https://github.com/pry/pry/wiki/Editor-integration#wiki-Edit_method BANNER :shellwords => false def (opt) (opt) opt.on :n, "no-reload", "Do not automatically reload the method's file after editing." opt.on "no-jump", "Do not fast forward editor to first line of method." opt.on :p, :patch, "Instead of editing the method's file, try to edit in a tempfile and apply as a monkey patch." end def process if !Pry.config.editor raise CommandError, "No editor set!\nEnsure that #{text.bold("Pry.config.editor")} is set to your editor of choice." end begin @method = method_object rescue NonMethodContextError => err end if opts.present?(:patch) || (@method && @method.dynamically_defined?) if err raise err # can't patch a non-method end process_patch else if err && !File.exist?(target.eval('__FILE__')) raise err # can't edit a non-file end process_file end end def process_patch lines = @method.source.lines.to_a lines[0] = definition_line_for_owner(lines[0]) temp_file do |f| f.puts lines.join f.flush f.close(false) invoke_editor(f.path, 0, true) if @method.alias? with_method_transaction(original_name, @method.owner) do Pry.new(:input => StringIO.new(File.read(f.path))).rep(@method.owner) Pry.binding_for(@method.owner).eval("alias #{@method.name} #{original_name}") end else Pry.new(:input => StringIO.new(File.read(f.path))).rep(@method.owner) end end end def process_file file, line = extract_file_and_line reload = !opts.present?(:no-reload') && !Pry.config.disable_auto_reload invoke_editor(file, opts["no-jump"] ? 0 : line, reload) silence_warnings do load file if reload end end protected def extract_file_and_line if @method if @method.source_type == :c raise CommandError, "Can't edit a C method." else [@method.source_file, @method.source_line] end else [target.eval('__FILE__'), target.eval('__LINE__')] end end def with_method_transaction(meth_name, target=TOPLEVEL_BINDING) target = Pry.binding_for(target) temp_name = "__pry_#{meth_name}__" target.eval("alias #{temp_name} #{meth_name}") yield target.eval("alias #{meth_name} #{temp_name}") ensure target.eval("undef #{temp_name}") rescue nil end # The original name of the method, if it's not present raise an error telling # the user why we don't work. # def original_name @method.original_name or raise CommandError, "Pry can only patch methods created with the `def` keyword." end # Update the definition line so that it can be eval'd directly on the Method's # owner instead of from the original context. # # In particular this takes `def self.foo` and turns it into `def foo` so that we # don't end up creating the method on the singleton class of the singleton class # by accident. # # This is necessarily done by String manipulation because we can't find out what # syntax is needed for the argument list by ruby-level introspection. # # @param String The original definition line. e.g. def self.foo(bar, baz=1) # @return String The new definition line. e.g. def foo(bar, baz=1) # def definition_line_for_owner(line) if line =~ /^def (?:.*?\.)?#{Regexp.escape(original_name)}(?=[\(\s;]|$)/ "def #{original_name}#{$'}" else raise CommandError, "Could not find original `def #{original_name}` line to patch." end end end create_command(/amend-line(?: (-?\d+)(?:\.\.(-?\d+))?)?/) do description "Amend a line of input in multi-line mode." :interpolate => false, :listing => "-line" <<-'BANNER' Amend a line of input in multi-line mode. `amend-line N`, where the N in `amend-line N` represents line to replace. Can also specify a range of lines using `amend-line N..M` syntax. Passing '!' as replacement content deletes the line(s) instead. e.g amend-line 1 puts 'hello world! # replace line 1' e.g amend-line 1..4 ! # delete lines 1..4 e.g amend-line 3 >puts 'goodbye' # insert before line 3 e.g amend-line puts 'hello again' # no line number modifies immediately preceding line BANNER def process start_line_number, end_line_number, replacement_line = *args if eval_string.empty? raise CommandError, "No input to amend." end replacement_line = "" if !replacement_line input_array = eval_string.each_line.to_a end_line_number = start_line_number.to_i if !end_line_number line_range = start_line_number ? (one_index_number(start_line_number.to_i)..one_index_number(end_line_number.to_i)) : input_array.size - 1 # delete selected lines if replacement line is '!' if arg_string == "!" input_array.slice!(line_range) elsif arg_string.start_with?(">") insert_slot = Array(line_range).first input_array.insert(insert_slot, arg_string[1..-1] + "\n") else input_array[line_range] = arg_string + "\n" end eval_string.replace input_array.join run "show-input" end end create_command "play" do include Helpers::DocumentationHelpers description "Play back a string variable or a method or a file as input." <<-BANNER Usage: play [OPTIONS] [--help] The play command enables you to replay code from files and methods as if they were entered directly in the Pry REPL. Default action (no options) is to play the provided string variable e.g: `play -i 20 --lines 1..3` e.g: `play -m Pry#repl --lines 1..-1` e.g: `play -f Rakefile --lines 5` https://github.com/pry/pry/wiki/User-Input#wiki-Play BANNER attr_accessor :content def setup self.content = "" end def (opt) opt.on :m, :method, "Play a method's source.", :argument => true do |meth_name| meth = get_method_or_raise(meth_name, target, {}) self.content << meth.source end opt.on :d, :doc, "Play 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, :ruby) end end opt.on :c, :command, "Play a command's source.", :argument => true do |command_name| command = find_command(command_name) block = Pry::Method.new(command.block) self.content << block.source end opt.on :f, :file, "Play a file.", :argument => true do |file| self.content << File.read(File.(file)) end opt.on :l, :lines, "Only play a subset of lines.", :optional_argument => true, :as => Range, :default => 1..-1 opt.on :i, :in, "Play entries from Pry's input expression history. Takes an index or range. Note this can only replay pure Ruby code, not Pry commands.", :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 :o, "open", 'When used with the -m switch, it plays the entire method except the last line, leaving the method definition "open". `amend-line` can then be used to modify the method.' end def process perform_play run "show-input" unless Pry::Code.complete_expression?(eval_string) end def process_non_opt args.each do |arg| begin self.content << target.eval(arg) rescue Pry::RescuableException raise CommandError, "Prblem when evaling #{arg}." end end end def perform_play process_non_opt if opts.present?(:lines) self.content = restrict_to_lines(self.content, opts[:l]) end if opts.present?(:open) self.content = restrict_to_lines(self.content, 1..-2) end eval_string << self.content end end end
- Context =
Pry::CommandSet.new do import Ls import Cd import FindMethod create_command "whereami" do description "Show code surrounding the current context." <<-BANNER Usage: whereami [OPTIONS] BANNER def setup @method = Pry::Method.from_binding(target) end def process if show_method? file = @method.source_file start = @method.source_range.begin finish = @method.source_range.end marker = target.eval("__LINE__") else file = target.eval("__FILE__") start = target.eval("__LINE__") finish = (args.first && args.first.to_i) || 5 marker = start end if invalid_file?(file) raise Pry::CommandError, "Cannot find local context. Did you use binding.pry?" end # TODO: refactor. if show_method? code = Pry::Code.from_file(file).between(start, finish) else code = Pry::Code.from_file(file).around(start, finish) end desc = (@method && @method.name_with_owner) || "" if !code.empty? set_file_and_dir_locals(file) output.puts "\n#{text.bold('From:')} #{file} @ line #{start} #{desc}:\n\n" output.puts code.with_line_numbers.with_marker(marker) output.puts end end private def show_method? args.empty? && @method && !@method.instance_of?(Pry::Method::Disowned) && @method.source_range.count < 20 end def invalid_file?(file) file != Pry.eval_path && (file =~ /(\(.*\))|<.*>/ || file == "" || file == "-e") end end create_command "pry-backtrace", "Show the backtrace for the Pry session." do <<-BANNER Usage: pry-backtrace [OPTIONS] [--help] Show the backtrace for the position in the code where Pry was started. This can be used to infer the behavior of the program immediately before it entered Pry, just like the backtrace property of an exception. (NOTE: if you are looking for the backtrace of the most recent exception raised, just type: `_ex_.backtrace` instead, see https://github.com/pry/pry/wiki/Special-Locals) e.g: pry-backtrace BANNER def process output.puts "\n#{text.bold('Backtrace:')}\n--\n" stagger_output _pry_.backtrace.join("\n") end end command "reset", "Reset the REPL to a clean state." do output.puts "Pry reset." exec "pry" end create_command(/wtf([?!]*)/, "Show the backtrace of the most recent exception") do :listing => 'wtf?' <<-BANNER Show's a few lines of the backtrace of the most recent exception (also available as _ex_.backtrace). If you want to see more lines, add more question marks or exclamation marks: e.g. pry(main)> wtf? pry(main)> wtf?!???!?!? To see the entire backtrace, pass the -v/--verbose flag: e.g. pry(main)> wtf -v BANNER def (opt) opt.on(:v, :verbose, "Show the full backtrace.") end def process raise Pry::CommandError, "No most-recent exception" unless _pry_.last_exception output.puts "#{text.bold('Exception:')} #{_pry_.last_exception.class}: #{_pry_.last_exception}\n--" if opts.verbose? output.puts Code.new(_pry_.last_exception.backtrace, 0, :text).with_line_numbers.to_s else output.puts Code.new(_pry_.last_exception.backtrace.first([captures[0].size, 0.5].max * 10), 0, :text).with_line_numbers.to_s end end end # N.B. using a regular expresion here so that "raise-up 'foo'" does the right thing. create_command(/raise-up(!?\b.*)/, :listing => 'raise-up') do description "Raise an exception out of the current pry instance." <<-BANNER Raise up, like exit, allows you to quit pry. Instead of returning a value however, it raises an exception. If you don't provide the exception to be raised, it will use the most recent exception (in pry _ex_). e.g. `raise-up "get-me-out-of-here"` is equivalent to: `raise "get-me-out-of-here" raise-up` When called as raise-up! (with an exclamation mark), this command raises the exception through any nested prys you have created by "cd"ing into objects. BANNER def process return stagger_output help if captures[0] =~ /(-h|--help)\b/ # Handle 'raise-up', 'raise-up "foo"', 'raise-up RuntimeError, 'farble' in a rubyesque manner target.eval("_pry_.raise_up#{captures[0]}") end end end
- Commands =
Pry::CommandSet.new do create_command "import-set", "Import a command set" do group "Commands" def process(command_set_name) raise CommandError, "Provide a command set name" if command_set.nil? set = target.eval(arg_string) _pry_.commands.import set end end create_command "install-command", "Install a disabled command." do |name| group 'Commands' <<-BANNER Usage: install-command COMMAND Installs the gems necessary to run the given COMMAND. You will generally not need to run this unless told to by an error message. BANNER def process(name) require 'rubygems/dependency_installer' unless defined? Gem::DependencyInstaller command = find_command(name) if command_dependencies_met?(command.) output.puts "Dependencies for #{command.name} are met. Nothing to do." return end output.puts "Attempting to install `#{name}` command..." gems_to_install = Array(command.[:requires_gem]) gems_to_install.each do |g| next if gem_installed?(g) output.puts "Installing `#{g}` gem..." begin Gem::DependencyInstaller.new.install(g) rescue Gem::GemNotFoundException raise CommandError, "Required Gem: `#{g}` not found. Aborting command installation." end end Gem.refresh gems_to_install.each do |g| begin require g rescue LoadError raise CommandError, "Required Gem: `#{g}` installed but not found?!. Aborting command installation." end end output.puts "Installation of `#{name}` successful! Type `help #{name}` for information" end end end
- EasterEggs =
Pry::CommandSet.new do command "nyan-cat", "", :requires_gem => ["nyancat"] do run ".nyancat" end command(/!s\/(.*?)\/(.*?)/, "") do |source, dest| eval_string.gsub!(/#{source}/) { dest } run "show-input" end command "get-naked", "" do text = %{ -- We dont have to take our clothes off to have a good time. We could dance & party all night And drink some cherry wine. -- Jermaine Stewart } output.puts text text end command "east-coker", "" do text = %{ -- Now the light falls Across the open field, leaving the deep lane Shuttered with branches, dark in the afternoon, Where you lean against a bank while a van passes, And the deep lane insists on the direction Into the village, in the electric heat Hypnotised. In a warm haze the sultry light Is absorbed, not refracted, by grey stone. The dahlias sleep in the empty silence. Wait for the early owl. -- T.S Eliot } output.puts text text end command "cohen-poem", "" do text = %{ -- When this American woman, whose thighs are bound in casual red cloth, comes thundering past my sitting place like a forest-burning Mongol tribe, the city is ravished and brittle buildings of a hundred years splash into the street; and my eyes are burnt for the embroidered Chinese girls, already old, and so small between the thin pines on these enormous landscapes, that if you turn your head they are lost for hours. -- Leonard Cohen } output.puts text text end command "test-ansi", "" do prev_color = Pry.color Pry.color = true picture = unindent <<-'EOS'.gsub(/[[:alpha:]!]/) { |s| text.red(s) } ____ _______________________ / \ | A W G | / O O \ | N I O N ! | | | | S S R I ! | \ \__/ / __| I K ! | \____/ \________________________| EOS if defined?(Win32::Console) move_up = proc { |n| "\e[#{n}F" } else move_up = proc { |n| "\e[#{n}A\e[0G" } end output.puts "\n" * 6 output.puts picture.lines.map(&:chomp).reverse.join(move_up[1]) output.puts "\n" * 6 output.puts "** ENV['TERM'] is #{ENV['TERM']} **\n\n" Pry.color = prev_color end end
- FindMethod =
Pry::CommandSet.new do create_command "find-method" do extend Helpers::BaseHelpers group "Context" :requires_gem => "ruby18_source_location" if mri_18? description "Recursively search for a method within a Class/Module or the current namespace. find-method [-n | -c] METHOD [NAMESPACE]" <<-BANNER Usage: find-method [-n | -c] METHOD [NAMESPACE] Recursively search for a method within a Class/Module or the current namespace. Use the `-n` switch (the default) to search for methods whose name matches the given regex. Use the `-c` switch to search for methods that contain the given code. e.g find-method re Pry # find all methods whose name match /re/ inside the Pry namespace. Matches Pry#repl, etc. e.g find-method -c 'output.puts' Pry # find all methods that contain the code: output.puts inside the Pry namepsace. BANNER def setup require 'ruby18_source_location' if mri_18? end def (opti) opti.on :n, :name, "Search for a method by name" opti.on :c, :content, "Search for a method based on content in Regex form" end def process return if args.size < 1 pattern = ::Regexp.new args[0] if args[1] klass = target.eval(args[1]) if !klass.is_a?(Module) klass = klass.class end else klass = (target_self.is_a?(Module)) ? target_self : target_self.class end matches = if opts.content? content_search(pattern, klass) else name_search(pattern, klass) end if matches.empty? output.puts text.bold("No Methods Matched") else print_matches(matches, pattern) end end private # pretty-print a list of matching methods. # # @param Array[Method] def print_matches(matches, pattern) grouped = matches.group_by(&:owner) order = grouped.keys.sort_by{ |x| x.name || x.to_s } order.each do |klass| output.puts text.bold(klass.name) grouped[klass].each do |method| header = method.name_with_owner extra = if opts.content? header += ": " colorize_code((method.source.split(/\n/).select {|x| x =~ pattern }).join("\n#{' ' * header.length}")) else "" end output.puts header + extra end end end # Run the given block against every constant in the provided namespace. # # @param Module The namespace in which to start the search. # @param Hash[Module,Boolean] The namespaces we've already visited (private) # @yieldparam klazz Each class/module in the namespace. # def recurse_namespace(klass, done={}, &block) return if !(Module === klass) || done[klass] done[klass] = true yield klass klass.constants.each do |name| next if klass.autoload?(name) begin const = klass.const_get(name) rescue RescuableException # constant loading is an inexact science at the best of times, # this often happens when a constant was .autoload? but someone # tried to load it. It's now not .autoload? but will still raise # a NameError when you access it. else recurse_namespace(const, done, &block) end end end # Gather all the methods in a namespace that pass the given block. # # @param Module The namespace in which to search. # @yieldparam Method The method to test # @yieldreturn Boolean # @return Array[Method] # def search_all_methods(namespace) done = Hash.new{ |h,k| h[k] = {} } matches = [] recurse_namespace(namespace) do |klass| (Pry::Method.all_from_class(klass) + Pry::Method.all_from_obj(klass)).each do |method| next if done[method.owner][method.name] done[method.owner][method.name] = true matches << method if yield method end end matches end # Search for all methods with a name that matches the given regex # within a namespace. # # @param Regex The regex to search for # @param Module The namespace to search # @return Array[Method] # def name_search(regex, namespace) search_all_methods(namespace) do |meth| meth.name =~ regex end end # Search for all methods who's implementation matches the given regex # within a namespace. # # @param Regex The regex to search for # @param Module The namespace to search # @return Array[Method] # def content_search(regex, namespace) search_all_methods(namespace) do |meth| begin meth.source =~ regex rescue RescuableException false end end end end end
- Introspection =
Pry::CommandSet.new do create_command "show-doc", "Show the documentation for a method or class. Aliases: \?", :shellwords => false do include ModuleIntrospectionHelpers include Helpers::DocumentationHelpers extend Helpers::BaseHelpers <<-BANNER Usage: show-doc [OPTIONS] [METH] Aliases: ? Show the documentation for a method or class. Tries instance methods first and then methods by default. e.g show-doc hello_method # docs for hello_method e.g show-doc Pry # docs for Pry class e.g show-doc Pry -a # docs for all definitions of Pry class (all monkey patches) BANNER :requires_gem => "ruby18_source_location" if mri_18? def setup require 'ruby18_source_location' if mri_18? end def (opt) (opt) opt.on :l, "line-numbers", "Show line numbers." opt.on :b, "base-one", "Show line numbers but start numbering at 1 (useful for `amend-line` and `play` commands)." opt.on :f, :flood, "Do not use a pager to view text longer than one screen." opt.on :a, :all, "Show docs for all definitions and monkeypatches of the module/class" end def process_module if opts.present?(:all) all_modules else normal_module end end def normal_module mod = module_object # source_file reveals the underlying .c file in case of core # classes on MRI. This is different to source_location, which # will return nil. if mod.yard_docs? file_name, line = mod.source_file, nil else file_name, line = mod.source_location end if mod.doc.empty? output.puts "No documentation found." "" else set_file_and_dir_locals(file_name) if !mod.yard_docs? doc = "" doc << mod.doc doc = Code.new(doc, module_start_line(mod), :text). with_line_numbers(use_line_numbers?).to_s doc.insert(0, "\n#{Pry::Helpers::Text.bold('From:')} #{file_name} @ line #{line ? line : "N/A"}:\n\n") end end def all_modules mod = module_object doc = "" doc << "Found #{mod.number_of_candidates} candidates for `#{mod.name}` definition:\n" mod.number_of_candidates.times do |v| begin doc << "\nCandidate #{v+1}/#{mod.number_of_candidates}: #{mod.source_file_for_candidate(v)} @ #{mod.source_line_for_candidate(v)}:\n\n" dc = mod.doc_for_candidate(v) doc << (dc.empty? ? "No documentation found.\n" : dc) rescue Pry::RescuableException next end end doc end def process_method meth = method_object raise Pry::CommandError, "No documentation found." if meth.doc.nil? || meth.doc.empty? doc = process_comment_markup(meth.doc, meth.source_type) output.puts make_header(meth, doc) output.puts "#{text.bold("Owner:")} #{meth.owner || "N/A"}" output.puts "#{text.bold("Visibility:")} #{meth.visibility}" output.puts "#{text.bold("Signature:")} #{meth.signature}" output.puts if use_line_numbers? doc = Code.new(doc, start_line, :text). with_line_numbers(true).to_s end doc end def module_start_line(mod, candidate=0) if opts.present?(:base-one') 1 else if mod.source_line_for_candidate(candidate) mod.source_line_for_candidate(candidate) - mod.doc_for_candidate(candidate).lines.count else 1 end end end def start_line if opts.present?(:base-one') 1 else (method_object.source_line - method_object.doc.lines.count) || 1 end end end alias_command "?", "show-doc" create_command "stat", "View method information and set _file_ and _dir_ locals.", :shellwords => false do <<-BANNER Usage: stat [OPTIONS] [METH] Show method information for method METH and set _file_ and _dir_ locals. e.g: stat hello_method BANNER def (opt) (opt) end def process meth = method_object output.puts unindent <<-EOS Method Information: -- Name: #{meth.name} Owner: #{meth.owner ? meth.owner : "Unknown"} Visibility: #{meth.visibility} Type: #{meth.is_a?(::Method) ? "Bound" : "Unbound"} Arity: #{meth.arity} Method Signature: #{meth.signature} Source Location: #{meth.source_location ? meth.source_location.join(":") : "Not found."} EOS end end create_command "show-source" do include ModuleIntrospectionHelpers extend Helpers::BaseHelpers description "Show the source for a method or class. Aliases: $, show-method" <<-BANNER Usage: show-source [OPTIONS] [METH|CLASS] Aliases: $, show-method Show the source for a method or class. Tries instance methods first and then methods by default. e.g: `show-source hello_method` e.g: `show-source -m hello_method` e.g: `show-source Pry#rep` # source for Pry#rep method e.g: `show-source Pry` # source for Pry class e.g: `show-source Pry -a` # source for all Pry class definitions (all monkey patches) https://github.com/pry/pry/wiki/Source-browsing#wiki-Show_method BANNER :shellwords => false :requires_gem => "ruby18_source_location" if mri_18? def setup require 'ruby18_source_location' if mri_18? end def (opt) (opt) opt.on :l, "line-numbers", "Show line numbers." opt.on :b, "base-one", "Show line numbers but start numbering at 1 (useful for `amend-line` and `play` commands)." opt.on :f, :flood, "Do not use a pager to view text longer than one screen." opt.on :a, :all, "Show source for all definitions and monkeypatches of the module/class" end def process_method raise CommandError, "Could not find method source" unless method_object.source code = "" code << make_header(method_object) code << "#{text.bold("Owner:")} #{method_object.owner || "N/A"}\n" code << "#{text.bold("Visibility:")} #{method_object.visibility}\n" code << "\n" code << Code.from_method(method_object, start_line). with_line_numbers(use_line_numbers?).to_s end def process_module if opts.present?(:all) all_modules else normal_module end end def normal_module mod = module_object file_name, line = mod.source_location set_file_and_dir_locals(file_name) code = Code.from_module(mod, module_start_line(mod)).with_line_numbers(use_line_numbers?).to_s result = "" result << "\n#{Pry::Helpers::Text.bold('From:')} #{file_name} @ line #{line}:\n" result << "#{Pry::Helpers::Text.bold('Number of lines:')} #{code.lines.count}\n\n" result << code end def all_modules mod = module_object result = "" result << "Found #{mod.number_of_candidates} candidates for `#{mod.name}` definition:\n" mod.number_of_candidates.times do |v| begin code = Code.new(mod.source_for_candidate(v), module_start_line(mod, v)).with_line_numbers(use_line_numbers?).to_s result << "\nCandidate #{v+1}/#{mod.number_of_candidates}: #{mod.source_file_for_candidate(v)} @ line #{mod.source_line_for_candidate(v)}:\n" result << "Number of lines: #{code.lines.count}\n\n" result << code rescue Pry::RescuableException next end end result end def use_line_numbers? opts.present?(:b) || opts.present?(:l) end def start_line if opts.present?(:base-one') 1 else method_object.source_line || 1 end end end alias_command "show-method", "show-source" alias_command "$", "show-source" command "show-command", "Show the source for CMD." do |*args| target = target() opts = Slop.parse!(args) do |opt| opt. unindent <<-USAGE Usage: show-command [OPTIONS] [CMD] Show the source for command CMD. e.g: show-command show-method USAGE opt.on :l, "line-numbers", "Show line numbers." opt.on :f, :flood, "Do not use a pager to view text longer than one screen." opt.on :h, :help, "This message." do output.puts opt.help end end next if opts.present?(:help) command_name = args.shift if !command_name raise CommandError, "You must provide a command name." end if find_command(command_name) block = Pry::Method.new(find_command(command_name).block) next unless block.source set_file_and_dir_locals(block.source_file) output.puts make_header(block) output.puts code = Code.from_method(block).with_line_numbers(opts.present?(:line-numbers')).to_s render_output(code, opts) else raise CommandError, "No such command: #{command_name}." end end create_command "ri", "View ri documentation. e.g `ri Array#each`" do <<-BANNER Usage: ri [spec] e.g. ri Array#each Relies on the rdoc gem being installed. See also: show-doc. BANNER def process(spec) # Lazily load RI require 'rdoc/ri/driver' unless defined? RDoc::RI::PryDriver # Subclass RI so that it formats its output nicely, and uses `lesspipe`. subclass = Class.new(RDoc::RI::Driver) # the hard way. subclass.class_eval do def page Pry::Helpers::BaseHelpers.lesspipe {|less| yield less} end def formatter(io) if @formatter_klass then @formatter_klass.new else RDoc::Markup::ToAnsi.new end end end RDoc::RI.const_set :PryDriver, subclass # hook it up! end # Spin-up an RI insance. ri = RDoc::RI::PryDriver.new :use_stdout => true, :interactive => false begin ri.display_names [spec] # Get the documentation (finally!) rescue RDoc::RI::Driver::NotFoundError => e output.puts "error: '#{e.name}' not found" end end end end
Pry::CommandSet.new do command "switch-to", "Start a new sub-session on a binding in the current stack (numbered by nesting)." do |selection| selection = selection.to_i if selection < 0 || selection > _pry_.binding_stack.size - 1 raise CommandError, "Invalid binding index #{selection} - use `nesting` command to view valid indices." else Pry.start(_pry_.binding_stack[selection]) end end command "nesting", "Show nesting information." do output.puts "Nesting status:" output.puts "--" _pry_.binding_stack.each_with_index do |obj, level| if level == 0 output.puts "#{level}. #{Pry.view_clip(obj.eval('self'))} (Pry top level)" else output.puts "#{level}. #{Pry.view_clip(obj.eval('self'))}" end end end command "jump-to", "Jump to a binding further up the stack, popping all bindings below." do |break_level| break_level = break_level.to_i nesting_level = _pry_.binding_stack.size - 1 case break_level when nesting_level output.puts "Already at nesting level #{nesting_level}" when (0...nesting_level) _pry_.binding_stack.slice!(break_level + 1, _pry_.binding_stack.size) else max_nest_level = nesting_level - 1 output.puts "Invalid nest level. Must be between 0 and #{max_nest_level}. Got #{break_level}." end end command "exit-all", "End the current Pry session (popping all bindings) and returning to caller. Accepts optional return value. Aliases: !!@" do # calculate user-given value exit_value = target.eval(arg_string) # clear the binding stack _pry_.binding_stack.clear # break out of the repl loop throw(:breakout, exit_value) end alias_command "!!@", "exit-all" create_command "exit" do description "Pop the previous binding (does NOT exit program). Aliases: quit" <<-BANNER Usage: exit [OPTIONS] [--help] Aliases: quit It can be useful to exit a context with a user-provided value. For instance an exit value can be used to determine program flow. e.g: `exit "pry this"` e.g: `exit` https://github.com/pry/pry/wiki/State-navigation#wiki-Exit_with_value BANNER ( :keep_retval => true ) def process if _pry_.binding_stack.one? _pry_.run_command "exit-all #{arg_string}" else # otherwise just pop a binding and return user supplied value process_pop_and_return end end def process_pop_and_return popped_object = _pry_.binding_stack.pop.eval('self') # return a user-specified value if given otherwise return the object return target.eval(arg_string) unless arg_string.empty? popped_object end end alias_command "quit", "exit" command "exit-program", "End the current program. Aliases: quit-program, !!!" do Pry.save_history if Pry.config.history.should_save Kernel.exit target.eval(arg_string).to_i end alias_command "quit-program", "exit-program" alias_command "!!!", "exit-program" command "!pry", "Start a Pry session on current self; this even works mid multi-line expression." do target.pry end end
- 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.(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" create_command "gist", "Gist a method or expression history to github.", :requires_gem => "gist" do include Pry::Helpers::DocumentationHelpers <<-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 :shellwords => false attr_accessor :content attr_accessor :code_type def setup require 'gist' self.content = "" self.code_type = :ruby end def (opt) 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 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) 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 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 opt.on :f, :file, "Gist a file.", :argument => true do |file| self.content << File.read(File.(file)) << "\n" end opt.on :o, :out, "Gist 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 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 opt.on :i, :in, "Gist entries from Pry's input expression history. Takes an index or range.", :optional_argument => true, :as => Range, :default => -5..-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 Gist.copy(self.content) output.puts "Copied content to clipboard!" end def perform_gist type_map = { :ruby => "rb", :c => "c", :plain => "plain" } # prevent Gist from exiting the session on error begin extname = opts.present?(:file) ? ".#{gist_file_extension(opts[:f])}" : ".#{type_map[self.code_type]}" if opts.present?(:lines) self.content = restrict_to_lines(content, opts[:l]) end link = Gist.write([:extension => extname, :input => self.content], !opts[:p]) rescue SystemExit end if link Gist.copy(link) output.puts "Gist created at #{link} 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 end alias_command "clipit", "gist --clip" create_command "save-file", "Export to a file using content from the REPL." do <<-USAGE Usage: save-file [OPTIONS] [FILE] 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 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 (opt) 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) block = Pry::Method.new(command.block) self.content << block.source end opt.on :f, :file, "Save a file.", :argument => true do |file| self.content << File.read(File.(file)) end 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 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.(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 <<-USAGE Usage: cat FILE cat --ex [STACK_INDEX] cat --in [INPUT_INDEX_OR_RANGE] 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. 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 def (opt) 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 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 opt.on :l, :line-numbers', "Show line numbers." opt.on :t, :type, "The file type for syntax highlighting (e.g., 'ruby' or 'python').", :argument => true, :as => Symbol 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| code.code_type = opts[:type] || :ruby code.between(opts[:start] || 1, opts[:end] || -1). with_line_numbers(opts.present?(:line-numbers') || opts.present?(:ex)) end render_output(output, opts) end def process_ex window_size = Pry.config.default_window_size || 5 ex = _pry_.last_exception 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 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 set_file_and_dir_locals(ex_file) 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.} -- #{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)) "#{header}#{code}" end def process_in 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 if zipped_items.length > 1 contents = "" zipped_items.each do |i, s| contents << "#{text.bold(i.to_s)}:\n" contents << yield(Pry::Code(s).with_indentation(2)).to_s end else contents = yield(Pry::Code(zipped_items.first.last)) end contents end def process_file file_name = args.shift unless file_name raise CommandError, "Must provide a filename, --in, or --ex." end file_name, line_num = file_name.split(':') file_name = File.(file_name) set_file_and_dir_locals(file_name) code = yield(Pry::Code.from_file(file_name)) if line_num code = code.around(line_num.to_i, Pry.config.default_window_size || 7) end code end end end