This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
License
|
||||
-------
|
||||
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2018 John Mair (banisterfiend)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,469 @@
|
||||
Pry
|
||||
===
|
||||
|
||||
[](https://github.com/pry/pry/actions)
|
||||
[](https://codeclimate.com/github/pry/pry)
|
||||
[](https://badge.fury.io/rb/pry)
|
||||
[](https://inch-ci.org/github/pry/pry)
|
||||
[](https://rubygems.org/gems/pry)
|
||||
|
||||

|
||||
|
||||
© John Mair ([banisterfiend](https://twitter.com/banisterfiend)) 2018<br> (Creator)
|
||||
|
||||
© Kyrylo Silin ([kyrylosilin](https://twitter.com/kyrylosilin)) 2018<br> (Maintainer)
|
||||
|
||||
**Alumni:**
|
||||
|
||||
* Conrad Irwin
|
||||
* Ryan Fitzgerald
|
||||
* Robert Gleeson
|
||||
|
||||
**Links:**
|
||||
|
||||
* https://pry.github.io/
|
||||
* [YARD API documentation](https://www.rubydoc.info/gems/pry)
|
||||
* [Wiki](https://github.com/pry/pry/wiki)
|
||||
|
||||
Table of Contents
|
||||
=================
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [Key features](#key-features)
|
||||
* [Installation](#installation)
|
||||
* [Overview](#overview)
|
||||
* [Commands](#commands)
|
||||
* [Navigating around state](#navigating-around-state)
|
||||
* [Runtime invocation](#runtime-invocation)
|
||||
* [Command Shell Integration](#command-shell-integration)
|
||||
* [Code Browsing](#code-browsing)
|
||||
* [Documentation Browsing](#documentation-browsing)
|
||||
* [Edit methods](#edit-methods)
|
||||
* [Live Help System](#live-help-system)
|
||||
* [Use Pry as your Rails Console](#use-pry-as-your-rails-console)
|
||||
* [Syntax Highlighting](#syntax-highlighting)
|
||||
* [Supported Rubies](#supported-rubies)
|
||||
* [Contact](#contact)
|
||||
* [License](#license)
|
||||
* [Contributors](#contributors)
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
Pry is a runtime developer console and IRB alternative with powerful
|
||||
introspection capabilities. Pry aims to be more than an IRB replacement. It is
|
||||
an attempt to bring REPL driven programming to the Ruby language.
|
||||
|
||||
Key features
|
||||
------------
|
||||
|
||||
* Source code browsing (including core C source with the pry-doc gem)
|
||||
* Documentation browsing
|
||||
* Live help system
|
||||
* Open methods in editors (`edit Class#method`)
|
||||
* Syntax highlighting
|
||||
* Command shell integration (start editors, run git, and rake from within Pry)
|
||||
* Gist integration
|
||||
* Navigation around state (`cd`, `ls` and friends)
|
||||
* Runtime invocation (use Pry as a developer console or debugger)
|
||||
* Exotic object support (BasicObject instances, IClasses, ...)
|
||||
* A powerful and flexible command system
|
||||
* Ability to view and replay history
|
||||
* Many convenience commands inspired by IPython, Smalltalk and other advanced
|
||||
REPLs
|
||||
* A wide-range number of
|
||||
[plugins](https://github.com/pry/pry/wiki/Available-plugins) that provide
|
||||
remote sessions, full debugging functionality, and more.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
### Bundler
|
||||
|
||||
```ruby
|
||||
gem 'pry', '~> 0.15.0'
|
||||
```
|
||||
|
||||
### Manual
|
||||
|
||||
```sh
|
||||
gem install pry
|
||||
```
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
Pry is fairly flexible and allows significant user
|
||||
[customization](https://github.com/pry/pry/wiki/Customization-and-configuration).
|
||||
It is trivial to read from any object that has a `readline` method and
|
||||
write to any object that has a `puts` method. Many other aspects of Pry are
|
||||
also configurable, making it a good choice for implementing custom shells.
|
||||
|
||||
Pry comes with an executable so it can be invoked at the command line. Just
|
||||
enter `pry` to start. A `pryrc` file in `$XDG_CONFIG_HOME/pry/` or the user's
|
||||
home directory will be loaded if it exists. Type `pry --help` at the command
|
||||
line for more information.
|
||||
|
||||
### Commands
|
||||
|
||||
Nearly every piece of functionality in a Pry session is implemented as a
|
||||
command. Commands are not methods and must start at the beginning of a line,
|
||||
with no whitespace in between. Commands support a flexible syntax and allow
|
||||
'options' in the same way as shell commands, for example the following Pry
|
||||
command will show a list of all private instance methods (in scope) that begin
|
||||
with 'pa'
|
||||
|
||||
```ruby
|
||||
pry(YARD::Parser::SourceParser):5> ls -Mp --grep ^pa
|
||||
YARD::Parser::SourceParser#methods: parse parser_class parser_type parser_type= parser_type_for_filename
|
||||
```
|
||||
|
||||
### Navigating around state
|
||||
|
||||
Pry allows us to pop in and out of different scopes (objects) using the `cd`
|
||||
command. This enables us to explore the run-time view of a program or
|
||||
library. To view which variables and methods are available within a particular
|
||||
scope we use the versatile [ls
|
||||
command.](https://gist.github.com/c0fc686ef923c8b87715)
|
||||
|
||||
Here we will begin Pry at top-level, then Pry on a class and then on an instance
|
||||
variable inside that class:
|
||||
|
||||
```ruby
|
||||
pry(main)> class Hello
|
||||
pry(main)* @x = 20
|
||||
pry(main)* end
|
||||
=> 20
|
||||
pry(main)> cd Hello
|
||||
pry(Hello):1> ls -i
|
||||
instance variables: @x
|
||||
pry(Hello):1> cd @x
|
||||
pry(20):2> self + 10
|
||||
=> 30
|
||||
pry(20):2> cd ..
|
||||
pry(Hello):1> cd ..
|
||||
pry(main)> cd ..
|
||||
```
|
||||
|
||||
The number after the `:` in the pry prompt indicates the nesting level. To
|
||||
display more information about nesting, use the `nesting` command. E.g
|
||||
|
||||
```ruby
|
||||
pry("friend"):3> nesting
|
||||
Nesting status:
|
||||
0. main (Pry top level)
|
||||
1. Hello
|
||||
2. 100
|
||||
3. "friend"
|
||||
=> nil
|
||||
```
|
||||
|
||||
We can then jump back to any of the previous nesting levels by using the
|
||||
`jump-to` command:
|
||||
|
||||
```ruby
|
||||
pry("friend"):3> jump-to 1
|
||||
=> 100
|
||||
pry(Hello):1>
|
||||
```
|
||||
|
||||
### Runtime invocation
|
||||
|
||||
Pry can be invoked in the middle of a running program. It opens a Pry session at
|
||||
the point it's called and makes all program state at that point available. It
|
||||
can be invoked on any object using the `my_object.pry` syntax or on the current
|
||||
binding (or any binding) using `binding.pry`. The Pry session will then begin
|
||||
within the scope of the object (or binding). When the session ends the program
|
||||
continues with any modifications you made to it.
|
||||
|
||||
This functionality can be used for such things as: debugging, implementing
|
||||
developer consoles and applying hot patches.
|
||||
|
||||
code:
|
||||
|
||||
```ruby
|
||||
# test.rb
|
||||
require 'pry'
|
||||
|
||||
class A
|
||||
def hello() puts "hello world!" end
|
||||
end
|
||||
|
||||
a = A.new
|
||||
|
||||
# start a REPL session
|
||||
binding.pry
|
||||
|
||||
# program resumes here (after pry session)
|
||||
puts "program resumes here."
|
||||
```
|
||||
|
||||
Pry session:
|
||||
|
||||
```ruby
|
||||
pry(main)> a.hello
|
||||
hello world!
|
||||
=> nil
|
||||
pry(main)> def a.goodbye
|
||||
pry(main)* puts "goodbye cruel world!"
|
||||
pry(main)* end
|
||||
=> :goodbye
|
||||
pry(main)> a.goodbye
|
||||
goodbye cruel world!
|
||||
=> nil
|
||||
pry(main)> exit
|
||||
|
||||
program resumes here.
|
||||
```
|
||||
|
||||
### Command Shell Integration
|
||||
|
||||
A line of input that begins with a '.' will be forwarded to the command
|
||||
shell. This enables us to navigate the file system, spawn editors, and run git
|
||||
and rake directly from within Pry.
|
||||
|
||||
Further, we can use the `shell-mode` command to incorporate the present working
|
||||
directory into the Pry prompt and bring in (limited at this stage, sorry) file
|
||||
name completion. We can also interpolate Ruby code directly into the shell by
|
||||
using the normal `#{}` string interpolation syntax.
|
||||
|
||||
In the code below we're going to switch to `shell-mode` and edit the `pryrc`
|
||||
file. We'll then cat its contents and reload the file.
|
||||
|
||||
```ruby
|
||||
pry(main)> shell-mode
|
||||
pry main:/home/john/ruby/projects/pry $ .cd ~
|
||||
pry main:/home/john $ .emacsclient .pryrc
|
||||
pry main:/home/john $ .cat .pryrc
|
||||
def hello_world
|
||||
puts "hello world!"
|
||||
end
|
||||
pry main:/home/john $ load ".pryrc"
|
||||
=> true
|
||||
pry main:/home/john $ hello_world
|
||||
hello world!
|
||||
```
|
||||
|
||||
We can also interpolate Ruby code into the shell. In the example below we use
|
||||
the shell command `cat` on a random file from the current directory and count
|
||||
the number of lines in that file with `wc`:
|
||||
|
||||
```ruby
|
||||
pry main:/home/john $ .cat #{Dir['*.*'].sample} | wc -l
|
||||
44
|
||||
```
|
||||
|
||||
### Code Browsing
|
||||
|
||||
You can browse method source code with the `show-source` command. Nearly all
|
||||
Ruby methods (and some C methods, with the pry-doc gem) can have their source
|
||||
viewed. Code that is longer than a page is sent through a pager (such as less),
|
||||
and all code is properly syntax highlighted (even C code).
|
||||
|
||||
The `show-source` command accepts two syntaxes, the typical ri `Class#method`
|
||||
syntax and also simply the name of a method that's in scope. You can optionally
|
||||
pass the `-l` option to `show-source` to include line numbers in the output.
|
||||
|
||||
In the following example we will enter the `Pry` class, list the instance
|
||||
methods beginning with 'se' and display the source code for the `set_last_result` method:
|
||||
|
||||
```ruby
|
||||
pry(main)> cd Pry
|
||||
pry(Pry):1> ls -M --grep se
|
||||
Pry#methods: raise_up raise_up! raise_up_common reset_eval_string select_prompt set_last_result
|
||||
pry(Pry):1> show-source set_last_result -l
|
||||
|
||||
From: /home/john/ruby/projects/pry/lib/pry/pry_instance.rb:405:
|
||||
Owner: Pry
|
||||
Visibility: public
|
||||
Signature: set_last_result(result, code=?)
|
||||
Number of lines: 6
|
||||
|
||||
405: def set_last_result(result, code = "")
|
||||
406: @last_result_is_exception = false
|
||||
407: @output_ring << result
|
||||
408:
|
||||
409: self.last_result = result unless code =~ /\A\s*\z/
|
||||
410: end
|
||||
```
|
||||
|
||||
Note that we can also view C methods (from Ruby Core) using the
|
||||
`pry-doc` plugin; we also show off the alternate syntax for
|
||||
`show-source`:
|
||||
|
||||
```ruby
|
||||
pry(main)> show-source Array#select
|
||||
|
||||
From: array.c in Ruby Core (C Method):
|
||||
Number of lines: 15
|
||||
|
||||
static VALUE
|
||||
rb_ary_select(VALUE ary)
|
||||
{
|
||||
VALUE result;
|
||||
long i;
|
||||
|
||||
RETURN_ENUMERATOR(ary, 0, 0);
|
||||
result = rb_ary_new2(RARRAY_LEN(ary));
|
||||
for (i = 0; i < RARRAY_LEN(ary); i++) {
|
||||
if (RTEST(rb_yield(RARRAY_PTR(ary)[i]))) {
|
||||
rb_ary_push(result, rb_ary_elt(ary, i));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
### Documentation Browsing
|
||||
|
||||
One use-case for Pry is to explore a program at run-time by `cd`-ing in and out
|
||||
of objects and viewing and invoking methods. In the course of exploring it may
|
||||
be useful to read the documentation for a specific method that you come
|
||||
across. `show-source` command supports two syntaxes - the
|
||||
normal `ri` syntax as well as accepting the name of any method that is currently
|
||||
in scope.
|
||||
|
||||
The Pry documentation system does not rely on pre-generated `rdoc` or `ri`,
|
||||
instead it grabs the comments directly above the method on demand. This results
|
||||
in speedier documentation retrieval and allows the Pry system to retrieve
|
||||
documentation for methods that would not be picked up by `rdoc`. Pry also has a
|
||||
basic understanding of both the rdoc and yard formats and will attempt to syntax
|
||||
highlight the documentation appropriately.
|
||||
|
||||
Nonetheless, the `ri` functionality is very good and has an advantage over Pry's
|
||||
system in that it allows documentation lookup for classes as well as
|
||||
methods. Pry therefore has good integration with `ri` through the `ri`
|
||||
command. The syntax for the command is exactly as it would be in command-line -
|
||||
so it is not necessary to quote strings.
|
||||
|
||||
In our example we will enter the `Gem` class and view the documentation for the
|
||||
`try_activate` method:
|
||||
|
||||
```ruby
|
||||
pry(main)> cd Gem
|
||||
pry(Gem):1> show-source try_activate -d
|
||||
|
||||
From: /Users/john/rbenv/versions/2.7.1/lib/ruby/2.7.0/rubygems.rb:194:
|
||||
Owner: #<Class:Gem>
|
||||
Visibility: public
|
||||
Signature: try_activate(path)
|
||||
Number of lines: 28
|
||||
|
||||
Try to activate a gem containing path. Returns true if
|
||||
activation succeeded or wasn't needed because it was already
|
||||
activated. Returns false if it can't find the path in a gem.
|
||||
|
||||
def self.try_activate(path)
|
||||
# finds the _latest_ version... regardless of loaded specs and their deps
|
||||
# if another gem had a requirement that would mean we shouldn't
|
||||
# activate the latest version, then either it would already be activated
|
||||
# or if it was ambiguous (and thus unresolved) the code in our custom
|
||||
# require will try to activate the more specific version.
|
||||
|
||||
spec = Gem::Specification.find_by_path path
|
||||
pry(Gem):1>
|
||||
```
|
||||
|
||||
We can also use `ri` in the normal way:
|
||||
|
||||
```ruby
|
||||
pry(main) ri Array#each
|
||||
----------------------------------------------------------- Array#each
|
||||
array.each {|item| block } -> array
|
||||
------------------------------------------------------------------------
|
||||
Calls _block_ once for each element in _self_, passing that element
|
||||
as a parameter.
|
||||
|
||||
a = [ "a", "b", "c" ]
|
||||
a.each {|x| print x, " -- " }
|
||||
|
||||
produces:
|
||||
|
||||
a -- b -- c --
|
||||
```
|
||||
|
||||
### Edit methods
|
||||
|
||||
You can use `edit Class#method` or `edit my_method` (if the method is in scope)
|
||||
to open a method for editing directly in your favorite editor. Pry has knowledge
|
||||
of a few different editors and will attempt to open the file at the line the
|
||||
method is defined.
|
||||
|
||||
You can set the editor to use by assigning to the `Pry.editor`
|
||||
accessor. `Pry.editor` will default to `$EDITOR` or failing that will use `nano`
|
||||
as the backup default. The file that is edited will be automatically reloaded
|
||||
after exiting the editor - reloading can be suppressed by passing the
|
||||
`--no-reload` option to `edit`
|
||||
|
||||
In the example below we will set our default editor to "emacsclient" and open
|
||||
the `Pry#repl` method for editing:
|
||||
|
||||
```ruby
|
||||
pry(main)> Pry.editor = "emacsclient"
|
||||
pry(main)> edit Pry#repl
|
||||
```
|
||||
|
||||
### Live Help System
|
||||
|
||||
Many other commands are available in Pry; to see the full list type `help` at
|
||||
the prompt. A short description of each command is provided with basic
|
||||
instructions for use; some commands have a more extensive help that can be
|
||||
accessed via typing `command_name --help`. A command will typically say in its
|
||||
description if the `--help` option is available.
|
||||
|
||||
### Use Pry as your Rails Console
|
||||
|
||||
You can run a Pry console in your app's environment using Pry's `-r` flag:
|
||||
|
||||
```sh
|
||||
pry -r ./config/environment
|
||||
```
|
||||
|
||||
Or start the rails console (`bin/rails console`) and then type `pry`.
|
||||
|
||||
It's also possible to use Pry as your Rails console by adding [the pry-rails
|
||||
gem](https://github.com/pry/pry-rails) to your Gemfile. This replaces the
|
||||
default console with Pry, in addition to loading the Rails console helpers and
|
||||
adding some useful Rails-specific commands.
|
||||
|
||||
**Note that `pry-rails` is not currently maintained.**
|
||||
|
||||
Also check out the
|
||||
[wiki](https://github.com/pry/pry/wiki/Setting-up-Rails-or-Heroku-to-use-Pry)
|
||||
for more information about integrating Pry with Rails.
|
||||
|
||||
### Syntax Highlighting
|
||||
|
||||
Syntax highlighting is on by default in Pry. If you want to change the colors,
|
||||
check out the [pry-theme](https://github.com/kyrylo/pry-theme) gem.
|
||||
|
||||
You can toggle the syntax highlighting on and off in a session by using the
|
||||
`toggle-color` command. Alternatively, you can turn it off permanently by
|
||||
putting the line `Pry.color = false` in your `pryrc` file.
|
||||
|
||||
Supported Rubies
|
||||
----------------
|
||||
|
||||
* CRuby >= 2.0.0
|
||||
* JRuby >= 9.0
|
||||
|
||||
Contact
|
||||
-------
|
||||
|
||||
In case you have a problem, question or a bug report, feel free to:
|
||||
|
||||
* ask a question on IRC (#pry on Freenode)
|
||||
* [file an issue](https://github.com/pry/pry/issues)
|
||||
* [tweet at us](https://twitter.com/pryruby)
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
The project uses the MIT License. See LICENSE.md for details.
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
Pry is primarily the work of [John Mair (banisterfiend)](https://github.com/banister), for full list
|
||||
of contributors see the
|
||||
[contributors graph](https://github.com/pry/pry/graphs/contributors).
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
# (C) John Mair (banisterfiend)
|
||||
# MIT license
|
||||
|
||||
$0 = 'pry'
|
||||
|
||||
require 'pry'
|
||||
|
||||
# Process command line options and run Pry
|
||||
opts = Pry::CLI.parse_options
|
||||
Pry::CLI.start(opts)
|
||||
@@ -0,0 +1,148 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# (C) John Mair (banisterfiend) 2016
|
||||
# MIT License
|
||||
|
||||
require 'pry/version'
|
||||
require 'pry/last_exception'
|
||||
require 'pry/forwardable'
|
||||
|
||||
require 'pry/helpers/base_helpers'
|
||||
require 'pry/helpers/documentation_helpers'
|
||||
require 'pry/helpers'
|
||||
|
||||
require 'pry/basic_object'
|
||||
require 'pry/prompt'
|
||||
require 'pry/code_object'
|
||||
require 'pry/exceptions'
|
||||
require 'pry/hooks'
|
||||
require 'pry/input_completer'
|
||||
require 'pry/command'
|
||||
require 'pry/class_command'
|
||||
require 'pry/block_command'
|
||||
require 'pry/command_set'
|
||||
require 'pry/syntax_highlighter'
|
||||
require 'pry/editor'
|
||||
require 'pry/history'
|
||||
require 'pry/color_printer'
|
||||
require 'pry/exception_handler'
|
||||
require 'pry/system_command_handler'
|
||||
require 'pry/control_d_handler'
|
||||
require 'pry/command_state'
|
||||
require 'pry/warning'
|
||||
require 'pry/env'
|
||||
|
||||
Pry::Commands = Pry::CommandSet.new unless defined?(Pry::Commands)
|
||||
|
||||
require 'pry/commands/ls/config'
|
||||
require 'pry/commands/ls/jruby_hacks'
|
||||
require 'pry/commands/ls/methods_helper'
|
||||
require 'pry/commands/ls/interrogatable'
|
||||
require 'pry/commands/ls/grep'
|
||||
require 'pry/commands/ls/formatter'
|
||||
require 'pry/commands/ls/globals'
|
||||
require 'pry/commands/ls/constants'
|
||||
require 'pry/commands/ls/methods'
|
||||
require 'pry/commands/ls/self_methods'
|
||||
require 'pry/commands/ls/instance_vars'
|
||||
require 'pry/commands/ls/local_names'
|
||||
require 'pry/commands/ls/local_vars'
|
||||
require 'pry/commands/ls/interrogatable'
|
||||
require 'pry/commands/ls/ls_entity'
|
||||
require 'pry/commands/ls/methods_helper'
|
||||
require 'pry/commands/ls'
|
||||
|
||||
require 'pry/config/attributable'
|
||||
require 'pry/config/value'
|
||||
require 'pry/config/memoized_value'
|
||||
require 'pry/config/lazy_value'
|
||||
require 'pry/config'
|
||||
|
||||
require 'pry/input/simple_stdio'
|
||||
|
||||
require 'pry/pry_class'
|
||||
require 'pry/pry_instance'
|
||||
require 'pry/inspector'
|
||||
require 'pry/pager'
|
||||
require 'pry/indent'
|
||||
require 'pry/object_path'
|
||||
require 'pry/output'
|
||||
require 'pry/input_lock'
|
||||
require 'pry/repl'
|
||||
require 'pry/code'
|
||||
require 'pry/ring'
|
||||
require 'pry/method'
|
||||
|
||||
require 'pry/wrapped_module'
|
||||
require 'pry/wrapped_module/candidate'
|
||||
|
||||
require 'pry/slop'
|
||||
require 'pry/cli'
|
||||
require 'pry/core_extensions'
|
||||
require 'pry/repl_file_loader'
|
||||
|
||||
require 'pry/code/loc'
|
||||
require 'pry/code/code_range'
|
||||
require 'pry/code/code_file'
|
||||
|
||||
require 'pry/method/weird_method_locator'
|
||||
require 'pry/method/disowned'
|
||||
require 'pry/method/patcher'
|
||||
|
||||
require 'pry/commands/amend_line'
|
||||
require 'pry/commands/bang'
|
||||
require 'pry/commands/bang_pry'
|
||||
|
||||
require 'pry/commands/cat'
|
||||
require 'pry/commands/cat/abstract_formatter.rb'
|
||||
require 'pry/commands/cat/input_expression_formatter.rb'
|
||||
require 'pry/commands/cat/exception_formatter.rb'
|
||||
require 'pry/commands/cat/file_formatter.rb'
|
||||
|
||||
require 'pry/commands/cd'
|
||||
require 'pry/commands/change_inspector'
|
||||
require 'pry/commands/change_prompt'
|
||||
require 'pry/commands/clear_screen'
|
||||
require 'pry/commands/code_collector'
|
||||
require 'pry/commands/disable_pry'
|
||||
require 'pry/commands/easter_eggs'
|
||||
|
||||
require 'pry/commands/edit'
|
||||
require 'pry/commands/edit/exception_patcher'
|
||||
require 'pry/commands/edit/file_and_line_locator'
|
||||
|
||||
require 'pry/commands/exit'
|
||||
require 'pry/commands/exit_all'
|
||||
require 'pry/commands/exit_program'
|
||||
require 'pry/commands/find_method'
|
||||
require 'pry/commands/fix_indent'
|
||||
require 'pry/commands/help'
|
||||
require 'pry/commands/hist'
|
||||
require 'pry/commands/import_set'
|
||||
require 'pry/commands/jump_to'
|
||||
require 'pry/commands/list_inspectors'
|
||||
|
||||
require 'pry/commands/nesting'
|
||||
require 'pry/commands/play'
|
||||
require 'pry/commands/pry_backtrace'
|
||||
require 'pry/commands/pry_version'
|
||||
require 'pry/commands/raise_up'
|
||||
require 'pry/commands/reload_code'
|
||||
require 'pry/commands/reset'
|
||||
require 'pry/commands/ri'
|
||||
require 'pry/commands/save_file'
|
||||
require 'pry/commands/shell_command'
|
||||
require 'pry/commands/shell_mode'
|
||||
require 'pry/commands/show_info'
|
||||
require 'pry/commands/show_doc'
|
||||
require 'pry/commands/show_input'
|
||||
require 'pry/commands/show_source'
|
||||
require 'pry/commands/stat'
|
||||
require 'pry/commands/switch_to'
|
||||
require 'pry/commands/toggle_color'
|
||||
|
||||
require 'pry/commands/watch_expression'
|
||||
require 'pry/commands/watch_expression/expression.rb'
|
||||
|
||||
require 'pry/commands/whereami'
|
||||
require 'pry/commands/wtf'
|
||||
@@ -0,0 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class BasicObject < BasicObject
|
||||
%i[Kernel File Dir LoadError ENV Pry].each do |constant|
|
||||
const_set constant, ::Object.const_get(constant)
|
||||
end
|
||||
include Kernel
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
# A super-class for Commands that are created with a single block.
|
||||
#
|
||||
# This class ensures that the block is called with the correct number of
|
||||
# arguments and the right context.
|
||||
#
|
||||
# Create subclasses using {Pry::CommandSet#command}.
|
||||
class BlockCommand < Command
|
||||
# Call the block that was registered with this command.
|
||||
# @param [Array<String>] args The arguments passed
|
||||
# @return [Object] The return value of the block
|
||||
def call(*args)
|
||||
instance_exec(*normalize_method_args(block, args), &block)
|
||||
end
|
||||
|
||||
def help
|
||||
"#{command_options[:listing].to_s.ljust(18)} #{description}"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,194 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
# A super-class of Commands with structure.
|
||||
#
|
||||
# This class implements the bare-minimum functionality that a command should
|
||||
# have, namely a --help switch, and then delegates actual processing to its
|
||||
# subclasses.
|
||||
#
|
||||
# Create subclasses using {Pry::CommandSet#create_command}, and override the
|
||||
# `options(opt)` method to set up an instance of Pry::Slop, and the `process`
|
||||
# method to actually run the command. If necessary, you can also override
|
||||
# `setup` which will be called before `options`, for example to require any
|
||||
# gems your command needs to run, or to set up state.
|
||||
class ClassCommand < Command
|
||||
class << self
|
||||
# Ensure that subclasses inherit the options, description and
|
||||
# match from a ClassCommand super class.
|
||||
def inherited(klass)
|
||||
klass.match match
|
||||
klass.description description
|
||||
klass.command_options options
|
||||
end
|
||||
|
||||
def source
|
||||
source_object.source
|
||||
end
|
||||
|
||||
def doc
|
||||
new.help
|
||||
end
|
||||
|
||||
def source_location
|
||||
source_object.source_location
|
||||
end
|
||||
|
||||
def source_file
|
||||
source_object.source_file
|
||||
end
|
||||
alias file source_file
|
||||
|
||||
def source_line
|
||||
source_object.source_line
|
||||
end
|
||||
alias line source_line
|
||||
|
||||
private
|
||||
|
||||
# The object used to extract the source for the command.
|
||||
#
|
||||
# This should be a `Pry::Method(block)` for a command made with `create_command`
|
||||
# and a `Pry::WrappedModule(self)` for a command that's a standard class.
|
||||
# @return [Pry::WrappedModule, Pry::Method]
|
||||
def source_object
|
||||
@source_object ||= if name =~ /^[A-Z]/
|
||||
Pry::WrappedModule(self)
|
||||
else
|
||||
Pry::Method(block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
attr_accessor :opts
|
||||
attr_accessor :args
|
||||
|
||||
# Set up `opts` and `args`, and then call `process`.
|
||||
#
|
||||
# This method will display help if necessary.
|
||||
#
|
||||
# @param [Array<String>] args The arguments passed
|
||||
# @return [Object] The return value of `process` or VOID_VALUE
|
||||
def call(*args)
|
||||
setup
|
||||
|
||||
self.opts = slop
|
||||
self.args = opts.parse!(args)
|
||||
|
||||
if opts.present?(:help)
|
||||
output.puts slop.help
|
||||
void
|
||||
else
|
||||
process(*normalize_method_args(method(:process), args))
|
||||
end
|
||||
end
|
||||
|
||||
# Return the help generated by Pry::Slop for this command.
|
||||
def help
|
||||
slop.help
|
||||
end
|
||||
|
||||
# Return an instance of Pry::Slop that can parse either subcommands or the
|
||||
# options that this command accepts.
|
||||
def slop
|
||||
Pry::Slop.new do |opt|
|
||||
opt.banner(unindent(self.class.banner))
|
||||
subcommands(opt)
|
||||
options(opt)
|
||||
opt.on :h, :help, 'Show this message.'
|
||||
end
|
||||
end
|
||||
|
||||
# Generate shell completions
|
||||
# @param [String] search The line typed so far
|
||||
# @return [Array<String>] the words to complete
|
||||
def complete(search)
|
||||
slop.flat_map do |opt|
|
||||
[opt.long && "--#{opt.long} " || opt.short && "-#{opt.short}"]
|
||||
end.compact + super
|
||||
end
|
||||
|
||||
# A method called just before `options(opt)` as part of `call`.
|
||||
#
|
||||
# This method can be used to set up any context your command needs to run,
|
||||
# for example requiring gems, or setting default values for options.
|
||||
#
|
||||
# @example
|
||||
# def setup
|
||||
# require 'gist'
|
||||
# @action = :method
|
||||
# end
|
||||
def setup; end
|
||||
|
||||
# A method to setup Pry::Slop commands so it can parse the subcommands your
|
||||
# command expects. If you need to set up default values, use `setup`
|
||||
# instead.
|
||||
#
|
||||
# @example A minimal example
|
||||
# def subcommands(cmd)
|
||||
# cmd.command :download do |opt|
|
||||
# description 'Downloads a content from a server'
|
||||
#
|
||||
# opt.on :verbose, 'Use verbose output'
|
||||
#
|
||||
# run do |options, arguments|
|
||||
# ContentDownloader.download(options, arguments)
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# @example Define the invocation block anywhere you want
|
||||
# def subcommands(cmd)
|
||||
# cmd.command :download do |opt|
|
||||
# description 'Downloads a content from a server'
|
||||
#
|
||||
# opt.on :verbose, 'Use verbose output'
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# def process
|
||||
# # Perform calculations...
|
||||
# opts.fetch_command(:download).run do |options, arguments|
|
||||
# ContentDownloader.download(options, arguments)
|
||||
# end
|
||||
# # More calculations...
|
||||
# end
|
||||
def subcommands(cmd); end
|
||||
|
||||
# A method to setup Pry::Slop so it can parse the options your command expects.
|
||||
#
|
||||
# @note Please don't do anything side-effecty in the main part of this
|
||||
# method, as it may be called by Pry at any time for introspection reasons.
|
||||
# If you need to set up default values, use `setup` instead.
|
||||
#
|
||||
# @example
|
||||
# def options(opt)
|
||||
# opt.banner "Gists methods or classes"
|
||||
# opt.on(:c, :class, "gist a class") do
|
||||
# @action = :class
|
||||
# end
|
||||
# end
|
||||
def options(opt); end
|
||||
|
||||
# The actual body of your command should go here.
|
||||
#
|
||||
# The `opts` method can be called to get the options that Pry::Slop has passed,
|
||||
# and `args` gives the remaining, unparsed arguments.
|
||||
#
|
||||
# The return value of this method is discarded unless the command was
|
||||
# created with `:keep_retval => true`, in which case it is returned to the
|
||||
# repl.
|
||||
#
|
||||
# @example
|
||||
# def process
|
||||
# if opts.present?(:class)
|
||||
# gist_class
|
||||
# else
|
||||
# gist_method
|
||||
# end
|
||||
# end
|
||||
def process
|
||||
raise CommandError, "command '#{command_name}' not implemented"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,211 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'stringio'
|
||||
|
||||
class Pry
|
||||
# Manage the processing of command line options
|
||||
class CLI
|
||||
NoOptionsError = Class.new(StandardError)
|
||||
|
||||
class << self
|
||||
# @return [Proc] The Proc defining the valid command line options.
|
||||
attr_accessor :options
|
||||
|
||||
# @return [Array] The Procs that process the parsed options. Plugins can
|
||||
# utilize this facility in order to add and process their own Pry
|
||||
# options.
|
||||
attr_accessor :option_processors
|
||||
|
||||
# @return [Array<String>] The input array of strings to process
|
||||
# as CLI options.
|
||||
attr_accessor :input_args
|
||||
|
||||
# Add another set of CLI options (a Pry::Slop block)
|
||||
def add_options(&block)
|
||||
if options
|
||||
old_options = options
|
||||
self.options = proc do
|
||||
instance_exec(&old_options)
|
||||
instance_exec(&block)
|
||||
end
|
||||
else
|
||||
self.options = block
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
# Add a block responsible for processing parsed options.
|
||||
def add_option_processor(&block)
|
||||
self.option_processors ||= []
|
||||
option_processors << block
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
# Clear `options` and `option_processors`
|
||||
def reset
|
||||
self.options = nil
|
||||
self.option_processors = nil
|
||||
end
|
||||
|
||||
def parse_options(args = ARGV)
|
||||
unless options
|
||||
raise NoOptionsError,
|
||||
"No command line options defined! Use Pry::CLI.add_options to " \
|
||||
"add command line options."
|
||||
end
|
||||
|
||||
@pass_argv = args.index { |cli_arg| %w[- --].include?(cli_arg) }
|
||||
if @pass_argv
|
||||
slop_args = args[0...@pass_argv]
|
||||
self.input_args = args.replace(args[@pass_argv + 1..-1])
|
||||
else
|
||||
self.input_args = slop_args = args
|
||||
end
|
||||
|
||||
begin
|
||||
opts = Pry::Slop.parse!(
|
||||
slop_args,
|
||||
help: true,
|
||||
multiple_switches: false,
|
||||
strict: true,
|
||||
&options
|
||||
)
|
||||
rescue Pry::Slop::InvalidOptionError
|
||||
# Display help message on unknown switches and exit.
|
||||
puts Pry::Slop.new(&options)
|
||||
Kernel.exit
|
||||
end
|
||||
|
||||
Pry.initial_session_setup
|
||||
Pry.final_session_setup
|
||||
|
||||
# Option processors are optional.
|
||||
option_processors.each { |processor| processor.call(opts) } if option_processors
|
||||
|
||||
opts
|
||||
end
|
||||
|
||||
def start(opts)
|
||||
Kernel.exit if opts.help?
|
||||
|
||||
# invoked via cli
|
||||
Pry.cli = true
|
||||
|
||||
# create the actual context
|
||||
if opts[:context]
|
||||
Pry.initial_session_setup
|
||||
context = Pry.binding_for(eval(opts[:context])) # rubocop:disable Security/Eval
|
||||
Pry.final_session_setup
|
||||
else
|
||||
context = Pry.toplevel_binding
|
||||
end
|
||||
|
||||
if !@pass_argv && Pry::CLI.input_args.any? && Pry::CLI.input_args != ["pry"]
|
||||
full_name = File.expand_path(Pry::CLI.input_args.first)
|
||||
Pry.load_file_through_repl(full_name)
|
||||
Kernel.exit
|
||||
end
|
||||
|
||||
# Start the session (running any code passed with -e, if there is any)
|
||||
Pry.start(context, input: StringIO.new(Pry.config.exec_string))
|
||||
end
|
||||
end
|
||||
|
||||
reset
|
||||
end
|
||||
end
|
||||
|
||||
# The default Pry command line options (before plugin options are included)
|
||||
Pry::CLI.add_options do
|
||||
banner(
|
||||
"Usage: pry [OPTIONS]\n" \
|
||||
"Start a Pry session.\n" \
|
||||
"See http://pry.github.io/ for more information.\n" \
|
||||
"Copyright (c) 2016 John Mair (banisterfiend)" \
|
||||
)
|
||||
|
||||
on(
|
||||
:e, :exec=, "A line of code to execute in context before the session starts"
|
||||
) do |input|
|
||||
Pry.config.exec_string += "\n" unless Pry.config.exec_string.empty?
|
||||
Pry.config.exec_string += input
|
||||
end
|
||||
|
||||
on "no-pager", "Disable pager for long output" do
|
||||
Pry.config.pager = false
|
||||
end
|
||||
|
||||
on "no-history", "Disable history loading" do
|
||||
Pry.config.history_load = false
|
||||
end
|
||||
|
||||
on "no-color", "Disable syntax highlighting for session" do
|
||||
Pry.config.color = false
|
||||
end
|
||||
|
||||
on "no-multiline", "Disables multiline (defaults to true with Reline)" do
|
||||
Pry.config.multiline = false
|
||||
end
|
||||
|
||||
on :f, "Suppress loading of pryrc" do
|
||||
Pry.config.should_load_rc = false
|
||||
Pry.config.should_load_local_rc = false
|
||||
end
|
||||
|
||||
on :s, "select-plugin=", "Only load specified plugin (and no others)." do |_plugin_name|
|
||||
warn "The --select-plugin option is deprecated and has no effect"
|
||||
end
|
||||
|
||||
on :d, "disable-plugin=", "Disable a specific plugin." do |_plugin_name|
|
||||
warn "The --disable-plugin option is deprecated and has no effect"
|
||||
end
|
||||
|
||||
on "no-plugins", "Suppress loading of plugins." do
|
||||
warn "The --no-plugins option is deprecated and has no effect"
|
||||
end
|
||||
|
||||
on "plugins", "List installed plugins." do
|
||||
warn "The --plugins option is deprecated and has no effect"
|
||||
warn "Try using `gem list pry-`"
|
||||
Kernel.exit
|
||||
end
|
||||
|
||||
on "simple-prompt", "Enable simple prompt mode" do
|
||||
Pry.config.prompt = Pry::Prompt[:simple]
|
||||
end
|
||||
|
||||
on "noprompt", "No prompt mode" do
|
||||
Pry.config.prompt = Pry::Prompt[:none]
|
||||
end
|
||||
|
||||
on :r, :require=, "`require` a Ruby script at startup" do |file|
|
||||
Pry.config.requires << file
|
||||
end
|
||||
|
||||
on(:I=, "Add a path to the $LOAD_PATH", as: Array, delimiter: ":") do |load_path|
|
||||
load_path.map! do |path|
|
||||
%r{\A\./} =~ path ? path : File.expand_path(path)
|
||||
end
|
||||
|
||||
$LOAD_PATH.unshift(*load_path)
|
||||
end
|
||||
|
||||
on "gem", "Shorthand for -I./lib -rgemname" do |_load_path|
|
||||
$LOAD_PATH.unshift("./lib")
|
||||
Dir["./lib/*.rb"].each do |file|
|
||||
Pry.config.requires << file
|
||||
end
|
||||
end
|
||||
|
||||
on :v, :version, "Display the Pry version" do
|
||||
puts "Pry version #{Pry::VERSION} on Ruby #{RUBY_VERSION}"
|
||||
Kernel.exit
|
||||
end
|
||||
|
||||
on :c, :context=,
|
||||
"Start the session in the specified context. Equivalent to " \
|
||||
"`context.pry` in a session.",
|
||||
default: "Pry.toplevel_binding"
|
||||
end
|
||||
@@ -0,0 +1,357 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'method_source'
|
||||
|
||||
class Pry
|
||||
class << self
|
||||
# Convert the given object into an instance of `Pry::Code`, if it isn't
|
||||
# already one.
|
||||
#
|
||||
# @param [Code, Method, UnboundMethod, Proc, Pry::Method, String, Array,
|
||||
# IO] obj
|
||||
def Code(obj)
|
||||
case obj
|
||||
when Code
|
||||
obj
|
||||
when ::Method, UnboundMethod, Proc, Pry::Method
|
||||
Code.from_method(obj)
|
||||
else
|
||||
Code.new(obj)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# `Pry::Code` is a class that encapsulates lines of source code and their
|
||||
# line numbers and formats them for terminal output. It can read from a file
|
||||
# or method definition or be instantiated with a `String` or an `Array`.
|
||||
#
|
||||
# In general, the formatting methods in `Code` return a new `Code` object
|
||||
# which will format the text as specified when `#to_s` is called. This allows
|
||||
# arbitrary chaining of formatting methods without mutating the original
|
||||
# object.
|
||||
class Code
|
||||
class << self
|
||||
include MethodSource::CodeHelpers
|
||||
|
||||
# Instantiate a `Code` object containing code loaded from a file or
|
||||
# Pry's line buffer.
|
||||
#
|
||||
# @param [String] filename The name of a file, or "(pry)".
|
||||
# @param [Symbol] code_type The type of code the file contains.
|
||||
# @return [Code]
|
||||
def from_file(filename, code_type = nil)
|
||||
code_file = CodeFile.new(filename, code_type)
|
||||
new(code_file.code, 1, code_file.code_type)
|
||||
end
|
||||
|
||||
# Instantiate a `Code` object containing code extracted from a
|
||||
# `::Method`, `UnboundMethod`, `Proc`, or `Pry::Method` object.
|
||||
#
|
||||
# @param [::Method, UnboundMethod, Proc, Pry::Method] meth The method
|
||||
# object.
|
||||
# @param [Integer, nil] start_line The line number to start on, or nil to
|
||||
# use the method's original line numbers.
|
||||
# @return [Code]
|
||||
def from_method(meth, start_line = nil)
|
||||
meth = Pry::Method(meth)
|
||||
start_line ||= meth.source_line || 1
|
||||
new(meth.source, start_line, meth.source_type)
|
||||
end
|
||||
|
||||
# Attempt to extract the source code for module (or class) `mod`.
|
||||
#
|
||||
# @param [Module, Class] mod The module (or class) of interest.
|
||||
# @param [Integer] candidate_rank The module candidate (by rank)
|
||||
# to use (see `Pry::WrappedModule::Candidate` for more information).
|
||||
# @param [Integer, nil] start_line The line number to start on, or nil to
|
||||
# use the method's original line numbers.
|
||||
# @return [Code]
|
||||
def from_module(mod, candidate_rank = 0, start_line = nil)
|
||||
candidate = Pry::WrappedModule(mod).candidate(candidate_rank)
|
||||
start_line ||= candidate.line
|
||||
new(candidate.source, start_line, :ruby)
|
||||
end
|
||||
end
|
||||
|
||||
# @return [Symbol] The type of code stored in this wrapper.
|
||||
attr_accessor :code_type
|
||||
|
||||
# Instantiate a `Code` object containing code from the given `Array`,
|
||||
# `String`, or `IO`. The first line will be line 1 unless specified
|
||||
# otherwise. If you need non-contiguous line numbers, you can create an
|
||||
# empty `Code` object and then use `#push` to insert the lines.
|
||||
#
|
||||
# @param [Array<String>, String, IO] lines
|
||||
# @param [Integer?] start_line
|
||||
# @param [Symbol?] code_type
|
||||
def initialize(lines = [], start_line = 1, code_type = :ruby)
|
||||
lines = lines.lines if lines.is_a? String
|
||||
@lines = lines.each_with_index.map do |line, lineno|
|
||||
LOC.new(line, lineno + start_line.to_i)
|
||||
end
|
||||
@code_type = code_type
|
||||
|
||||
@with_marker = @with_indentation = @with_line_numbers = nil
|
||||
end
|
||||
|
||||
# Append the given line. +lineno+ is one more than the last existing
|
||||
# line, unless specified otherwise.
|
||||
#
|
||||
# @param [String] line
|
||||
# @return [void]
|
||||
def push(line)
|
||||
line_number = @lines.any? ? @lines.last.lineno + 1 : 1
|
||||
@lines.push(LOC.new(line, line_number))
|
||||
end
|
||||
alias << push
|
||||
|
||||
# Filter the lines using the given block.
|
||||
#
|
||||
# @yield [LOC]
|
||||
# @return [Code]
|
||||
def select(&block)
|
||||
alter do
|
||||
@lines = @lines.select(&block)
|
||||
end
|
||||
end
|
||||
|
||||
# Filter the lines using the given block.
|
||||
#
|
||||
# @yield [LOC]
|
||||
# @return [Code]
|
||||
def reject(&block)
|
||||
alter do
|
||||
@lines = @lines.reject(&block)
|
||||
end
|
||||
end
|
||||
|
||||
# Remove all lines that aren't in the given range, expressed either as a
|
||||
# `Range` object or a first and last line number (inclusive). Negative
|
||||
# indices count from the end of the array of lines.
|
||||
#
|
||||
# @param [Range, Integer] start_line
|
||||
# @param [Integer?] end_line
|
||||
# @return [Code]
|
||||
def between(start_line, end_line = nil)
|
||||
return self unless start_line
|
||||
|
||||
code_range = CodeRange.new(start_line, end_line)
|
||||
|
||||
alter do
|
||||
@lines = @lines[code_range.indices_range(@lines)] || []
|
||||
end
|
||||
end
|
||||
|
||||
# Take `num_lines` from `start_line`, forward or backwards.
|
||||
#
|
||||
# @param [Integer] start_line
|
||||
# @param [Integer] num_lines
|
||||
# @return [Code]
|
||||
def take_lines(start_line, num_lines)
|
||||
start_idx =
|
||||
if start_line >= 0
|
||||
@lines.index { |loc| loc.lineno >= start_line } || @lines.length
|
||||
else
|
||||
[@lines.length + start_line, 0].max
|
||||
end
|
||||
|
||||
alter do
|
||||
@lines = @lines.slice(start_idx, num_lines)
|
||||
end
|
||||
end
|
||||
|
||||
# Remove all lines except for the +lines+ up to and excluding +lineno+.
|
||||
#
|
||||
# @param [Integer] lineno
|
||||
# @param [Integer] lines
|
||||
# @return [Code]
|
||||
def before(lineno, lines = 1)
|
||||
return self unless lineno
|
||||
|
||||
select do |loc|
|
||||
loc.lineno >= lineno - lines && loc.lineno < lineno
|
||||
end
|
||||
end
|
||||
|
||||
# Remove all lines except for the +lines+ on either side of and including
|
||||
# +lineno+.
|
||||
#
|
||||
# @param [Integer] lineno
|
||||
# @param [Integer] lines
|
||||
# @return [Code]
|
||||
def around(lineno, lines = 1)
|
||||
return self unless lineno
|
||||
|
||||
select do |loc|
|
||||
loc.lineno >= lineno - lines && loc.lineno <= lineno + lines
|
||||
end
|
||||
end
|
||||
|
||||
# Remove all lines except for the +lines+ after and excluding +lineno+.
|
||||
#
|
||||
# @param [Integer] lineno
|
||||
# @param [Integer] lines
|
||||
# @return [Code]
|
||||
def after(lineno, lines = 1)
|
||||
return self unless lineno
|
||||
|
||||
select do |loc|
|
||||
loc.lineno > lineno && loc.lineno <= lineno + lines
|
||||
end
|
||||
end
|
||||
|
||||
# Remove all lines that don't match the given `pattern`.
|
||||
#
|
||||
# @param [Regexp] pattern
|
||||
# @return [Code]
|
||||
def grep(pattern)
|
||||
return self unless pattern
|
||||
|
||||
pattern = Regexp.new(pattern)
|
||||
|
||||
select do |loc|
|
||||
loc.line =~ pattern
|
||||
end
|
||||
end
|
||||
|
||||
# Format output with line numbers next to it, unless `y_n` is falsy.
|
||||
#
|
||||
# @param [Boolean?] y_n
|
||||
# @return [Code]
|
||||
def with_line_numbers(y_n = true)
|
||||
alter do
|
||||
@with_line_numbers = y_n
|
||||
end
|
||||
end
|
||||
|
||||
# Format output with a marker next to the given +lineno+, unless +lineno+ is
|
||||
# falsy.
|
||||
#
|
||||
# @param [Integer?] lineno
|
||||
# @return [Code]
|
||||
def with_marker(lineno = 1)
|
||||
alter do
|
||||
@with_marker = !!lineno
|
||||
@marker_lineno = lineno
|
||||
end
|
||||
end
|
||||
|
||||
# Format output with the specified number of spaces in front of every line,
|
||||
# unless `spaces` is falsy.
|
||||
#
|
||||
# @param [Integer?] spaces
|
||||
# @return [Code]
|
||||
def with_indentation(spaces = 0)
|
||||
alter do
|
||||
@with_indentation = !!spaces
|
||||
@indentation_num = spaces
|
||||
end
|
||||
end
|
||||
|
||||
# @return [Integer] the number of digits in the last line.
|
||||
def max_lineno_width
|
||||
!@lines.empty? ? @lines.last.lineno.to_s.length : 0
|
||||
end
|
||||
|
||||
# @return [String] a formatted representation (based on the configuration of
|
||||
# the object).
|
||||
def to_s
|
||||
print_to_output(''.dup, false)
|
||||
end
|
||||
|
||||
# @return [String] a (possibly highlighted) copy of the source code.
|
||||
def highlighted
|
||||
print_to_output(''.dup, true)
|
||||
end
|
||||
|
||||
# Writes a formatted representation (based on the configuration of the
|
||||
# object) to the given output, which must respond to `#<<`.
|
||||
def print_to_output(output, color = false)
|
||||
@lines.each do |loc|
|
||||
loc = loc.dup
|
||||
loc.colorize(@code_type) if color
|
||||
loc.add_line_number(max_lineno_width, color) if @with_line_numbers
|
||||
loc.add_marker(@marker_lineno) if @with_marker
|
||||
loc.indent(@indentation_num) if @with_indentation
|
||||
output << loc.line
|
||||
output << "\n"
|
||||
end
|
||||
output
|
||||
end
|
||||
|
||||
# Get the comment that describes the expression on the given line number.
|
||||
#
|
||||
# @param [Integer] line_number (1-based)
|
||||
# @return [String] the code.
|
||||
def comment_describing(line_number)
|
||||
self.class.comment_describing(raw, line_number)
|
||||
end
|
||||
|
||||
# Get the multiline expression that starts on the given line number.
|
||||
#
|
||||
# @param [Integer] line_number (1-based)
|
||||
# @return [String] the code.
|
||||
def expression_at(line_number, consume = 0)
|
||||
self.class.expression_at(raw, line_number, consume: consume)
|
||||
end
|
||||
|
||||
# Get the (approximate) Module.nesting at the give line number.
|
||||
#
|
||||
# @param [Integer] line_number line number starting from 1
|
||||
# @return [Array<Module>] a list of open modules.
|
||||
def nesting_at(line_number)
|
||||
Pry::Indent.nesting_at(raw, line_number)
|
||||
end
|
||||
|
||||
# Return an unformatted String of the code.
|
||||
#
|
||||
# @return [String]
|
||||
def raw
|
||||
@lines.map(&:line).join("\n") << "\n"
|
||||
end
|
||||
|
||||
# Return the number of lines stored.
|
||||
#
|
||||
# @return [Integer]
|
||||
def length
|
||||
@lines ? @lines.length : 0
|
||||
end
|
||||
|
||||
# Two `Code` objects are equal if they contain the same lines with the same
|
||||
# numbers. Otherwise, call `to_s` and `chomp` and compare as Strings.
|
||||
#
|
||||
# @param [Code, Object] other
|
||||
# @return [Boolean]
|
||||
def ==(other)
|
||||
if other.is_a?(Code)
|
||||
other_lines = other.instance_variable_get(:@lines)
|
||||
@lines.each_with_index.all? { |loc, i| loc == other_lines[i] }
|
||||
else
|
||||
to_s.chomp == other.to_s.chomp
|
||||
end
|
||||
end
|
||||
|
||||
# Forward any missing methods to the output of `#to_s`.
|
||||
def method_missing(method_name, *args, &block)
|
||||
if (string = to_s).respond_to?(method_name)
|
||||
string.__send__(method_name, *args, &block)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
undef =~ if method_defined?(:=~)
|
||||
|
||||
# Check whether String responds to missing methods.
|
||||
def respond_to_missing?(method_name, include_private = false)
|
||||
''.respond_to?(method_name, include_private) || super
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# An abstraction of the `dup.instance_eval` pattern used throughout this
|
||||
# class.
|
||||
def alter(&block)
|
||||
dup.tap { |o| o.instance_eval(&block) }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,114 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'method_source'
|
||||
|
||||
class Pry
|
||||
class CodeFile
|
||||
DEFAULT_EXT = '.rb'.freeze
|
||||
|
||||
# List of all supported languages.
|
||||
# @return [Hash]
|
||||
EXTENSIONS = {
|
||||
%w[.py] => :python,
|
||||
%w[.js] => :javascript,
|
||||
%w[.css] => :css,
|
||||
%w[.xml] => :xml,
|
||||
%w[.php] => :php,
|
||||
%w[.html] => :html,
|
||||
%w[.diff] => :diff,
|
||||
%w[.java] => :java,
|
||||
%w[.json] => :json,
|
||||
%w[.c .h] => :c,
|
||||
%w[.rhtml] => :rhtml,
|
||||
%w[.yaml .yml] => :yaml,
|
||||
%w[.cpp .hpp .cc .h .cxx] => :cpp,
|
||||
%w[.rb .ru .irbrc .gemspec .pryrc .rake] => :ruby
|
||||
}.freeze
|
||||
|
||||
FILES = {
|
||||
%w[Gemfile Rakefile Guardfile Capfile] => :ruby
|
||||
}.freeze
|
||||
|
||||
# Store the current working directory. This allows show-source etc. to work if
|
||||
# your process has changed directory since boot. [Issue #675]
|
||||
INITIAL_PWD = Dir.pwd
|
||||
|
||||
# @return [Symbol] The type of code stored in this wrapper.
|
||||
attr_reader :code_type
|
||||
|
||||
# @param [String] filename The name of a file with code to be detected
|
||||
# @param [Symbol] code_type The type of code the `filename` contains
|
||||
def initialize(filename, code_type = type_from_filename(filename))
|
||||
@filename = filename
|
||||
@code_type = code_type
|
||||
end
|
||||
|
||||
# @return [String] The code contained in the current `@filename`.
|
||||
def code
|
||||
if @filename == Pry.eval_path
|
||||
Pry.line_buffer.drop(1)
|
||||
elsif Pry::Method::Patcher.code_for(@filename)
|
||||
Pry::Method::Patcher.code_for(@filename)
|
||||
else
|
||||
path = abs_path
|
||||
@code_type = type_from_filename(path)
|
||||
File.read(path)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# @raise [MethodSource::SourceNotFoundError] if the `filename` is not
|
||||
# readable for some reason.
|
||||
# @return [String] absolute path for the given `filename`.
|
||||
def abs_path
|
||||
code_path.detect { |path| readable?(path) } ||
|
||||
raise(MethodSource::SourceNotFoundError,
|
||||
"Cannot open #{@filename.inspect} for reading.")
|
||||
end
|
||||
|
||||
# @param [String] path
|
||||
# @return [Boolean] if the path, with or without the default ext,
|
||||
# is a readable file then `true`, otherwise `false`.
|
||||
def readable?(path)
|
||||
File.readable?(path) && !File.directory?(path) ||
|
||||
File.readable?(path << DEFAULT_EXT)
|
||||
end
|
||||
|
||||
# @return [Array] All the paths that contain code that Pry can use for its
|
||||
# API's. Skips directories.
|
||||
def code_path
|
||||
[from_pwd, from_pry_init_pwd, *from_load_path]
|
||||
end
|
||||
|
||||
# @param [String] filename
|
||||
# @param [Symbol] default (:unknown) the file type to assume if none could be
|
||||
# detected.
|
||||
# @return [Symbol, nil] The SyntaxHighlighter type of a file from its
|
||||
# extension, or `nil` if `:unknown`.
|
||||
def type_from_filename(filename, default = :unknown)
|
||||
_, @code_type = EXTENSIONS.find do |k, _|
|
||||
k.any? { |ext| ext == File.extname(filename) }
|
||||
end || FILES.find do |k, _|
|
||||
k.any? { |file_name| file_name == File.basename(filename) }
|
||||
end
|
||||
|
||||
code_type || default
|
||||
end
|
||||
|
||||
# @return [String]
|
||||
def from_pwd
|
||||
File.expand_path(@filename, Dir.pwd)
|
||||
end
|
||||
|
||||
# @return [String]
|
||||
def from_pry_init_pwd
|
||||
File.expand_path(@filename, INITIAL_PWD)
|
||||
end
|
||||
|
||||
# @return [String]
|
||||
def from_load_path
|
||||
$LOAD_PATH.map { |path| File.expand_path(@filename, path) }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,73 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Code
|
||||
# Represents a range of lines in a code listing.
|
||||
#
|
||||
# @api private
|
||||
class CodeRange
|
||||
# @param [Integer] start_line
|
||||
# @param [Integer?] end_line
|
||||
def initialize(start_line, end_line = nil)
|
||||
@start_line = start_line
|
||||
@end_line = end_line
|
||||
force_set_end_line
|
||||
end
|
||||
|
||||
# @param [Array<LOC>] lines
|
||||
# @return [Range]
|
||||
def indices_range(lines)
|
||||
Range.new(*indices(lines))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :start_line
|
||||
|
||||
attr_reader :end_line
|
||||
|
||||
# If `end_line` is equal to `nil`, then calculate it from the first
|
||||
# parameter, `start_line`. Otherwise, leave it as it is.
|
||||
# @return [void]
|
||||
def force_set_end_line
|
||||
if start_line.is_a?(Range)
|
||||
set_end_line_from_range
|
||||
else
|
||||
@end_line ||= start_line
|
||||
end
|
||||
end
|
||||
|
||||
# Finds indices of `start_line` and `end_line` in the given Array of
|
||||
# +lines+.
|
||||
#
|
||||
# @param [Array<LOC>] lines
|
||||
# @return [Array<Integer>]
|
||||
def indices(lines)
|
||||
[find_start_index(lines), find_end_index(lines)]
|
||||
end
|
||||
|
||||
# @return [Integer]
|
||||
def find_start_index(lines)
|
||||
return start_line if start_line < 0
|
||||
|
||||
lines.index { |loc| loc.lineno >= start_line } || lines.length
|
||||
end
|
||||
|
||||
# @return [Integer]
|
||||
def find_end_index(lines)
|
||||
return end_line if end_line < 0
|
||||
|
||||
(lines.index { |loc| loc.lineno > end_line } || 0) - 1
|
||||
end
|
||||
|
||||
# For example, if the range is 4..10, then `start_line` would be equal to
|
||||
# 4 and `end_line` to 10.
|
||||
# @return [void]
|
||||
def set_end_line_from_range
|
||||
@end_line = start_line.last
|
||||
@end_line -= 1 if start_line.exclude_end?
|
||||
@start_line = start_line.first
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,105 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Code
|
||||
# Represents a line of code (which may, in fact, contain multiple lines if
|
||||
# the entirety was eval'd as a single unit following the `edit` command).
|
||||
#
|
||||
# A line of code is a tuple, which consists of a line and a line number. A
|
||||
# `LOC` object's state (namely, the line parameter) can be changed via
|
||||
# instance methods. `Pry::Code` heavily uses this class.
|
||||
#
|
||||
# @api private
|
||||
# @example
|
||||
# loc = LOC.new("def example\n :example\nend", 1)
|
||||
# puts loc.line
|
||||
# def example
|
||||
# :example
|
||||
# end
|
||||
# #=> nil
|
||||
#
|
||||
# loc.indent(3)
|
||||
# loc.line #=> " def example\n :example\nend"
|
||||
class LOC
|
||||
# @return [Array<String, Integer>]
|
||||
attr_reader :tuple
|
||||
|
||||
# @param [String] line The line of code.
|
||||
# @param [Integer] lineno The position of the +line+.
|
||||
def initialize(line, lineno)
|
||||
@tuple = [line.chomp, lineno.to_i]
|
||||
end
|
||||
|
||||
# @return [Boolean]
|
||||
def ==(other)
|
||||
other.tuple == tuple
|
||||
end
|
||||
|
||||
def dup
|
||||
self.class.new(line, lineno)
|
||||
end
|
||||
|
||||
# @return [String]
|
||||
def line
|
||||
tuple.first
|
||||
end
|
||||
|
||||
# @return [Integer]
|
||||
def lineno
|
||||
tuple.last
|
||||
end
|
||||
|
||||
# Paints the `line` of code.
|
||||
#
|
||||
# @param [Symbol] code_type
|
||||
# @return [void]
|
||||
def colorize(code_type)
|
||||
tuple[0] = SyntaxHighlighter.highlight(line, code_type)
|
||||
end
|
||||
|
||||
# Prepends the line number `lineno` to the `line`.
|
||||
#
|
||||
# @param [Integer] max_width
|
||||
# @return [void]
|
||||
def add_line_number(max_width = 0, color = false)
|
||||
padded = lineno.to_s.rjust(max_width)
|
||||
colorized_lineno =
|
||||
if color
|
||||
Pry::Helpers::BaseHelpers.colorize_code(padded)
|
||||
else
|
||||
padded
|
||||
end
|
||||
properly_padded_line = handle_multiline_entries_from_edit_command(line, max_width)
|
||||
tuple[0] = "#{colorized_lineno}: #{properly_padded_line}"
|
||||
end
|
||||
|
||||
# Prepends a marker "=>" or an empty marker to the +line+.
|
||||
#
|
||||
# @param [Integer] marker_lineno If it is equal to the `lineno`, then
|
||||
# prepend a hashrocket. Otherwise, an empty marker.
|
||||
# @return [void]
|
||||
def add_marker(marker_lineno)
|
||||
tuple[0] =
|
||||
if lineno == marker_lineno
|
||||
" => #{line}"
|
||||
else
|
||||
" #{line}"
|
||||
end
|
||||
end
|
||||
|
||||
# Indents the `line` with +distance+ spaces.
|
||||
#
|
||||
# @param [Integer] distance
|
||||
# @return [void]
|
||||
def indent(distance)
|
||||
tuple[0] = "#{' ' * distance}#{line}"
|
||||
end
|
||||
|
||||
def handle_multiline_entries_from_edit_command(line, max_width)
|
||||
line.split("\n").map.with_index do |inner_line, i|
|
||||
i.zero? ? inner_line : "#{' ' * (max_width + 2)}#{inner_line}"
|
||||
end.join("\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,197 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
# This class is responsible for taking a string (identifying a
|
||||
# command/class/method/etc) and returning the relevant type of object.
|
||||
# For example, if the user looks up "show-source" then a
|
||||
# `Pry::Command` will be returned. Alternatively, if the user passes in "Pry#repl" then
|
||||
# a `Pry::Method` object will be returned.
|
||||
#
|
||||
# The `CodeObject.lookup` method is responsible for 1. figuring out what kind of
|
||||
# object the user wants (applying precedence rules in doing so -- i.e methods
|
||||
# get precedence over commands with the same name) and 2. Returning
|
||||
# the appropriate object. If the user fails to provide a string
|
||||
# identifier for the object (i.e they pass in `nil` or "") then the
|
||||
# object looked up will be the 'current method' or 'current class'
|
||||
# associated with the Binding.
|
||||
#
|
||||
# TODO: This class is a clusterfuck. We need a much more robust
|
||||
# concept of what a "Code Object" really is. Currently
|
||||
# commands/classes/candidates/methods and so on just share a very
|
||||
# ill-defined interface.
|
||||
class CodeObject
|
||||
module Helpers
|
||||
# we need this helper as some Pry::Method objects can wrap Procs
|
||||
# @return [Boolean]
|
||||
def real_method_object?
|
||||
is_a?(::Method) || is_a?(::UnboundMethod)
|
||||
end
|
||||
|
||||
def c_method?
|
||||
real_method_object? && source_type == :c
|
||||
end
|
||||
|
||||
def module_with_yard_docs?
|
||||
is_a?(WrappedModule) && yard_docs?
|
||||
end
|
||||
|
||||
def command?
|
||||
is_a?(Module) && self <= Pry::Command
|
||||
end
|
||||
|
||||
# @return [Boolean] `true` if this module was defined by means of the C API,
|
||||
# `false` if it's a Ruby module.
|
||||
# @note If a module defined by C was extended with a lot of methods written
|
||||
# in Ruby, this method would fail.
|
||||
def c_module?
|
||||
return unless is_a?(WrappedModule)
|
||||
|
||||
method_locations = wrapped.methods(false).map do |m|
|
||||
wrapped.method(m).source_location
|
||||
end
|
||||
|
||||
method_locations.concat(
|
||||
wrapped.instance_methods(false).map do |m|
|
||||
wrapped.instance_method(m).source_location
|
||||
end
|
||||
)
|
||||
|
||||
c_methods = method_locations.grep(nil).count
|
||||
ruby_methods = method_locations.count - c_methods
|
||||
|
||||
c_methods > ruby_methods
|
||||
end
|
||||
end
|
||||
|
||||
include Pry::Helpers::CommandHelpers
|
||||
|
||||
class << self
|
||||
def lookup(str, pry_instance, options = {})
|
||||
co = new(str, pry_instance, options)
|
||||
|
||||
co.default_lookup || co.method_or_class_lookup ||
|
||||
co.command_lookup || co.empty_lookup
|
||||
end
|
||||
end
|
||||
|
||||
attr_accessor :str
|
||||
attr_accessor :target
|
||||
attr_accessor :pry_instance
|
||||
attr_accessor :super_level
|
||||
|
||||
def initialize(str, pry_instance, options = {})
|
||||
options = {
|
||||
super: 0
|
||||
}.merge!(options)
|
||||
|
||||
@str = str
|
||||
@pry_instance = pry_instance
|
||||
@target = pry_instance.current_context
|
||||
@super_level = options[:super]
|
||||
end
|
||||
|
||||
# TODO: just make it so find_command_by_match_or_listing doesn't raise?
|
||||
def command_lookup
|
||||
pry_instance.commands.find_command_by_match_or_listing(str)
|
||||
rescue StandardError
|
||||
nil
|
||||
end
|
||||
|
||||
# when no parameter is given (i.e CodeObject.lookup(nil)), then we
|
||||
# lookup the 'current object' from the binding.
|
||||
def empty_lookup
|
||||
return nil if str && !str.empty?
|
||||
|
||||
obj = if internal_binding?(target)
|
||||
mod = target_self.is_a?(Module) ? target_self : target_self.class
|
||||
Pry::WrappedModule(mod)
|
||||
else
|
||||
Pry::Method.from_binding(target)
|
||||
end
|
||||
|
||||
# respect the super level (i.e user might have specified a
|
||||
# --super flag to show-source)
|
||||
lookup_super(obj, super_level)
|
||||
end
|
||||
|
||||
# lookup variables and constants and `self` that are not modules
|
||||
def default_lookup
|
||||
# we skip instance methods as we want those to fall through to
|
||||
# method_or_class_lookup()
|
||||
if safe_to_evaluate?(str) && !looks_like_an_instance_method?(str)
|
||||
obj = target.eval(str)
|
||||
|
||||
# restrict to only objects we KNOW for sure support the full API
|
||||
# Do NOT support just any object that responds to source_location
|
||||
if sourcable_object?(obj)
|
||||
Pry::Method(obj)
|
||||
elsif !obj.is_a?(Module)
|
||||
Pry::WrappedModule(obj.class)
|
||||
end
|
||||
end
|
||||
rescue Pry::RescuableException
|
||||
nil
|
||||
end
|
||||
|
||||
def method_or_class_lookup
|
||||
obj =
|
||||
case str
|
||||
when /\S+\(\)\z/
|
||||
Pry::Method.from_str(str.sub(/\(\)\z/, ''), target) ||
|
||||
Pry::WrappedModule.from_str(str, target)
|
||||
else
|
||||
Pry::WrappedModule.from_str(str, target) ||
|
||||
Pry::Method.from_str(str, target)
|
||||
end
|
||||
|
||||
lookup_super(obj, super_level)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sourcable_object?(obj)
|
||||
[::Proc, ::Method, ::UnboundMethod].any? { |o| obj.is_a?(o) }
|
||||
end
|
||||
|
||||
# Returns true if `str` looks like a method, i.e Klass#method
|
||||
# We need to consider this case because method lookups should fall
|
||||
# through to the `method_or_class_lookup()` method but a
|
||||
# defined?() on a "Klass#method` string will see the `#` as a
|
||||
# comment and only evaluate the `Klass` part.
|
||||
# @param [String] str
|
||||
# @return [Boolean] Whether the string looks like an instance method.
|
||||
def looks_like_an_instance_method?(str)
|
||||
str =~ /\S#\S/
|
||||
end
|
||||
|
||||
# We use this method to decide whether code is safe to eval. Method's are
|
||||
# generally not, but everything else is.
|
||||
# TODO: is just checking != "method" enough??
|
||||
# TODO: see duplication of this method in Pry::WrappedModule
|
||||
# @param [String] str The string to lookup
|
||||
# @return [Boolean]
|
||||
def safe_to_evaluate?(str)
|
||||
return true if str.strip == "self"
|
||||
return false if str =~ /%/
|
||||
|
||||
kind = target.eval("defined?(#{str})")
|
||||
kind =~ /variable|constant/
|
||||
end
|
||||
|
||||
def target_self
|
||||
target.eval('self')
|
||||
end
|
||||
|
||||
# grab the nth (`super_level`) super of `obj
|
||||
# @param [Object] obj
|
||||
# @param [Fixnum] super_level How far up the super chain to ascend.
|
||||
def lookup_super(obj, super_level)
|
||||
return unless obj
|
||||
|
||||
sup = obj.super(super_level)
|
||||
raise Pry::CommandError, "No superclass found for #{obj.wrapped}" unless sup
|
||||
|
||||
sup
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,66 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'pp'
|
||||
require 'English'
|
||||
|
||||
class Pry
|
||||
# PP subclass for streaming inspect output in color.
|
||||
class ColorPrinter < ::PP
|
||||
Pry::SyntaxHighlighter.overwrite_coderay_comment_token!
|
||||
|
||||
def self.default(_output, value, pry_instance)
|
||||
pry_instance.pager.open do |pager|
|
||||
pager.print pry_instance.config.output_prefix
|
||||
pp(value, pager, pry_instance.output.width - 1)
|
||||
end
|
||||
end
|
||||
|
||||
def self.pp(obj, output = $DEFAULT_OUTPUT, max_width = 79)
|
||||
queue = ColorPrinter.new(output, max_width, "\n")
|
||||
queue.guard_inspect_key { queue.pp(obj) }
|
||||
queue.flush
|
||||
output << "\n"
|
||||
end
|
||||
|
||||
def pp(object)
|
||||
return super unless object.is_a?(String)
|
||||
|
||||
# Avoid calling Ruby 2.4+ String#pretty_print that prints multiline
|
||||
# Strings prettier
|
||||
text(object.inspect)
|
||||
rescue StandardError => exception
|
||||
raise if exception.is_a?(Pry::Pager::StopPaging)
|
||||
|
||||
text(highlight_object_literal(inspect_object(object)))
|
||||
end
|
||||
|
||||
def text(str, max_width = str.length)
|
||||
if str.include?("\e[")
|
||||
super("#{str}\e[0m", max_width)
|
||||
elsif str.start_with?('#<') || %w[= >].include?(str)
|
||||
super(highlight_object_literal(str), max_width)
|
||||
else
|
||||
super(SyntaxHighlighter.highlight(str), max_width)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def highlight_object_literal(object_literal)
|
||||
code = Pry::SyntaxHighlighter.keyword_token_color
|
||||
obj_color = code.start_with?("\e") ? code : "\e[0m\e[0;#{code}m"
|
||||
"#{obj_color}#{object_literal}\e[0m"
|
||||
end
|
||||
|
||||
def inspect_object(object)
|
||||
object.inspect
|
||||
rescue StandardError
|
||||
# Read the class name off of the singleton class to provide a default
|
||||
# inspect.
|
||||
singleton = class << object; self; end
|
||||
ancestors = Pry::Method.safe_send(singleton, :ancestors)
|
||||
klass = ancestors.find { |k| k != singleton }
|
||||
"#<#{klass}:0x#{object.__id__.to_s(16)}>"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,520 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'delegate'
|
||||
require 'shellwords'
|
||||
|
||||
class Pry
|
||||
# The super-class of all commands, new commands should be created by calling
|
||||
# {Pry::CommandSet#command} which creates a BlockCommand or
|
||||
# {Pry::CommandSet#create_command} which creates a ClassCommand. Please don't
|
||||
# use this class directly.
|
||||
class Command
|
||||
extend Helpers::DocumentationHelpers
|
||||
extend CodeObject::Helpers
|
||||
|
||||
include Pry::Helpers::BaseHelpers
|
||||
include Pry::Helpers::CommandHelpers
|
||||
include Pry::Helpers::Text
|
||||
|
||||
# represents a void return value for a command
|
||||
VOID_VALUE = Object.new
|
||||
|
||||
# give it a nice inspect
|
||||
def VOID_VALUE.inspect
|
||||
"void"
|
||||
end
|
||||
|
||||
# Properties of the command itself (as passed as arguments to
|
||||
# {CommandSet#command} or {CommandSet#create_command}).
|
||||
class << self
|
||||
attr_writer :block
|
||||
attr_writer :description
|
||||
attr_writer :command_options
|
||||
attr_writer :match
|
||||
|
||||
def match(arg = nil)
|
||||
if arg
|
||||
@command_options ||= default_options(arg)
|
||||
@command_options[:listing] = arg.is_a?(String) ? arg : arg.inspect
|
||||
@match = arg
|
||||
end
|
||||
@match ||= nil
|
||||
end
|
||||
|
||||
# Define or get the command's description
|
||||
def description(arg = nil)
|
||||
@description = arg if arg
|
||||
@description ||= nil
|
||||
end
|
||||
|
||||
# Define or get the command's options
|
||||
def command_options(arg = nil)
|
||||
@command_options ||= default_options(match)
|
||||
@command_options.merge!(arg) if arg
|
||||
@command_options
|
||||
end
|
||||
# backward compatibility
|
||||
alias options command_options
|
||||
alias options= command_options=
|
||||
|
||||
# Define or get the command's banner
|
||||
def banner(arg = nil)
|
||||
@banner = arg if arg
|
||||
@banner ||= description
|
||||
end
|
||||
|
||||
def block
|
||||
@block || instance_method(:process)
|
||||
end
|
||||
|
||||
def source
|
||||
file, line = block.source_location
|
||||
strip_leading_whitespace(Pry::Code.from_file(file).expression_at(line))
|
||||
end
|
||||
|
||||
def doc
|
||||
new.help
|
||||
end
|
||||
|
||||
def source_file
|
||||
Array(block.source_location).first
|
||||
end
|
||||
alias file source_file
|
||||
|
||||
def source_line
|
||||
Array(block.source_location).last
|
||||
end
|
||||
alias line source_line
|
||||
|
||||
def default_options(match)
|
||||
{
|
||||
keep_retval: false,
|
||||
argument_required: false,
|
||||
interpolate: true,
|
||||
shellwords: true,
|
||||
listing: (match.is_a?(String) ? match : match.inspect),
|
||||
use_prefix: true,
|
||||
takes_block: false
|
||||
}
|
||||
end
|
||||
|
||||
def name
|
||||
super.to_s == "" ? "#<class(Pry::Command #{match.inspect})>" : super
|
||||
end
|
||||
|
||||
def inspect
|
||||
name
|
||||
end
|
||||
|
||||
def command_name
|
||||
options[:listing]
|
||||
end
|
||||
|
||||
# Create a new command with the given properties.
|
||||
# @param [String, Regex] match The thing that triggers this command
|
||||
# @param [String] description The description to appear in `help`
|
||||
# @param [Hash] options Behavioral options (see {Pry::CommandSet#command})
|
||||
# @param [Module] helpers A module of helper functions to be included.
|
||||
# @yield optional, used for BlockCommands
|
||||
# @return [Class] (a subclass of {Pry::Command})
|
||||
def subclass(match, description, options, helpers, &block)
|
||||
klass = Class.new(self)
|
||||
klass.send(:include, helpers)
|
||||
klass.match = match
|
||||
klass.description = description
|
||||
klass.command_options = options
|
||||
klass.block = block
|
||||
klass
|
||||
end
|
||||
|
||||
# Should this command be called for the given line?
|
||||
# @param [String] val A line input at the REPL
|
||||
# @return [Boolean]
|
||||
def matches?(val)
|
||||
command_regex =~ val
|
||||
end
|
||||
|
||||
# How well does this command match the given line?
|
||||
#
|
||||
# Higher scores are better because they imply that this command matches
|
||||
# the line more closely.
|
||||
#
|
||||
# The score is calculated by taking the number of characters at the start
|
||||
# of the string that are used only to identify the command, not as part of
|
||||
# the arguments.
|
||||
#
|
||||
# @example
|
||||
# /\.(.*)/.match_score(".foo") #=> 1
|
||||
# /\.*(.*)/.match_score("...foo") #=> 3
|
||||
# 'hi'.match_score("hi there") #=> 2
|
||||
#
|
||||
# @param [String] val A line input at the REPL
|
||||
# @return [Fixnum]
|
||||
def match_score(val)
|
||||
if command_regex =~ val
|
||||
if Regexp.last_match.size > 1
|
||||
Regexp.last_match.begin(1)
|
||||
else
|
||||
Regexp.last_match.end(0)
|
||||
end
|
||||
else
|
||||
-1
|
||||
end
|
||||
end
|
||||
|
||||
def command_regex
|
||||
prefix = convert_to_regex(Pry.config.command_prefix)
|
||||
prefix = "(?:#{prefix})?" unless options[:use_prefix]
|
||||
|
||||
/\A#{prefix}#{convert_to_regex(match)}(?!\S)/
|
||||
end
|
||||
|
||||
def convert_to_regex(obj)
|
||||
case obj
|
||||
when String
|
||||
Regexp.escape(obj)
|
||||
else
|
||||
obj
|
||||
end
|
||||
end
|
||||
|
||||
# The group in which the command should be displayed in "help" output.
|
||||
# This is usually auto-generated from directory naming, but it can be
|
||||
# manually overridden if necessary.
|
||||
# Group should not be changed once it is initialized.
|
||||
def group(name = nil)
|
||||
@group ||= begin
|
||||
name || case Pry::Method(block).source_file
|
||||
when %r{/pry/.*_commands/(.*).rb}
|
||||
Regexp.last_match(1).capitalize.tr('_', " ")
|
||||
when /(pry-\w+)-([\d\.]+([\w\.]+)?)/
|
||||
name = Regexp.last_match(1)
|
||||
version = Regexp.last_match(2)
|
||||
"#{name} (v#{version})"
|
||||
when /pryrc/
|
||||
"pryrc"
|
||||
else
|
||||
"(other)"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def state
|
||||
Pry::CommandState.default.state_for(self)
|
||||
end
|
||||
end
|
||||
|
||||
# Properties of one execution of a command (passed by {Pry#run_command} as a hash of
|
||||
# context and expanded in `#initialize`
|
||||
attr_accessor :output
|
||||
attr_accessor :target
|
||||
attr_accessor :captures
|
||||
attr_accessor :eval_string
|
||||
attr_accessor :arg_string
|
||||
attr_accessor :context
|
||||
attr_accessor :command_set
|
||||
attr_accessor :hooks
|
||||
attr_accessor :pry_instance
|
||||
alias _pry_= pry_instance=
|
||||
|
||||
# The block we pass *into* a command so long as `:takes_block` is
|
||||
# not equal to `false`
|
||||
# @example
|
||||
# my-command | do
|
||||
# puts "block content"
|
||||
# end
|
||||
attr_accessor :command_block
|
||||
|
||||
# Instantiate a command, in preparation for calling it.
|
||||
# @param [Hash] context The runtime context to use with this command.
|
||||
def initialize(context = {})
|
||||
self.context = context
|
||||
self.target = context[:target]
|
||||
self.output = context[:output]
|
||||
self.eval_string = context[:eval_string]
|
||||
self.command_set = context[:command_set]
|
||||
self.hooks = context[:hooks]
|
||||
self.pry_instance = context[:pry_instance]
|
||||
end
|
||||
|
||||
# Make those properties accessible to instances
|
||||
def name
|
||||
self.class.name
|
||||
end
|
||||
|
||||
def match
|
||||
self.class.match
|
||||
end
|
||||
|
||||
def description
|
||||
self.class.description
|
||||
end
|
||||
|
||||
def block
|
||||
self.class.block
|
||||
end
|
||||
|
||||
def command_options
|
||||
self.class.options
|
||||
end
|
||||
|
||||
def command_name
|
||||
self.class.command_name
|
||||
end
|
||||
|
||||
def source
|
||||
self.class.source
|
||||
end
|
||||
|
||||
# Run a command from another command.
|
||||
# @param [String] command_string The string that invokes the command
|
||||
# @param [Array] args Further arguments to pass to the command
|
||||
# @example
|
||||
# run "show-input"
|
||||
# @example
|
||||
# run ".ls"
|
||||
# @example
|
||||
# run "amend-line", "5", 'puts "hello world"'
|
||||
def run(command_string, *args)
|
||||
command_string = pry_instance.config.command_prefix.to_s + command_string
|
||||
complete_string = "#{command_string} #{args.join(' ')}".rstrip
|
||||
command_set.process_line(complete_string, context)
|
||||
end
|
||||
|
||||
def commands
|
||||
command_set.to_hash
|
||||
end
|
||||
|
||||
def void
|
||||
VOID_VALUE
|
||||
end
|
||||
|
||||
def _pry_
|
||||
Pry::Warning.warn('_pry_ is deprecated, use pry_instance instead')
|
||||
pry_instance
|
||||
end
|
||||
|
||||
# @return [Object] The value of `self` inside the `target` binding.
|
||||
def target_self
|
||||
target.eval('self')
|
||||
end
|
||||
|
||||
# @return [Hash] Pry commands can store arbitrary state
|
||||
# here. This state persists between subsequent command invocations.
|
||||
# All state saved here is unique to the command, it does not
|
||||
# need to be namespaced.
|
||||
# @example
|
||||
# state.my_state = "my state" # this will not conflict with any
|
||||
# # `state.my_state` used in another command.
|
||||
def state
|
||||
self.class.state
|
||||
end
|
||||
|
||||
# Revaluate the string (str) and perform interpolation.
|
||||
# @param [String] str The string to reevaluate with interpolation.
|
||||
#
|
||||
# @return [String] The reevaluated string with interpolations
|
||||
# applied (if any).
|
||||
def interpolate_string(str)
|
||||
dumped_str = str.dump
|
||||
if dumped_str.gsub!(/\\\#\{/, '#{')
|
||||
target.eval(dumped_str)
|
||||
else
|
||||
str
|
||||
end
|
||||
end
|
||||
|
||||
# Display a warning if a command collides with a local/method in
|
||||
# the current scope.
|
||||
def check_for_command_collision(command_match, arg_string)
|
||||
collision_type = target.eval("defined?(#{command_match})")
|
||||
collision_type ||= 'local-variable' if arg_string =~ %r{\A\s*[-+*/%&|^]*=}
|
||||
|
||||
if collision_type
|
||||
output.puts(
|
||||
"#{Helpers::Text.bold('WARNING:')} Calling Pry command '#{command_match}', " \
|
||||
"which conflicts with a #{collision_type}.\n\n"
|
||||
)
|
||||
end
|
||||
rescue Pry::RescuableException # rubocop:disable Lint/HandleExceptions
|
||||
end
|
||||
|
||||
# Extract necessary information from a line that Command.matches? this
|
||||
# command.
|
||||
#
|
||||
# Returns an array of four elements:
|
||||
#
|
||||
# ```
|
||||
# [String] the portion of the line that matched with the Command match
|
||||
# [String] a string of all the arguments (i.e. everything but the match)
|
||||
# [Array] the captures caught by the command_regex
|
||||
# [Array] the arguments obtained by splitting the arg_string
|
||||
# ```
|
||||
#
|
||||
# @param [String] val The line of input
|
||||
# @return [Array]
|
||||
def tokenize(val)
|
||||
val = interpolate_string(val) if command_options[:interpolate]
|
||||
|
||||
self.class.command_regex =~ val
|
||||
|
||||
# please call Command.matches? before Command#call_safely
|
||||
unless Regexp.last_match
|
||||
raise CommandError, "fatal: called a command which didn't match?!"
|
||||
end
|
||||
|
||||
captures = Regexp.last_match.captures
|
||||
pos = Regexp.last_match.end(0)
|
||||
|
||||
arg_string = val[pos..-1]
|
||||
|
||||
# remove the one leading space if it exists
|
||||
arg_string.slice!(0) if arg_string.start_with?(" ")
|
||||
|
||||
# process and pass a block if one is found
|
||||
pass_block(arg_string) if command_options[:takes_block]
|
||||
|
||||
args =
|
||||
if arg_string
|
||||
if command_options[:shellwords]
|
||||
Shellwords.shellwords(arg_string)
|
||||
else
|
||||
arg_string.split(" ")
|
||||
end
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
[val[0..pos].rstrip, arg_string, captures, args]
|
||||
end
|
||||
|
||||
# Process a line that Command.matches? this command.
|
||||
# @param [String] line The line to process
|
||||
# @return [Object, Command::VOID_VALUE]
|
||||
def process_line(line)
|
||||
command_match, arg_string, captures, args = tokenize(line)
|
||||
|
||||
if Pry.config.collision_warning
|
||||
check_for_command_collision(command_match, arg_string)
|
||||
end
|
||||
|
||||
self.arg_string = arg_string
|
||||
self.captures = captures
|
||||
|
||||
call_safely(*(captures + args))
|
||||
end
|
||||
|
||||
# Generate completions for this command
|
||||
#
|
||||
# @param [String] _search The line typed so far
|
||||
# @return [Array<String>] Completion words
|
||||
def complete(_search)
|
||||
[]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Run the command with the given `args`.
|
||||
#
|
||||
# This is a public wrapper around `#call` which ensures all preconditions
|
||||
# are met.
|
||||
#
|
||||
# @param [Array<String>] args The arguments to pass to this command.
|
||||
# @return [Object] The return value of the `#call` method, or
|
||||
# {Command::VOID_VALUE}.
|
||||
def call_safely(*args)
|
||||
if command_options[:argument_required] && args.empty?
|
||||
raise CommandError, "The command '#{command_name}' requires an argument."
|
||||
end
|
||||
|
||||
ret = use_unpatched_symbol do
|
||||
call_with_hooks(*args)
|
||||
end
|
||||
command_options[:keep_retval] ? ret : void
|
||||
end
|
||||
|
||||
def use_unpatched_symbol
|
||||
call_method = Symbol.method_defined?(:call) && Symbol.instance_method(:call)
|
||||
Symbol.class_eval { undef :call } if call_method
|
||||
yield
|
||||
ensure
|
||||
Symbol.instance_eval { define_method(:call, call_method) } if call_method
|
||||
end
|
||||
|
||||
# Pass a block argument to a command.
|
||||
# @param [String] arg_string The arguments (as a string) passed to the command.
|
||||
# We inspect these for a '| do' or a '| {' and if we find it we use it
|
||||
# to start a block input sequence. Once we have a complete
|
||||
# block, we save it to an accessor that can be retrieved from the command context.
|
||||
# Note that if we find the '| do' or '| {' we delete this and the
|
||||
# elements following it from `arg_string`.
|
||||
def pass_block(arg_string)
|
||||
# Workaround for weird JRuby bug where rindex in this case can return nil
|
||||
# even when there's a match.
|
||||
arg_string.scan(/\| *(?:do|\{)/)
|
||||
block_index = $LAST_MATCH_INFO && $LAST_MATCH_INFO.offset(0)[0]
|
||||
|
||||
return unless block_index
|
||||
|
||||
block_init_string = arg_string.slice!(block_index..-1)[1..-1]
|
||||
prime_string = "proc #{block_init_string}\n"
|
||||
|
||||
block_string =
|
||||
if !Pry::Code.complete_expression?(prime_string)
|
||||
pry_instance.r(target, prime_string)
|
||||
else
|
||||
prime_string
|
||||
end
|
||||
|
||||
begin
|
||||
self.command_block = target.eval(block_string)
|
||||
rescue Pry::RescuableException
|
||||
raise CommandError, "Incomplete block definition."
|
||||
end
|
||||
end
|
||||
|
||||
def find_hooks(event)
|
||||
event_name = "#{event}_#{command_name}"
|
||||
(hooks || Pry.hooks || self.class.hooks).get_hooks(event_name).values
|
||||
end
|
||||
|
||||
def before_hooks
|
||||
find_hooks('before')
|
||||
end
|
||||
|
||||
def after_hooks
|
||||
find_hooks('after')
|
||||
end
|
||||
|
||||
# Run the `#call` method and all the registered hooks.
|
||||
# @param [Array<String>] args The arguments to `#call`
|
||||
# @return [Object] The return value from `#call`
|
||||
def call_with_hooks(*args)
|
||||
before_hooks.each { |block| instance_exec(*args, &block) }
|
||||
|
||||
ret = call(*args)
|
||||
|
||||
after_hooks.each do |block|
|
||||
ret = instance_exec(*args, &block)
|
||||
end
|
||||
|
||||
ret
|
||||
end
|
||||
|
||||
# Normalize method arguments according to its arity.
|
||||
#
|
||||
# @param [Integer] method
|
||||
# @param [Array] args
|
||||
# @return [Array] a (possibly shorter) array of the arguments to pass
|
||||
def normalize_method_args(method, args)
|
||||
case method.arity
|
||||
when -1
|
||||
args
|
||||
when 0
|
||||
[]
|
||||
else
|
||||
args.values_at(*(0..(method.arity - 1)).to_a)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,418 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class NoCommandError < StandardError
|
||||
def initialize(match, owner)
|
||||
super "Command '#{match}' not found in command set #{owner}"
|
||||
end
|
||||
end
|
||||
|
||||
# This class is used to create sets of commands. Commands can be imported from
|
||||
# different sets, aliased, removed, etc.
|
||||
class CommandSet
|
||||
include Enumerable
|
||||
include Pry::Helpers::BaseHelpers
|
||||
attr_reader :helper_module
|
||||
|
||||
# @param [Array<Commandset>] imported_sets
|
||||
# Sets which will be imported automatically
|
||||
# @yield Optional block run to define commands
|
||||
def initialize(*imported_sets, &block)
|
||||
@commands = {}
|
||||
@helper_module = Module.new
|
||||
import(*imported_sets)
|
||||
instance_eval(&block) if block
|
||||
end
|
||||
|
||||
# Defines a new Pry command.
|
||||
# @param [String, Regexp] match The start of invocations of this command.
|
||||
# @param [String] description A description of the command.
|
||||
# @param [Hash] options The optional configuration parameters.
|
||||
# @option options [Boolean] :keep_retval Whether or not to use return value
|
||||
# of the block for return of `command` or just to return `nil`
|
||||
# (the default).
|
||||
# @option options [Boolean] :interpolate Whether string #{} based
|
||||
# interpolation is applied to the command arguments before
|
||||
# executing the command. Defaults to true.
|
||||
# @option options [String] :listing The listing name of the
|
||||
# command. That is the name by which the command is looked up by
|
||||
# help and by show-source. Necessary for commands with regex matches.
|
||||
# @option options [Boolean] :use_prefix Whether the command uses
|
||||
# `Pry.config.command_prefix` prefix (if one is defined). Defaults
|
||||
# to true.
|
||||
# @option options [Boolean] :shellwords Whether the command's arguments
|
||||
# should be split using Shellwords instead of just split on spaces.
|
||||
# Defaults to true.
|
||||
# @yield The action to perform. The parameters in the block
|
||||
# determines the parameters the command will receive. All
|
||||
# parameters passed into the block will be strings. Successive
|
||||
# command parameters are separated by whitespace at the Pry prompt.
|
||||
# @example
|
||||
# MyCommands = Pry::CommandSet.new do
|
||||
# command "greet", "Greet somebody" do |name|
|
||||
# puts "Good afternoon #{name.capitalize}!"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# # From pry:
|
||||
# # pry(main)> pry_instance.commands = MyCommands
|
||||
# # pry(main)> greet john
|
||||
# # Good afternoon John!
|
||||
# # pry(main)> help greet
|
||||
# # Greet somebody
|
||||
# @example Regexp command
|
||||
# MyCommands = Pry::CommandSet.new do
|
||||
# command(
|
||||
# /number-(\d+)/, "number-N regex command", :listing => "number"
|
||||
# ) do |num, name|
|
||||
# puts "hello #{name}, nice number: #{num}"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# # From pry:
|
||||
# # pry(main)> pry_instance.commands = MyCommands
|
||||
# # pry(main)> number-10 john
|
||||
# # hello john, nice number: 10
|
||||
# # pry(main)> help number
|
||||
# # number-N regex command
|
||||
def block_command(match, description = "No description.", options = {}, &block)
|
||||
if description.is_a?(Hash)
|
||||
options = description
|
||||
description = "No description."
|
||||
end
|
||||
options = Pry::Command.default_options(match).merge!(options)
|
||||
|
||||
@commands[match] = Pry::BlockCommand.subclass(
|
||||
match, description, options, helper_module, &block
|
||||
)
|
||||
end
|
||||
alias command block_command
|
||||
|
||||
# Defines a new Pry command class.
|
||||
#
|
||||
# @param [String, Regexp] match The start of invocations of this command.
|
||||
# @param [String] description A description of the command.
|
||||
# @param [Hash] options The optional configuration parameters, see {#command}
|
||||
# @yield The class body's definition.
|
||||
#
|
||||
# @example
|
||||
# Pry::Commands.create_command "echo", "echo's the input", :shellwords => false do
|
||||
# def options(opt)
|
||||
# opt.banner "Usage: echo [-u | -d] <string to echo>"
|
||||
# opt.on :u, :upcase, "ensure the output is all upper-case"
|
||||
# opt.on :d, :downcase, "ensure the output is all lower-case"
|
||||
# end
|
||||
#
|
||||
# def process
|
||||
# if opts.present?(:u) && opts.present?(:d)
|
||||
# raise Pry::CommandError, "-u and -d makes no sense"
|
||||
# end
|
||||
# result = args.join(" ")
|
||||
# result.downcase! if opts.present?(:downcase)
|
||||
# result.upcase! if opts.present?(:upcase)
|
||||
# output.puts result
|
||||
# end
|
||||
# end
|
||||
#
|
||||
def create_command(match, description = "No description.", options = {}, &block)
|
||||
if description.is_a?(Hash)
|
||||
options = description
|
||||
description = "No description."
|
||||
end
|
||||
options = Pry::Command.default_options(match).merge!(options)
|
||||
|
||||
@commands[match] = Pry::ClassCommand.subclass(
|
||||
match, description, options, helper_module, &block
|
||||
)
|
||||
@commands[match].class_eval(&block)
|
||||
@commands[match]
|
||||
end
|
||||
|
||||
def each(&block)
|
||||
@commands.each(&block)
|
||||
end
|
||||
|
||||
# Removes some commands from the set
|
||||
# @param [Array<String>] searches the matches or listings of the commands
|
||||
# to remove
|
||||
def delete(*searches)
|
||||
searches.each do |search|
|
||||
cmd = find_command_by_match_or_listing(search)
|
||||
@commands.delete cmd.match
|
||||
end
|
||||
end
|
||||
|
||||
# Imports all the commands from one or more sets.
|
||||
# @param [Array<CommandSet>] sets Command sets, all of the commands of which
|
||||
# will be imported.
|
||||
# @return [Pry::CommandSet] Returns the receiver (a command set).
|
||||
def import(*sets)
|
||||
sets.each do |set|
|
||||
@commands.merge! set.to_hash
|
||||
helper_module.send :include, set.helper_module
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
# Imports some commands from a set
|
||||
# @param [CommandSet] set Set to import commands from
|
||||
# @param [Array<String>] matches Commands to import
|
||||
# @return [Pry::CommandSet] Returns the receiver (a command set).
|
||||
def import_from(set, *matches)
|
||||
helper_module.send :include, set.helper_module
|
||||
matches.each do |match|
|
||||
cmd = set.find_command_by_match_or_listing(match)
|
||||
@commands[cmd.match] = cmd
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
# @param [String, Regexp] match_or_listing The match or listing of a command.
|
||||
# of the command to retrieve.
|
||||
# @return [Command] The command object matched.
|
||||
def find_command_by_match_or_listing(match_or_listing)
|
||||
cmd = (@commands[match_or_listing] ||
|
||||
Pry::Helpers::BaseHelpers.find_command(match_or_listing, @commands))
|
||||
cmd || raise(ArgumentError, "cannot find a command: '#{match_or_listing}'")
|
||||
end
|
||||
|
||||
# Aliases a command
|
||||
# @param [String, Regex] match The match of the alias (can be a regex).
|
||||
# @param [String] action The action to be performed (typically
|
||||
# another command).
|
||||
# @param [Hash] options The optional configuration parameters,
|
||||
# accepts the same as the `command` method, but also allows the
|
||||
# command description to be passed this way too as `:desc`
|
||||
# @example Creating an alias for `ls -M`
|
||||
# Pry.config.commands.alias_command "lM", "ls -M"
|
||||
# @example Pass explicit description (overriding default).
|
||||
# Pry.config.commands.alias_command "lM", "ls -M", :desc => "cutiepie"
|
||||
def alias_command(match, action, options = {})
|
||||
(cmd = find_command(action)) || raise("command: '#{action}' not found")
|
||||
original_options = cmd.options.dup
|
||||
|
||||
options = original_options.merge!(
|
||||
desc: "Alias for `#{action}`",
|
||||
listing: match.is_a?(String) ? match : match.inspect
|
||||
).merge!(options)
|
||||
|
||||
# ensure default description is used if desc is nil
|
||||
desc = options.delete(:desc).to_s
|
||||
|
||||
c = block_command match, desc, options do |*args|
|
||||
run action, *args
|
||||
end
|
||||
|
||||
# TODO: untested. What's this about?
|
||||
c.class_eval do
|
||||
define_method(:complete) do |input|
|
||||
cmd.new(context).complete(input)
|
||||
end
|
||||
end
|
||||
|
||||
c.group "Aliases"
|
||||
|
||||
c
|
||||
end
|
||||
|
||||
# Rename a command. Accepts either match or listing for the search.
|
||||
#
|
||||
# @param [String, Regexp] new_match The new match for the command.
|
||||
# @param [String, Regexp] search The command's current match or listing.
|
||||
# @param [Hash] options The optional configuration parameters,
|
||||
# accepts the same as the `command` method, but also allows the
|
||||
# command description to be passed this way too.
|
||||
# @example Renaming the `ls` command and changing its description.
|
||||
# Pry.config.commands.rename "dir", "ls", :description => "DOS friendly ls"
|
||||
def rename_command(new_match, search, options = {})
|
||||
cmd = find_command_by_match_or_listing(search)
|
||||
|
||||
options = {
|
||||
listing: new_match,
|
||||
description: cmd.description
|
||||
}.merge!(options)
|
||||
|
||||
@commands[new_match] = cmd.dup
|
||||
@commands[new_match].match = new_match
|
||||
@commands[new_match].description = options.delete(:description)
|
||||
@commands[new_match].options.merge!(options)
|
||||
@commands.delete(cmd.match)
|
||||
end
|
||||
|
||||
# Sets or gets the description for a command (replacing the old
|
||||
# description). Returns current description if no description
|
||||
# parameter provided.
|
||||
# @param [String, Regexp] search The command match.
|
||||
# @param [String?] description (nil) The command description.
|
||||
# @example Setting
|
||||
# MyCommands = Pry::CommandSet.new do
|
||||
# desc "help", "help description"
|
||||
# end
|
||||
# @example Getting
|
||||
# Pry.config.commands.desc "amend-line"
|
||||
def desc(search, description = nil)
|
||||
cmd = find_command_by_match_or_listing(search)
|
||||
return cmd.description unless description
|
||||
|
||||
cmd.description = description
|
||||
end
|
||||
|
||||
# @return [Array]
|
||||
# The list of commands provided by the command set.
|
||||
def list_commands
|
||||
@commands.keys
|
||||
end
|
||||
alias keys list_commands
|
||||
|
||||
def to_hash
|
||||
@commands.dup
|
||||
end
|
||||
alias to_h to_hash
|
||||
|
||||
# Find a command that matches the given line
|
||||
# @param [String] pattern The line that might be a command invocation
|
||||
# @return [Pry::Command, nil]
|
||||
def [](pattern)
|
||||
commands = @commands.values.select do |command|
|
||||
command.matches?(pattern)
|
||||
end
|
||||
commands.max_by { |command| command.match_score(pattern) }
|
||||
end
|
||||
alias find_command []
|
||||
|
||||
#
|
||||
# Re-assign the command found at _pattern_ with _command_.
|
||||
#
|
||||
# @param [Regexp, String] pattern
|
||||
# The command to add or replace(found at _pattern_).
|
||||
#
|
||||
# @param [Pry::Command] command
|
||||
# The command to add.
|
||||
#
|
||||
# @return [Pry::Command]
|
||||
# Returns the new command (matched with "pattern".)
|
||||
#
|
||||
# @example
|
||||
# Pry.config.commands["help"] = MyHelpCommand
|
||||
#
|
||||
def []=(pattern, command)
|
||||
if command.equal?(nil)
|
||||
@commands.delete(pattern)
|
||||
return
|
||||
end
|
||||
|
||||
unless command.is_a?(Class) && command < Pry::Command
|
||||
raise TypeError, "command is not a subclass of Pry::Command"
|
||||
end
|
||||
|
||||
bind_command_to_pattern = pattern != command.match
|
||||
if bind_command_to_pattern
|
||||
command_copy = command.dup
|
||||
command_copy.match = pattern
|
||||
@commands[pattern] = command_copy
|
||||
else
|
||||
@commands[pattern] = command
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Add a command to set.
|
||||
#
|
||||
# @param [Command] command
|
||||
# a subclass of Pry::Command.
|
||||
#
|
||||
def add_command(command)
|
||||
self[command.match] = command
|
||||
end
|
||||
|
||||
# Find the command that the user might be trying to refer to.
|
||||
# @param [String] search The user's search.
|
||||
# @return [Pry::Command?]
|
||||
def find_command_for_help(search)
|
||||
find_command(search) ||
|
||||
(begin
|
||||
find_command_by_match_or_listing(search)
|
||||
rescue ArgumentError
|
||||
nil
|
||||
end)
|
||||
end
|
||||
|
||||
# Is the given line a command invocation?
|
||||
# @param [String] val
|
||||
# @return [Boolean]
|
||||
def valid_command?(val)
|
||||
!!find_command(val)
|
||||
end
|
||||
|
||||
# Process the given line to see whether it needs executing as a command.
|
||||
# @param [String] val The line to execute
|
||||
# @param [Hash] context The context to execute the commands with
|
||||
# @return [CommandSet::Result]
|
||||
def process_line(val, context = {})
|
||||
if (command = find_command(val))
|
||||
context = context.merge(command_set: self)
|
||||
retval = command.new(context).process_line(val)
|
||||
Result.new(true, retval)
|
||||
else
|
||||
Result.new(false)
|
||||
end
|
||||
end
|
||||
|
||||
# Generate completions for the user's search.
|
||||
# @param [String] search The line to search for
|
||||
# @param [Hash] context The context to create the command with
|
||||
# @return [Array<String>]
|
||||
def complete(search, context = {})
|
||||
if (command = find_command(search))
|
||||
command.new(context).complete(search)
|
||||
else
|
||||
keys = @commands.keys.select do |key|
|
||||
key.is_a?(String) && key.start_with?(search)
|
||||
end
|
||||
keys.map { |key| key + " " }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Defines helpers methods for this command sets.
|
||||
# Those helpers are only defined in this command set.
|
||||
#
|
||||
# @yield A block defining helper methods
|
||||
# @example
|
||||
# helpers do
|
||||
# def hello
|
||||
# puts "Hello!"
|
||||
# end
|
||||
#
|
||||
# include OtherModule
|
||||
# end
|
||||
def helpers(&block)
|
||||
helper_module.class_eval(&block)
|
||||
end
|
||||
end
|
||||
|
||||
# Wraps the return result of process_commands, indicates if the
|
||||
# result IS a command and what kind of command (e.g void)
|
||||
class Result
|
||||
attr_reader :retval
|
||||
|
||||
def initialize(is_command, retval = nil)
|
||||
@is_command = is_command
|
||||
@retval = retval
|
||||
end
|
||||
|
||||
# Is the result a command?
|
||||
# @return [Boolean]
|
||||
def command?
|
||||
@is_command
|
||||
end
|
||||
|
||||
# Is the result a command and if it is, is it a void command?
|
||||
# (one that does not return a value)
|
||||
# @return [Boolean]
|
||||
def void_command?
|
||||
retval == Command::VOID_VALUE
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,36 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
# CommandState is a data structure to hold per-command state.
|
||||
#
|
||||
# Pry commands can store arbitrary state here. This state persists between
|
||||
# subsequent command invocations. All state saved here is unique to the
|
||||
# command.
|
||||
#
|
||||
# @since v0.13.0
|
||||
# @api private
|
||||
class CommandState
|
||||
def self.default
|
||||
@default ||= new
|
||||
end
|
||||
|
||||
def initialize
|
||||
@command_state = {}
|
||||
end
|
||||
|
||||
def state_for(command_class)
|
||||
@command_state[command_class] ||= command_struct(command_class)
|
||||
end
|
||||
|
||||
def reset(command_class)
|
||||
@command_state[command_class] = command_struct(command_class)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def command_struct(command_class)
|
||||
Struct.new(:command, *command_class.command_options[:state])
|
||||
.new(command: command_class)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,103 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class AmendLine < Pry::ClassCommand
|
||||
match(/amend-line(?: (-?\d+)(?:\.\.(-?\d+))?)?/)
|
||||
group 'Editing'
|
||||
description 'Amend a line of input in multi-line mode.'
|
||||
command_options interpolate: false, listing: 'amend-line'
|
||||
|
||||
banner <<-'BANNER'
|
||||
Amend a line of input in multi-line mode. `amend-line N`, where the 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.
|
||||
|
||||
amend-line 1 puts 'new' # replace line 1
|
||||
amend-line 1..4 ! # delete lines 1..4
|
||||
amend-line 3 >puts 'bye' # insert before line 3
|
||||
amend-line puts 'appended' # no line number modifies immediately preceding line
|
||||
BANNER
|
||||
|
||||
def process
|
||||
raise CommandError, "No input to amend." if eval_string.empty?
|
||||
|
||||
eval_string.replace(amend_input)
|
||||
run "fix-indent"
|
||||
run "show-input"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# @return [String] A new string with the amendments applied to it.
|
||||
def amend_input
|
||||
input_array = eval_string.each_line.to_a
|
||||
|
||||
if arg_string == "!"
|
||||
delete_from_array(input_array, line_range)
|
||||
elsif arg_string.start_with?(">")
|
||||
insert_into_array(input_array, line_range)
|
||||
else
|
||||
replace_in_array(input_array, line_range)
|
||||
end
|
||||
|
||||
input_array.join
|
||||
end
|
||||
|
||||
def delete_from_array(array, range)
|
||||
array.slice!(range)
|
||||
end
|
||||
|
||||
def insert_into_array(array, range)
|
||||
insert_slot = Array(range).first
|
||||
array.insert(insert_slot, arg_string[1..-1] << "\n")
|
||||
end
|
||||
|
||||
def replace_in_array(array, range)
|
||||
array[range] = arg_string + "\n"
|
||||
end
|
||||
|
||||
# @return [Fixnum] The number of lines currently in `eval_string` (the
|
||||
# input buffer)
|
||||
def line_count
|
||||
eval_string.lines.count
|
||||
end
|
||||
|
||||
# Returns the (one-indexed) start and end lines given by the user.
|
||||
# The lines in this range will be affected by the `amend-line`.
|
||||
# Returns `nil` if no lines were specified by the user.
|
||||
# @return [Array<Fixnum>, nil]
|
||||
def start_and_end_line_number
|
||||
start_line_number, end_line_number = args
|
||||
end_line_number ||= start_line_number.to_i
|
||||
|
||||
[start_line_number.to_i, end_line_number.to_i] if start_line_number
|
||||
end
|
||||
|
||||
# Takes two numbers that are 1-indexed, and returns a range (or
|
||||
# number) that is 0-indexed. 1-indexed means the first element is
|
||||
# identified by 1 rather than by 0 (as is the case for Ruby arrays).
|
||||
# @param [Fixnum] start_line_number One-indexed number.
|
||||
# @param [Fixnum] end_line_number One-indexed number.
|
||||
# @return [Range] The zero-indexed range.
|
||||
def zero_indexed_range_from_one_indexed_numbers(start_line_number, end_line_number)
|
||||
# FIXME: one_index_number is a horrible name for this method
|
||||
one_index_number(start_line_number)..one_index_number(end_line_number)
|
||||
end
|
||||
|
||||
# The lines (or line) that will be modified by the `amend-line`.
|
||||
# @return [Range, Fixnum] The lines or line.
|
||||
def line_range
|
||||
start_line_number, end_line_number = start_and_end_line_number
|
||||
if start_line_number
|
||||
zero_indexed_range_from_one_indexed_numbers(start_line_number,
|
||||
end_line_number)
|
||||
else
|
||||
line_count - 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::AmendLine)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,24 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Bang < Pry::ClassCommand
|
||||
match(/^\s*!\s*$/)
|
||||
group 'Editing'
|
||||
description 'Clear the input buffer.'
|
||||
command_options use_prefix: false, listing: '!'
|
||||
|
||||
banner <<-'BANNER'
|
||||
Clear the input buffer. Useful if the parsing process goes wrong and you get
|
||||
stuck in the read loop.
|
||||
BANNER
|
||||
|
||||
def process
|
||||
output.puts 'Input buffer cleared!'
|
||||
eval_string.replace('')
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::Bang)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class BangPry < Pry::ClassCommand
|
||||
match '!pry'
|
||||
group 'Navigating Pry'
|
||||
description 'Start a Pry session on current self.'
|
||||
|
||||
banner <<-'BANNER'
|
||||
Start a Pry session on current self. Also works mid multi-line expression.
|
||||
BANNER
|
||||
|
||||
def process
|
||||
target.pry
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::BangPry)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,70 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Cat < Pry::ClassCommand
|
||||
match 'cat'
|
||||
group 'Input and Output'
|
||||
description "Show code from a file, Pry's input buffer, or the last exception."
|
||||
|
||||
banner <<-'BANNER'
|
||||
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.
|
||||
BANNER
|
||||
|
||||
def options(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
|
||||
end
|
||||
|
||||
def process
|
||||
output =
|
||||
if opts.present?(:ex)
|
||||
ExceptionFormatter.new(
|
||||
pry_instance.last_exception, pry_instance, opts
|
||||
).format
|
||||
elsif opts.present?(:in)
|
||||
InputExpressionFormatter.new(pry_instance.input_ring, opts).format
|
||||
else
|
||||
FileFormatter.new(args.first, pry_instance, opts).format
|
||||
end
|
||||
|
||||
pry_instance.pager.page output
|
||||
end
|
||||
|
||||
def complete(search)
|
||||
super | load_path_completions
|
||||
end
|
||||
|
||||
def load_path_completions
|
||||
$LOAD_PATH.flat_map do |path|
|
||||
Dir[path + '/**/*'].map do |f|
|
||||
next if File.directory?(f)
|
||||
|
||||
f.sub!(path + '/', '')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::Cat)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,32 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Cat
|
||||
class AbstractFormatter
|
||||
include Pry::Helpers::CommandHelpers
|
||||
include Pry::Helpers::BaseHelpers
|
||||
|
||||
private
|
||||
|
||||
def decorate(content)
|
||||
content.code_type = code_type
|
||||
content.between(*between_lines)
|
||||
.with_line_numbers(use_line_numbers?).highlighted
|
||||
end
|
||||
|
||||
def code_type
|
||||
opts[:type] || :ruby
|
||||
end
|
||||
|
||||
def use_line_numbers?
|
||||
opts.present?(:'line-numbers') || opts.present?(:ex)
|
||||
end
|
||||
|
||||
def between_lines
|
||||
[opts[:start] || 1, opts[:end] || -1]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,90 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Cat
|
||||
class ExceptionFormatter < AbstractFormatter
|
||||
attr_reader :ex
|
||||
attr_reader :opts
|
||||
attr_reader :pry_instance
|
||||
include Pry::Helpers::Text
|
||||
|
||||
def initialize(exception, pry_instance, opts)
|
||||
@ex = exception
|
||||
@opts = opts
|
||||
@pry_instance = pry_instance
|
||||
end
|
||||
|
||||
def format
|
||||
check_for_errors
|
||||
set_file_and_dir_locals(
|
||||
backtrace_file, pry_instance, pry_instance.current_context
|
||||
)
|
||||
code = decorate(
|
||||
Pry::Code.from_file(backtrace_file)
|
||||
.between(*start_and_end_line_for_code_window)
|
||||
.with_marker(backtrace_line)
|
||||
)
|
||||
"#{header}#{code}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def code_window_size
|
||||
pry_instance.config.default_window_size || 5
|
||||
end
|
||||
|
||||
def backtrace_level
|
||||
@backtrace_level ||=
|
||||
begin
|
||||
bl =
|
||||
if opts[:ex].nil?
|
||||
ex.bt_index
|
||||
else
|
||||
ex.bt_index = absolute_index_number(opts[:ex], ex.backtrace.size)
|
||||
end
|
||||
|
||||
increment_backtrace_level
|
||||
bl
|
||||
end
|
||||
end
|
||||
|
||||
def increment_backtrace_level
|
||||
ex.inc_bt_index
|
||||
end
|
||||
|
||||
def backtrace_file
|
||||
Array(ex.bt_source_location_for(backtrace_level)).first
|
||||
end
|
||||
|
||||
def backtrace_line
|
||||
Array(ex.bt_source_location_for(backtrace_level)).last
|
||||
end
|
||||
|
||||
def check_for_errors
|
||||
raise CommandError, "No exception found." unless ex
|
||||
return if backtrace_file
|
||||
|
||||
raise CommandError, "The given backtrace level is out of bounds."
|
||||
end
|
||||
|
||||
def start_and_end_line_for_code_window
|
||||
start_line = backtrace_line - code_window_size
|
||||
start_line = 1 if start_line < 1
|
||||
|
||||
[start_line, backtrace_line + code_window_size]
|
||||
end
|
||||
|
||||
def header
|
||||
unindent(
|
||||
"#{bold 'Exception:'} #{ex.class}: #{ex.message}\n" \
|
||||
"--\n" \
|
||||
"#{bold('From:')} #{backtrace_file}:#{backtrace_line} @ " \
|
||||
"#{bold("level: #{backtrace_level}")} of backtrace " \
|
||||
"(of #{ex.backtrace.size - 1}).\n\n"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,77 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Cat
|
||||
class FileFormatter < AbstractFormatter
|
||||
attr_reader :file_with_embedded_line
|
||||
attr_reader :opts
|
||||
attr_reader :pry_instance
|
||||
|
||||
def initialize(file_with_embedded_line, pry_instance, opts)
|
||||
unless file_with_embedded_line
|
||||
raise CommandError, "Must provide a filename, --in, or --ex."
|
||||
end
|
||||
|
||||
@file_with_embedded_line = file_with_embedded_line
|
||||
@opts = opts
|
||||
@pry_instance = pry_instance
|
||||
@code_from_file = Pry::Code.from_file(file_name)
|
||||
end
|
||||
|
||||
def format
|
||||
set_file_and_dir_locals(file_name, pry_instance, pry_instance.current_context)
|
||||
decorate(@code_from_file)
|
||||
end
|
||||
|
||||
def file_and_line
|
||||
file_name, line_num = file_with_embedded_line.split(%r{:(?!/|\\)})
|
||||
|
||||
[file_name, line_num ? line_num.to_i : nil]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def file_name
|
||||
file_and_line.first
|
||||
end
|
||||
|
||||
def line_number
|
||||
file_and_line.last
|
||||
end
|
||||
|
||||
def code_window_size
|
||||
pry_instance.config.default_window_size || 7
|
||||
end
|
||||
|
||||
def decorate(content)
|
||||
if line_number
|
||||
super(content.around(line_number, code_window_size))
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def code_type
|
||||
opts[:type] || detect_code_type_from_file(file_name)
|
||||
end
|
||||
|
||||
def detect_code_type_from_file(file_name)
|
||||
code_type = @code_from_file.code_type
|
||||
|
||||
if code_type == :unknown
|
||||
name = File.basename(file_name).split('.', 2).first
|
||||
case name
|
||||
when "Rakefile", "Gemfile"
|
||||
:ruby
|
||||
else
|
||||
:text
|
||||
end
|
||||
else
|
||||
code_type
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,48 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Cat
|
||||
class InputExpressionFormatter < AbstractFormatter
|
||||
attr_accessor :input_expressions
|
||||
attr_accessor :opts
|
||||
|
||||
def initialize(input_expressions, opts)
|
||||
@input_expressions = input_expressions
|
||||
@opts = opts
|
||||
end
|
||||
|
||||
def format
|
||||
raise CommandError, "No input expressions!" if numbered_input_items.empty?
|
||||
|
||||
if numbered_input_items.length > 1
|
||||
content = ''
|
||||
numbered_input_items.each do |i, s|
|
||||
content += "#{Helpers::Text.bold(i.to_s)}:\n"
|
||||
content += decorate(Pry::Code(s).with_indentation(2)).to_s
|
||||
end
|
||||
|
||||
content
|
||||
else
|
||||
decorate(Pry::Code(selected_input_items.first))
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def selected_input_items
|
||||
input_expressions[normalized_expression_range] || []
|
||||
end
|
||||
|
||||
def numbered_input_items
|
||||
@numbered_input_items ||= normalized_expression_range.zip(selected_input_items)
|
||||
.reject { |_, s| s.nil? || s == "" }
|
||||
end
|
||||
|
||||
def normalized_expression_range
|
||||
absolute_index_range(opts[:i], input_expressions.count)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,48 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Cd < Pry::ClassCommand
|
||||
match 'cd'
|
||||
group 'Context'
|
||||
description 'Move into a new context (object or scope).'
|
||||
|
||||
banner <<-'BANNER'
|
||||
Usage: cd [OPTIONS] [--help]
|
||||
|
||||
Move into new context (object or scope). As in UNIX shells use `cd ..` to go
|
||||
back, `cd /` to return to Pry top-level and `cd -` to toggle between last two
|
||||
scopes. Complex syntax (e.g `cd ../@x/@y`) also supported.
|
||||
|
||||
cd @x
|
||||
cd ..
|
||||
cd /
|
||||
cd -
|
||||
|
||||
https://github.com/pry/pry/wiki/State-navigation#wiki-Changing_scope
|
||||
BANNER
|
||||
|
||||
command_options state: %i[old_stack]
|
||||
|
||||
def process
|
||||
state.old_stack ||= []
|
||||
|
||||
if arg_string.strip == "-"
|
||||
unless state.old_stack.empty?
|
||||
pry_instance.binding_stack, state.old_stack =
|
||||
state.old_stack, pry_instance.binding_stack
|
||||
end
|
||||
else
|
||||
stack = ObjectPath.new(arg_string, pry_instance.binding_stack).resolve
|
||||
|
||||
if stack && stack != pry_instance.binding_stack
|
||||
state.old_stack = pry_instance.binding_stack
|
||||
pry_instance.binding_stack = stack
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::Cd)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,34 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class ChangeInspector < Pry::ClassCommand
|
||||
match 'change-inspector'
|
||||
group 'Input and Output'
|
||||
description 'Change the current inspector proc.'
|
||||
command_options argument_required: true
|
||||
banner <<-BANNER
|
||||
Usage: change-inspector NAME
|
||||
|
||||
Change the proc used to print return values. See list-inspectors for a list
|
||||
of available procs and a short description of what each one does.
|
||||
BANNER
|
||||
|
||||
def process(inspector)
|
||||
unless inspector_map.key?(inspector)
|
||||
raise Pry::CommandError, "'#{inspector}' isn't a known inspector!"
|
||||
end
|
||||
|
||||
pry_instance.print = inspector_map[inspector][:value]
|
||||
output.puts "Switched to the '#{inspector}' inspector!"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def inspector_map
|
||||
Pry::Inspector::MAP
|
||||
end
|
||||
Pry::Commands.add_command(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,51 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class ChangePrompt < Pry::ClassCommand
|
||||
match 'change-prompt'
|
||||
group 'Input and Output'
|
||||
description 'Change the current prompt.'
|
||||
command_options argument_required: true
|
||||
banner <<-BANNER
|
||||
Usage: change-prompt [OPTIONS] [NAME]
|
||||
|
||||
Change the current prompt.
|
||||
BANNER
|
||||
|
||||
def options(opt)
|
||||
opt.on(:l, :list, 'List the available prompts')
|
||||
end
|
||||
|
||||
def process(prompt)
|
||||
if opts.present?(:l)
|
||||
list_prompts
|
||||
else
|
||||
change_prompt(prompt)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def list_prompts
|
||||
prompts = Pry::Prompt.all.map do |name, prompt|
|
||||
"#{bold(name)}#{red(' (selected)') if pry_instance.prompt == prompt}\n" +
|
||||
prompt.description
|
||||
end
|
||||
output.puts(prompts.join("\n" * 2))
|
||||
end
|
||||
|
||||
def change_prompt(prompt)
|
||||
if Pry::Prompt[prompt]
|
||||
pry_instance.prompt = Pry::Prompt[prompt]
|
||||
else
|
||||
raise Pry::CommandError,
|
||||
"'#{prompt}' isn't a known prompt. Run `change-prompt --list` " \
|
||||
"to see the list of known prompts."
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class ClearScreen < Pry::ClassCommand
|
||||
match 'clear-screen'
|
||||
group 'Input and Output'
|
||||
description 'Clear the contents of the screen/window Pry is running in.'
|
||||
|
||||
def process
|
||||
if Pry::Helpers::Platform.windows?
|
||||
pry_instance.config.system.call(pry_instance.output, 'cls', pry_instance)
|
||||
else
|
||||
pry_instance.config.system.call(pry_instance.output, 'clear', pry_instance)
|
||||
end
|
||||
end
|
||||
Pry::Commands.add_command(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,182 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class CodeCollector
|
||||
include Helpers::CommandHelpers
|
||||
|
||||
attr_reader :args
|
||||
attr_reader :opts
|
||||
attr_reader :pry_instance
|
||||
|
||||
# The name of the explicitly given file (if any).
|
||||
attr_accessor :file
|
||||
|
||||
class << self
|
||||
attr_accessor :input_expression_ranges
|
||||
attr_accessor :output_result_ranges
|
||||
end
|
||||
|
||||
@input_expression_ranges = []
|
||||
@output_result_ranges = []
|
||||
|
||||
def initialize(args, opts, pry_instance)
|
||||
@args = args
|
||||
@opts = opts
|
||||
@pry_instance = pry_instance
|
||||
end
|
||||
|
||||
# Add the `--lines`, `-o`, `-i`, `-s`, `-d` options.
|
||||
def self.inject_options(opt)
|
||||
@input_expression_ranges = []
|
||||
@output_result_ranges = []
|
||||
|
||||
opt.on :l, :lines, "Restrict to a subset of lines. Takes a line number " \
|
||||
"or range",
|
||||
optional_argument: true, as: Range, default: 1..-1
|
||||
opt.on :o, :out, "Select lines from Pry's output result history. " \
|
||||
"Takes an index or range",
|
||||
optional_argument: true, as: Range, default: -5..-1 do |r|
|
||||
output_result_ranges << (r || (-5..-1))
|
||||
end
|
||||
opt.on :i, :in, "Select lines from Pry's input expression history. " \
|
||||
"Takes an index or range",
|
||||
optional_argument: true, as: Range, default: -5..-1 do |r|
|
||||
input_expression_ranges << (r || (-5..-1))
|
||||
end
|
||||
opt.on :s, :super, "Select the 'super' method. Can be repeated to " \
|
||||
"traverse the ancestors",
|
||||
as: :count
|
||||
opt.on :d, :doc, "Select lines from the code object's documentation"
|
||||
end
|
||||
|
||||
# The content (i.e code/docs) for the selected object.
|
||||
# If the user provided a bare code object, it returns the source.
|
||||
# If the user provided the `-i` or `-o` switches, it returns the
|
||||
# selected input/output lines joined as a string. If the user used
|
||||
# `-d CODE_OBJECT` it returns the docs for that code object.
|
||||
#
|
||||
# @return [String]
|
||||
def content
|
||||
@content ||=
|
||||
begin
|
||||
if bad_option_combination?
|
||||
raise CommandError,
|
||||
"Only one of --out, --in, --doc and CODE_OBJECT may " \
|
||||
"be specified."
|
||||
end
|
||||
|
||||
content = if opts.present?(:o)
|
||||
pry_output_content
|
||||
elsif opts.present?(:i)
|
||||
pry_input_content
|
||||
elsif opts.present?(:d)
|
||||
code_object_doc
|
||||
else
|
||||
code_object_source_or_file
|
||||
end
|
||||
|
||||
restrict_to_lines(content, line_range)
|
||||
end
|
||||
end
|
||||
|
||||
# The code object
|
||||
#
|
||||
# @return [Pry::WrappedModule, Pry::Method, Pry::Command]
|
||||
def code_object
|
||||
Pry::CodeObject.lookup(obj_name, pry_instance, super: opts[:super])
|
||||
end
|
||||
|
||||
# Given a string and a range, return the `range` lines of that
|
||||
# string.
|
||||
#
|
||||
# @param [String] content
|
||||
# @param [Range, Fixnum] range
|
||||
# @return [String] The string restricted to the given range
|
||||
def restrict_to_lines(content, range)
|
||||
Array(content.lines.to_a[range]).join
|
||||
end
|
||||
|
||||
# The selected `pry_instance.output_ring` as a string, as specified by
|
||||
# the `-o` switch.
|
||||
#
|
||||
# @return [String]
|
||||
def pry_output_content
|
||||
pry_array_content_as_string(
|
||||
pry_instance.output_ring,
|
||||
self.class.output_result_ranges,
|
||||
&:pretty_inspect
|
||||
)
|
||||
end
|
||||
|
||||
# The selected `pry_instance.input_ring` as a string, as specified by
|
||||
# the `-i` switch.
|
||||
#
|
||||
# @return [String]
|
||||
def pry_input_content
|
||||
pry_array_content_as_string(
|
||||
pry_instance.input_ring, self.class.input_expression_ranges
|
||||
) { |v| v }
|
||||
end
|
||||
|
||||
# The line range passed to `--lines`, converted to a 0-indexed range.
|
||||
def line_range
|
||||
opts.present?(:lines) ? one_index_range_or_number(opts[:lines]) : 0..-1
|
||||
end
|
||||
|
||||
# Name of the object argument
|
||||
def obj_name
|
||||
@obj_name ||= args.empty? ? "" : args.join(" ")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def bad_option_combination?
|
||||
[opts.present?(:in), opts.present?(:out),
|
||||
!args.empty?].count(true) > 1
|
||||
end
|
||||
|
||||
def pry_array_content_as_string(array, ranges)
|
||||
all = ''
|
||||
ranges.each do |range|
|
||||
if convert_to_range(range).first == 0
|
||||
raise CommandError, "Minimum value for range is 1, not 0."
|
||||
end
|
||||
|
||||
ranged_array = Array(array[range]) || []
|
||||
ranged_array.compact.each { |v| all += yield(v) }
|
||||
end
|
||||
|
||||
all
|
||||
end
|
||||
|
||||
def code_object_doc
|
||||
(code_object && code_object.doc) || could_not_locate(obj_name)
|
||||
end
|
||||
|
||||
def code_object_source_or_file
|
||||
(code_object && code_object.source) || file_content
|
||||
end
|
||||
|
||||
def file_content
|
||||
if File.exist?(obj_name)
|
||||
# Set the file accessor.
|
||||
self.file = obj_name
|
||||
File.read(obj_name)
|
||||
else
|
||||
could_not_locate(obj_name)
|
||||
end
|
||||
end
|
||||
|
||||
def could_not_locate(name)
|
||||
raise CommandError, "Cannot locate: #{name}!"
|
||||
end
|
||||
|
||||
def convert_to_range(range)
|
||||
return range if range.is_a?(Range)
|
||||
|
||||
(range..range)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,31 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class DisablePry < Pry::ClassCommand
|
||||
match 'disable-pry'
|
||||
group 'Navigating Pry'
|
||||
description 'Stops all future calls to pry and exits the current session.'
|
||||
|
||||
banner <<-'BANNER'
|
||||
Usage: disable-pry
|
||||
|
||||
After this command is run any further calls to pry will immediately return `nil`
|
||||
without interrupting the flow of your program. This is particularly useful when
|
||||
you've debugged the problem you were having, and now wish the program to run to
|
||||
the end.
|
||||
|
||||
As alternatives, consider using `exit!` to force the current Ruby process
|
||||
to quit immediately; or using `edit -p` to remove the `binding.pry`
|
||||
from the code.
|
||||
BANNER
|
||||
|
||||
def process
|
||||
ENV['DISABLE_PRY'] = 'true'
|
||||
pry_instance.run_command "exit"
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::DisablePry)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,101 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
Pry::Commands.instance_eval do
|
||||
command(%r{!s/(.*?)/(.*?)}, "") do |source, dest|
|
||||
eval_string.gsub!(/#{source}/) { dest }
|
||||
run "show-input"
|
||||
end
|
||||
|
||||
command "east-coker", "" do
|
||||
txt = %(
|
||||
--
|
||||
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 txt
|
||||
txt
|
||||
end
|
||||
|
||||
command "cohen-poem", "" do
|
||||
txt = %(
|
||||
--
|
||||
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 txt
|
||||
txt
|
||||
end
|
||||
|
||||
command "pessoa-poem", "" do
|
||||
output.puts <<-TEXT
|
||||
--
|
||||
I've gone to bed with every feeling,
|
||||
I've been the pimp of every emotion,
|
||||
All felt sensations have bought me drinks,
|
||||
I've traded glances with every motive for every act,
|
||||
I've held hands with every urge to depart,
|
||||
..
|
||||
Rage, foam, the vastness that doesn't fit in my handkerchief,
|
||||
The dog in heat howling in the night,
|
||||
The pond from the farm going in circles around my insomnia,
|
||||
The woods as they were, on our late-afternoon walks, the rose,
|
||||
The indifferent tuft of hair, the moss, the pines,
|
||||
The rage of not containing all this, not retaining all this,
|
||||
O abstract hunger for things, impotent libido for moments,
|
||||
Intellectual orgy of feeling life!
|
||||
-- Fernando Pessoa
|
||||
TEXT
|
||||
end
|
||||
|
||||
command "test-ansi", "" do
|
||||
prev_color = pry_instance.config.color
|
||||
pry_instance.config.color = true
|
||||
|
||||
picture = unindent <<-'OUTPUT'.gsub(/[[:alpha:]!]/) { |s| red(s) }
|
||||
____ _______________________
|
||||
/ \ | A W G |
|
||||
/ O O \ | N I O N ! |
|
||||
| | | S S R I ! |
|
||||
\ \__/ / __| I K ! |
|
||||
\____/ \________________________|
|
||||
OUTPUT
|
||||
|
||||
move_up =
|
||||
if Helpers::Platform.windows_ansi?
|
||||
proc { |n| "\e[#{n}F" }
|
||||
else
|
||||
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_instance.config.color = prev_color
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,225 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Edit < Pry::ClassCommand
|
||||
match 'edit'
|
||||
group 'Editing'
|
||||
description 'Invoke the default editor on a file.'
|
||||
|
||||
banner <<-'BANNER'
|
||||
Usage: edit [--no-reload|--reload|--patch] [--line LINE] [--temp|--ex|FILE[:LINE]|OBJECT|--in N]
|
||||
|
||||
Open a text editor. When no FILE is given, edits the pry input buffer.
|
||||
When a method/module/command is given, the code is opened in an editor.
|
||||
Ensure `Pry.config.editor` or `pry_instance.config.editor` is set to your editor of choice.
|
||||
|
||||
edit sample.rb edit -p MyClass#my_method
|
||||
edit sample.rb --line 105 edit MyClass
|
||||
edit MyClass#my_method edit --ex
|
||||
edit --method edit --ex -p
|
||||
|
||||
https://github.com/pry/pry/wiki/Editor-integration#wiki-Edit_command
|
||||
BANNER
|
||||
|
||||
command_options state: %i[dynamical_ex_file]
|
||||
|
||||
def options(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 input " \
|
||||
"expression. 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 file"
|
||||
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)"
|
||||
opt.on :p, :patch, "Instead of editing the object's file, try to edit " \
|
||||
"in a tempfile and apply as a monkey patch"
|
||||
opt.on :m, :method, "Explicitly edit the _current_ method (when " \
|
||||
"inside a method context)."
|
||||
end
|
||||
|
||||
def process
|
||||
if bad_option_combination?
|
||||
raise CommandError, "Only one of --ex, --temp, --in, --method and " \
|
||||
"FILE may be specified."
|
||||
end
|
||||
|
||||
if repl_edit?
|
||||
# code defined in pry, eval'd within pry.
|
||||
repl_edit
|
||||
elsif runtime_patch?
|
||||
# patch code without persisting changes, implies future changes are patches
|
||||
apply_runtime_patch
|
||||
else
|
||||
# code stored in actual files, eval'd at top-level
|
||||
file_edit
|
||||
end
|
||||
end
|
||||
|
||||
def repl_edit?
|
||||
!opts.present?(:ex) && !opts.present?(:current) && !opts.present?(:method) &&
|
||||
filename_argument.empty?
|
||||
end
|
||||
|
||||
def repl_edit
|
||||
content = Pry::Editor.new(pry_instance).edit_tempfile_with_content(
|
||||
initial_temp_file_content,
|
||||
initial_temp_file_content.lines.count
|
||||
)
|
||||
pry_instance.eval_string = content
|
||||
Pry.history.push(content)
|
||||
end
|
||||
|
||||
def file_based_exception?
|
||||
opts.present?(:ex) && !opts.present?(:patch)
|
||||
end
|
||||
|
||||
def runtime_patch?
|
||||
!file_based_exception? &&
|
||||
(opts.present?(:patch) ||
|
||||
previously_patched?(code_object) ||
|
||||
pry_method?(code_object))
|
||||
end
|
||||
|
||||
def apply_runtime_patch
|
||||
if patch_exception?
|
||||
ExceptionPatcher.new(
|
||||
pry_instance, state, file_and_line_for_current_exception
|
||||
).perform_patch
|
||||
elsif code_object.is_a?(Pry::Method)
|
||||
code_object.redefine(
|
||||
Pry::Editor.new(pry_instance).edit_tempfile_with_content(
|
||||
code_object.source
|
||||
)
|
||||
)
|
||||
else
|
||||
raise NotImplementedError, "Cannot yet patch #{code_object} objects!"
|
||||
end
|
||||
end
|
||||
|
||||
def ensure_file_name_is_valid(file_name)
|
||||
unless file_name
|
||||
raise CommandError, "Cannot find a valid file for #{filename_argument}"
|
||||
end
|
||||
|
||||
return unless not_a_real_file?(file_name)
|
||||
|
||||
raise CommandError, "#{file_name} is not a valid file name, cannot edit!"
|
||||
end
|
||||
|
||||
def file_and_line_for_current_exception
|
||||
FileAndLineLocator.from_exception(pry_instance.last_exception, opts[:ex].to_i)
|
||||
end
|
||||
|
||||
def file_and_line
|
||||
file_name, line =
|
||||
if opts.present?(:current)
|
||||
FileAndLineLocator.from_binding(target)
|
||||
elsif opts.present?(:ex)
|
||||
file_and_line_for_current_exception
|
||||
elsif code_object
|
||||
FileAndLineLocator.from_code_object(code_object, filename_argument)
|
||||
else
|
||||
# when file and line are passed as a single arg, e.g my_file.rb:30
|
||||
FileAndLineLocator.from_filename_argument(filename_argument)
|
||||
end
|
||||
|
||||
[file_name, opts.present?(:line) ? opts[:l].to_i : line]
|
||||
end
|
||||
|
||||
def file_edit
|
||||
file_name, line = file_and_line
|
||||
|
||||
ensure_file_name_is_valid(file_name)
|
||||
|
||||
Pry::Editor.new(pry_instance).invoke_editor(file_name, line, reload?(file_name))
|
||||
set_file_and_dir_locals(file_name)
|
||||
|
||||
return unless reload?(file_name)
|
||||
|
||||
silence_warnings { load(file_name) }
|
||||
end
|
||||
|
||||
def filename_argument
|
||||
args.join(' ')
|
||||
end
|
||||
|
||||
def code_object
|
||||
@code_object ||=
|
||||
!probably_a_file?(filename_argument) &&
|
||||
Pry::CodeObject.lookup(filename_argument, pry_instance)
|
||||
end
|
||||
|
||||
def pry_method?(code_object)
|
||||
code_object.is_a?(Pry::Method) &&
|
||||
code_object.pry_method?
|
||||
end
|
||||
|
||||
def previously_patched?(code_object)
|
||||
code_object.is_a?(Pry::Method) &&
|
||||
Pry::Method::Patcher.code_for(code_object.source_location.first)
|
||||
end
|
||||
|
||||
def patch_exception?
|
||||
opts.present?(:ex) && opts.present?(:patch)
|
||||
end
|
||||
|
||||
def bad_option_combination?
|
||||
[
|
||||
opts.present?(:ex), opts.present?(:temp),
|
||||
opts.present?(:in), opts.present?(:method),
|
||||
!filename_argument.empty?
|
||||
].count(true) > 1
|
||||
end
|
||||
|
||||
def input_expression
|
||||
case opts[:i]
|
||||
when Range
|
||||
(pry_instance.input_ring[opts[:i]] || []).join
|
||||
when Integer
|
||||
pry_instance.input_ring[opts[:i]] || ""
|
||||
else
|
||||
raise Pry::CommandError, "Not a valid range: #{opts[:i]}"
|
||||
end
|
||||
end
|
||||
|
||||
def reloadable?
|
||||
opts.present?(:reload) || opts.present?(:ex)
|
||||
end
|
||||
|
||||
def never_reload?
|
||||
opts.present?(:'no-reload') || pry_instance.config.disable_auto_reload
|
||||
end
|
||||
|
||||
def reload?(file_name = "")
|
||||
(reloadable? || file_name.end_with?(".rb")) && !never_reload?
|
||||
end
|
||||
|
||||
def initial_temp_file_content
|
||||
if opts.present?(:temp)
|
||||
""
|
||||
elsif opts.present?(:in)
|
||||
input_expression
|
||||
elsif eval_string.strip != ""
|
||||
eval_string
|
||||
else
|
||||
pry_instance.input_ring.to_a.reverse_each.find { |x| x && x.strip != "" } || ""
|
||||
end
|
||||
end
|
||||
|
||||
def probably_a_file?(str)
|
||||
[".rb", ".c", ".py", ".yml", ".gemspec"].include?(File.extname(str)) ||
|
||||
str =~ %r{/|\\}
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::Edit)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Edit
|
||||
class ExceptionPatcher
|
||||
attr_accessor :pry_instance
|
||||
attr_accessor :state
|
||||
attr_accessor :file_and_line
|
||||
|
||||
def initialize(pry_instance, state, exception_file_and_line)
|
||||
@pry_instance = pry_instance
|
||||
@state = state
|
||||
@file_and_line = exception_file_and_line
|
||||
end
|
||||
|
||||
# perform the patch
|
||||
def perform_patch
|
||||
file_name, = file_and_line
|
||||
lines = state.dynamical_ex_file || File.read(file_name)
|
||||
|
||||
source = Pry::Editor.new(pry_instance).edit_tempfile_with_content(lines)
|
||||
pry_instance.evaluate_ruby source
|
||||
state.dynamical_ex_file = source.split("\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,47 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Edit
|
||||
module FileAndLineLocator
|
||||
class << self
|
||||
def from_binding(target)
|
||||
if target.respond_to?(:source_location)
|
||||
target.source_location
|
||||
else
|
||||
target.eval("[__FILE__, __LINE__]")
|
||||
end
|
||||
end
|
||||
|
||||
def from_code_object(code_object, filename_argument)
|
||||
unless File.exist?(code_object.source_file.to_s)
|
||||
raise CommandError, "Cannot find a file for #{filename_argument}!"
|
||||
end
|
||||
|
||||
[code_object.source_file, code_object.source_line]
|
||||
end
|
||||
|
||||
def from_exception(exception, backtrace_level)
|
||||
raise CommandError, "No exception found." if exception.nil?
|
||||
|
||||
file_name, line = exception.bt_source_location_for(backtrace_level)
|
||||
raise CommandError, "Exception has no associated file." if file_name.nil?
|
||||
|
||||
if Pry.eval_path == file_name
|
||||
raise CommandError, "Cannot edit exceptions raised in REPL."
|
||||
end
|
||||
|
||||
[file_name, line]
|
||||
end
|
||||
|
||||
# when file and line are passed as a single arg, e.g my_file.rb:30
|
||||
def from_filename_argument(filename_argument)
|
||||
f = File.expand_path(filename_argument)
|
||||
l = f.sub!(/:(\d+)$/, "") ? Regexp.last_match(1).to_i : 1
|
||||
[f, l]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,47 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Exit < Pry::ClassCommand
|
||||
match 'exit'
|
||||
group 'Navigating Pry'
|
||||
description 'Pop the previous binding.'
|
||||
command_options keep_retval: true
|
||||
|
||||
banner <<-'BANNER'
|
||||
Usage: exit [OPTIONS] [--help]
|
||||
Aliases: quit
|
||||
|
||||
Pop the previous binding (does NOT exit program). 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.
|
||||
|
||||
exit "pry this"
|
||||
exit
|
||||
|
||||
https://github.com/pry/pry/wiki/State-navigation#wiki-Exit_with_value
|
||||
BANNER
|
||||
|
||||
def process
|
||||
if pry_instance.binding_stack.one?
|
||||
pry_instance.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_instance.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
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::Exit)
|
||||
Pry::Commands.alias_command 'quit', 'exit'
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class ExitAll < Pry::ClassCommand
|
||||
match 'exit-all'
|
||||
group 'Navigating Pry'
|
||||
description 'End the current Pry session.'
|
||||
|
||||
banner <<-'BANNER'
|
||||
Usage: exit-all [--help]
|
||||
Aliases: !!@
|
||||
|
||||
End the current Pry session (popping all bindings and returning to caller).
|
||||
Accepts optional return value.
|
||||
BANNER
|
||||
|
||||
def process
|
||||
# calculate user-given value
|
||||
exit_value = target.eval(arg_string)
|
||||
|
||||
# clear the binding stack
|
||||
pry_instance.binding_stack.clear
|
||||
|
||||
# break out of the repl loop
|
||||
throw(:breakout, exit_value)
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::ExitAll)
|
||||
Pry::Commands.alias_command '!!@', 'exit-all'
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,27 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class ExitProgram < Pry::ClassCommand
|
||||
match 'exit-program'
|
||||
group 'Navigating Pry'
|
||||
description 'End the current program.'
|
||||
|
||||
banner <<-'BANNER'
|
||||
Usage: exit-program [--help]
|
||||
Aliases: quit-program
|
||||
!!!
|
||||
|
||||
End the current program.
|
||||
BANNER
|
||||
|
||||
def process
|
||||
Kernel.exit target.eval(arg_string).to_i
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::ExitProgram)
|
||||
Pry::Commands.alias_command 'quit-program', 'exit-program'
|
||||
Pry::Commands.alias_command '!!!', 'exit-program'
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,199 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class FindMethod < Pry::ClassCommand
|
||||
extend Pry::Helpers::BaseHelpers
|
||||
|
||||
match 'find-method'
|
||||
group 'Context'
|
||||
description 'Recursively search for a method within a Class/Module or ' \
|
||||
'the current namespace.'
|
||||
command_options shellwords: false
|
||||
|
||||
banner <<-'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.
|
||||
|
||||
# Find all methods whose name match /re/ inside
|
||||
# the Pry namespace. Matches Pry#repl, etc.
|
||||
find-method re Pry
|
||||
|
||||
# Find all methods that contain the code:
|
||||
# output.puts inside the Pry namespace.
|
||||
find-method -c 'output.puts' Pry
|
||||
BANNER
|
||||
|
||||
def options(opt)
|
||||
opt.on :n, :name, "Search for a method by name"
|
||||
opt.on :c, :content, "Search for a method based on content in Regex form"
|
||||
end
|
||||
|
||||
def process
|
||||
return if args.empty?
|
||||
|
||||
klass = search_class
|
||||
|
||||
matches = opts.content? ? content_search(klass) : name_search(klass)
|
||||
show_search_results(matches)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# @return [Regexp] The pattern to search for.
|
||||
def pattern
|
||||
@pattern ||= ::Regexp.new args[0]
|
||||
end
|
||||
|
||||
# Output the result of the search.
|
||||
#
|
||||
# @param [Array] matches
|
||||
def show_search_results(matches)
|
||||
if matches.empty?
|
||||
output.puts bold("No Methods Matched")
|
||||
else
|
||||
print_matches(matches)
|
||||
end
|
||||
end
|
||||
|
||||
# The class to search for methods.
|
||||
# We only search classes, so if the search object is an
|
||||
# instance, return its class. If no search object is given
|
||||
# search `target_self`.
|
||||
def search_class
|
||||
klass = if args[1]
|
||||
target.eval(args[1])
|
||||
else
|
||||
target_self
|
||||
end
|
||||
|
||||
klass.is_a?(Module) ? klass : klass.class
|
||||
end
|
||||
|
||||
# pretty-print a list of matching methods.
|
||||
#
|
||||
# @param [Array<Method>] matches
|
||||
def print_matches(matches)
|
||||
grouped = matches.group_by(&:owner)
|
||||
order = grouped.keys.sort_by { |x| x.name || x.to_s }
|
||||
|
||||
order.each do |klass|
|
||||
print_matches_for_class(klass, grouped)
|
||||
end
|
||||
end
|
||||
|
||||
# Print matched methods for a class
|
||||
def print_matches_for_class(klass, grouped)
|
||||
output.puts bold(klass.name)
|
||||
grouped[klass].each do |method|
|
||||
header = method.name_with_owner
|
||||
output.puts header + additional_info(header, method)
|
||||
end
|
||||
end
|
||||
|
||||
# Return the matched lines of method source if `-c` is given or ""
|
||||
# if `-c` was not given
|
||||
def additional_info(header, method)
|
||||
if opts.content?
|
||||
': ' + colorize_code(matched_method_lines(header, method))
|
||||
else
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
def matched_method_lines(header, method)
|
||||
method.source.split(/\n/).select { |x| x =~ pattern }.join(
|
||||
"\n#{' ' * header.length}"
|
||||
)
|
||||
end
|
||||
|
||||
# Run the given block against every constant in the provided namespace.
|
||||
#
|
||||
# @param [Module] klass The namespace in which to start the search.
|
||||
# @param [Hash<Module,Boolean>] done The namespaces we've already visited (private)
|
||||
# @yieldparam klass Each class/module in the namespace.
|
||||
#
|
||||
def recurse_namespace(klass, done = {}, &block)
|
||||
return if !klass.is_a?(Module) || done[klass]
|
||||
|
||||
done[klass] = true
|
||||
|
||||
yield klass
|
||||
|
||||
klass.constants.each do |name|
|
||||
next if klass.autoload?(name)
|
||||
|
||||
begin
|
||||
const = Pry::Method.singleton_class_of(klass.const_get(name))
|
||||
rescue RescuableException # rubocop:disable Lint/HandleExceptions
|
||||
# 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] namespace The namespace in which to search.
|
||||
# @yieldparam [Method] 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|
|
||||
methods = Pry::Method.all_from_class(klass) + Pry::Method.all_from_obj(klass)
|
||||
methods.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 [Module] namespace The namespace to search
|
||||
# @return [Array<Method>]
|
||||
#
|
||||
def name_search(namespace)
|
||||
search_all_methods(namespace) do |meth|
|
||||
meth.name =~ pattern
|
||||
end
|
||||
end
|
||||
|
||||
# Search for all methods who's implementation matches the given regex
|
||||
# within a namespace.
|
||||
#
|
||||
# @param [Module] namespace The namespace to search
|
||||
# @return [Array<Method>]
|
||||
#
|
||||
def content_search(namespace)
|
||||
search_all_methods(namespace) do |meth|
|
||||
begin
|
||||
meth.source =~ pattern
|
||||
rescue RescuableException
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::FindMethod)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,23 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class FixIndent < Pry::ClassCommand
|
||||
match 'fix-indent'
|
||||
group 'Input and Output'
|
||||
|
||||
description "Correct the indentation for contents of the input buffer"
|
||||
|
||||
banner <<-USAGE
|
||||
Usage: fix-indent
|
||||
USAGE
|
||||
|
||||
def process
|
||||
indented_str = Pry::Indent.indent(eval_string)
|
||||
pry_instance.eval_string = indented_str
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::FixIndent)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,171 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Help < Pry::ClassCommand
|
||||
match 'help'
|
||||
group 'Help'
|
||||
description 'Show a list of commands or information about a specific command.'
|
||||
|
||||
banner <<-'BANNER'
|
||||
Usage: help [COMMAND]
|
||||
|
||||
With no arguments, help lists all the available commands along with their
|
||||
descriptions. 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>>] groups
|
||||
def display_index(groups)
|
||||
help_text = []
|
||||
|
||||
sorted_group_names(groups).each do |group_name|
|
||||
commands = sorted_commands(groups[group_name])
|
||||
|
||||
help_text << help_text_for_commands(group_name, commands) if commands.any?
|
||||
end
|
||||
|
||||
pry_instance.pager.page help_text.join("\n\n")
|
||||
end
|
||||
|
||||
# Given a group name and an array of commands,
|
||||
# return the help string for those commands.
|
||||
#
|
||||
# @param [String] name The group name.
|
||||
# @param [Array<Pry::Command>] commands
|
||||
# @return [String] The generated help string.
|
||||
def help_text_for_commands(name, commands)
|
||||
"#{bold(name.capitalize)}\n" + commands.map do |command|
|
||||
" #{command.options[:listing].to_s.ljust(18)} " \
|
||||
"#{command.description.capitalize}"
|
||||
end.join("\n")
|
||||
end
|
||||
|
||||
# @param [Hash] groups
|
||||
# @return [Array<String>] An array of sorted group names.
|
||||
def sorted_group_names(groups)
|
||||
groups.keys.sort_by(&method(:group_sort_key))
|
||||
end
|
||||
|
||||
# Sort an array of commands by their `listing` name.
|
||||
#
|
||||
# @param [Array<Pry::Command>] commands The commands to sort
|
||||
# @return [Array<Pry::Command>] commands sorted by listing name.
|
||||
def sorted_commands(commands)
|
||||
commands.sort_by { |command| command.options[:listing].to_s }
|
||||
end
|
||||
|
||||
# Display help for an individual command or group.
|
||||
#
|
||||
# @param [String] search The string to search for.
|
||||
def display_search(search)
|
||||
if (command = command_set.find_command_for_help(search))
|
||||
display_command(command)
|
||||
else
|
||||
display_filtered_search_results(search)
|
||||
end
|
||||
end
|
||||
|
||||
# Display help for a searched item, filtered first by group
|
||||
# and if that fails, filtered by command name.
|
||||
#
|
||||
# @param [String] search The string to search for.
|
||||
def display_filtered_search_results(search)
|
||||
groups = search_hash(search, command_groups)
|
||||
|
||||
if !groups.empty?
|
||||
display_index(groups)
|
||||
else
|
||||
display_filtered_commands(search)
|
||||
end
|
||||
end
|
||||
|
||||
# Display help for a searched item, filtered by group
|
||||
#
|
||||
# @param [String] search The string to search for.
|
||||
def display_filtered_commands(search)
|
||||
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
|
||||
|
||||
# Display help for an individual command.
|
||||
#
|
||||
# @param [Pry::Command] command
|
||||
def display_command(command)
|
||||
pry_instance.pager.page 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] search the search term
|
||||
# @param [Hash] hash the hash to search
|
||||
def search_hash(search, hash)
|
||||
matching = {}
|
||||
|
||||
hash.each_pair do |key, value|
|
||||
next unless key.is_a?(String)
|
||||
return { key => value } if normalize(key) == normalize(search)
|
||||
next unless normalize(key).start_with?(normalize(search))
|
||||
|
||||
matching[key] = value
|
||||
end
|
||||
|
||||
matching
|
||||
end
|
||||
|
||||
# Clean search terms to make it easier to search group names
|
||||
#
|
||||
# @param [String] key
|
||||
# @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.tr(' ', '_')) || 99, group_name
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::Help)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,182 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Hist < Pry::ClassCommand
|
||||
match 'hist'
|
||||
group 'Editing'
|
||||
description 'Show and replay Readline history.'
|
||||
|
||||
banner <<-'BANNER'
|
||||
Usage: hist [--head|--tail]
|
||||
hist --all
|
||||
hist --head N
|
||||
hist --tail N
|
||||
hist --show START..END
|
||||
hist --grep PATTERN
|
||||
hist --clear
|
||||
hist --replay START..END
|
||||
hist --save [START..END] FILE
|
||||
Aliases: history
|
||||
|
||||
Show and replay Readline history.
|
||||
BANNER
|
||||
|
||||
def options(opt)
|
||||
opt.on :a, :all, "Display all history"
|
||||
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"
|
||||
end
|
||||
|
||||
def process
|
||||
@history = find_history
|
||||
|
||||
@history = @history.between(opts[:show]) if opts.present?(:show)
|
||||
|
||||
@history = @history.grep(opts[:grep]) if opts.present?(:grep)
|
||||
|
||||
@history =
|
||||
if opts.present?(:head)
|
||||
@history.take_lines(1, opts[:head] || 10)
|
||||
elsif opts.present?(:tail)
|
||||
@history.take_lines(-(opts[:tail] || 10), opts[:tail] || 10)
|
||||
else
|
||||
@history
|
||||
end
|
||||
|
||||
if opts.present?(:'exclude-pry')
|
||||
@history = @history.reject do |loc|
|
||||
command_set.valid_command?(loc.line)
|
||||
end
|
||||
end
|
||||
|
||||
if opts.present?(:save)
|
||||
process_save
|
||||
elsif opts.present?(:clear)
|
||||
process_clear
|
||||
elsif opts.present?(:replay)
|
||||
process_replay
|
||||
else
|
||||
process_display
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def process_display
|
||||
@history = @history.with_line_numbers unless opts.present?(:'no-numbers')
|
||||
|
||||
pry_instance.pager.open do |pager|
|
||||
@history.print_to_output(pager, true)
|
||||
end
|
||||
end
|
||||
|
||||
def process_save
|
||||
case opts[:save]
|
||||
when Range
|
||||
@history = @history.between(opts[:save])
|
||||
|
||||
raise CommandError, "Must provide a file name." unless args.first
|
||||
|
||||
file_name = File.expand_path(args.first)
|
||||
when String
|
||||
file_name = File.expand_path(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])
|
||||
replay_sequence = @history.raw
|
||||
|
||||
# If we met follow-up "hist" call, check for the "--replay" option
|
||||
# presence. If "hist" command is called with other options, proceed
|
||||
# further.
|
||||
check_for_juxtaposed_replay(replay_sequence)
|
||||
|
||||
replay_sequence.lines.each do |line|
|
||||
pry_instance.eval line, generated: true
|
||||
end
|
||||
end
|
||||
|
||||
# Checks +replay_sequence+ for the presence of neighboring replay calls.
|
||||
# @example
|
||||
# [1] pry(main)> hist --show 46894
|
||||
# 46894: hist --replay 46675..46677
|
||||
# [2] pry(main)> hist --show 46675..46677
|
||||
# 46675: 1+1
|
||||
# 46676: a = 100
|
||||
# 46677: hist --tail
|
||||
# [3] pry(main)> hist --replay 46894
|
||||
# Error: Replay index 46894 points out to another replay call:
|
||||
# `hist -r 46675..46677`
|
||||
# [4] pry(main)>
|
||||
#
|
||||
# @raise [Pry::CommandError] If +replay_sequence+ contains another
|
||||
# "hist --replay" call
|
||||
# @param [String] replay_sequence The sequence of commands to be replayed
|
||||
# (per saltum)
|
||||
# @return [Boolean] `false` if +replay_sequence+ does not contain another
|
||||
# "hist --replay" call
|
||||
def check_for_juxtaposed_replay(replay_sequence)
|
||||
if replay_sequence =~ /\Ahist(?:ory)?\b/
|
||||
# Create *fresh* instance of Options for parsing of "hist" command.
|
||||
slop_instance = slop
|
||||
slop_instance.parse(replay_sequence.split(' ')[1..-1])
|
||||
|
||||
if slop_instance.present?(:r)
|
||||
replay_sequence = replay_sequence.split("\n").join('; ')
|
||||
index = opts[:r]
|
||||
index = index.min if index.min == index.max || index.max.nil?
|
||||
|
||||
raise CommandError,
|
||||
"Replay index #{index} points out to another replay call: " \
|
||||
"`#{replay_sequence}`"
|
||||
end
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# Finds history depending on the given switch.
|
||||
#
|
||||
# @return [Pry::Code] if it finds `--all` (or `-a`) switch, returns all
|
||||
# entries in history. Without the switch returns only the entries from the
|
||||
# current Pry session.
|
||||
def find_history
|
||||
h = if opts.present?(:all)
|
||||
Pry.history.to_a
|
||||
else
|
||||
Pry.history.to_a.last(Pry.history.session_line_count)
|
||||
end
|
||||
|
||||
Pry::Code(Pry.history.filter(h[0..-2]))
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::Hist)
|
||||
Pry::Commands.alias_command 'history', 'hist'
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,27 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class ImportSet < Pry::ClassCommand
|
||||
match 'import-set'
|
||||
group 'Commands'
|
||||
# TODO: Provide a better description with examples and a general conception
|
||||
# of this command.
|
||||
description 'Import a Pry command set.'
|
||||
|
||||
banner <<-'BANNER'
|
||||
Import a Pry command set.
|
||||
BANNER
|
||||
|
||||
# TODO: resolve unused parameter.
|
||||
def process(_command_set_name)
|
||||
raise CommandError, "Provide a command set name" if command_set.nil?
|
||||
|
||||
set = target.eval(arg_string)
|
||||
pry_instance.commands.import set
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::ImportSet)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class JumpTo < Pry::ClassCommand
|
||||
match 'jump-to'
|
||||
group 'Navigating Pry'
|
||||
description 'Jump to a binding further up the stack.'
|
||||
|
||||
banner <<-'BANNER'
|
||||
Jump to a binding further up the stack, popping all bindings below.
|
||||
BANNER
|
||||
|
||||
def process(break_level)
|
||||
break_level = break_level.to_i
|
||||
nesting_level = pry_instance.binding_stack.size - 1
|
||||
max_nest_level = nesting_level - 1
|
||||
|
||||
case break_level
|
||||
when nesting_level
|
||||
output.puts "Already at nesting level #{nesting_level}"
|
||||
when 0..max_nest_level
|
||||
pry_instance.binding_stack = pry_instance.binding_stack[0..break_level]
|
||||
else
|
||||
output.puts "Invalid nest level. Must be between 0 and " \
|
||||
"#{max_nest_level}. Got #{break_level}."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::JumpTo)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,42 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class ListInspectors < Pry::ClassCommand
|
||||
match 'list-inspectors'
|
||||
group 'Input and Output'
|
||||
description 'List the inspector procs available for use.'
|
||||
banner <<-BANNER
|
||||
Usage: list-inspectors
|
||||
|
||||
List the inspector procs available to print return values. You can use
|
||||
change-inspector to switch between them.
|
||||
BANNER
|
||||
|
||||
def process
|
||||
output.puts heading("Available inspectors") + "\n"
|
||||
inspector_map.each do |name, inspector|
|
||||
output.write "Name: #{bold(name)}"
|
||||
output.puts selected_inspector?(inspector) ? selected_text : ""
|
||||
output.puts inspector[:description]
|
||||
output.puts
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def inspector_map
|
||||
Pry::Inspector::MAP
|
||||
end
|
||||
|
||||
def selected_text
|
||||
red " (selected) "
|
||||
end
|
||||
|
||||
def selected_inspector?(inspector)
|
||||
pry_instance.print == inspector[:value]
|
||||
end
|
||||
Pry::Commands.add_command(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,114 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Ls < Pry::ClassCommand
|
||||
match 'ls'
|
||||
group 'Context'
|
||||
description 'Show the list of vars and methods in the current scope.'
|
||||
command_options shellwords: false, interpolate: false
|
||||
|
||||
banner <<-'BANNER'
|
||||
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.
|
||||
|
||||
Also check out `find-method` command (run `help find-method`).
|
||||
BANNER
|
||||
|
||||
def options(opt)
|
||||
opt.on :m, :methods, "Show public methods defined on the Object"
|
||||
opt.on :M, "instance-methods", "Show public 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 hash of local vars, sorted by descending size"
|
||||
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 Object.respond_to?(:deprecate_constant)
|
||||
opt.on :d, :dconstants, "Show deprecated constants"
|
||||
end
|
||||
|
||||
return unless Helpers::Platform.jruby?
|
||||
|
||||
opt.on :J, "all-java", "Show all the aliases for methods from java " \
|
||||
"(default is to show only prettiest)"
|
||||
end
|
||||
|
||||
# Exclude -q, -v and --grep because they,
|
||||
# don't specify what the user wants to see.
|
||||
def no_user_opts?
|
||||
!(opts[:methods] || opts['instance-methods'] || opts[:ppp] ||
|
||||
opts[:globals] || opts[:locals] || opts[:constants] || opts[:ivars])
|
||||
end
|
||||
|
||||
def process
|
||||
@interrogatee = args.empty? ? target_self : target.eval(args.join(' '))
|
||||
raise_errors_if_arguments_are_weird
|
||||
ls_entity = LsEntity.new(
|
||||
interrogatee: @interrogatee,
|
||||
no_user_opts: no_user_opts?,
|
||||
opts: opts,
|
||||
args: args,
|
||||
pry_instance: pry_instance
|
||||
)
|
||||
|
||||
pry_instance.pager.page ls_entity.entities_table
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def error_list
|
||||
any_args = args.any?
|
||||
# rubocop:disable Style/CaseEquality
|
||||
non_mod_interrogatee = !(Module === @interrogatee)
|
||||
# rubocop:enable Style/CaseEquality
|
||||
[
|
||||
['-l does not make sense with a specified Object', :locals, any_args],
|
||||
['-g does not make sense with a specified Object', :globals, any_args],
|
||||
['-q does not make sense with -v', :quiet, opts.present?(:verbose)],
|
||||
[
|
||||
'-M only makes sense with a Module or a Class', 'instance-methods',
|
||||
non_mod_interrogatee
|
||||
],
|
||||
[
|
||||
'-c only makes sense with a Module or a Class', :constants,
|
||||
any_args && non_mod_interrogatee
|
||||
]
|
||||
]
|
||||
end
|
||||
|
||||
def raise_errors_if_arguments_are_weird
|
||||
error_list.each do |message, option, invalid_expr|
|
||||
raise Pry::CommandError, message if opts.present?(option) && invalid_expr
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::Ls)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,54 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'forwardable'
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Ls < Pry::ClassCommand
|
||||
class Config
|
||||
extend Forwardable
|
||||
|
||||
DEFAULT_OPTIONS = {
|
||||
heading_color: :bright_blue,
|
||||
public_method_color: :default,
|
||||
private_method_color: :blue,
|
||||
protected_method_color: :blue,
|
||||
method_missing_color: :bright_red,
|
||||
local_var_color: :yellow,
|
||||
pry_var_color: :default, # e.g. _, pry_instance, _file_
|
||||
instance_var_color: :blue, # e.g. @foo
|
||||
class_var_color: :bright_blue, # e.g. @@foo
|
||||
global_var_color: :default, # e.g. $CODERAY_DEBUG, $eventmachine_library
|
||||
builtin_global_color: :cyan, # e.g. $stdin, $-w, $PID
|
||||
pseudo_global_color: :cyan, # e.g. $~, $1..$9, $LAST_MATCH_INFO
|
||||
constant_color: :default, # e.g. VERSION, ARGF
|
||||
class_constant_color: :blue, # e.g. Object, Kernel
|
||||
exception_constant_color: :magenta, # e.g. Exception, RuntimeError
|
||||
unloaded_constant_color: :yellow, # Constant still in .autoload? state
|
||||
separator: " ",
|
||||
ceiling: [Object, Module, Class]
|
||||
}.freeze
|
||||
|
||||
DEFAULT_OPTIONS.each_key do |key|
|
||||
define_method key do
|
||||
@config[key]
|
||||
end
|
||||
|
||||
define_method "#{key}=" do |value|
|
||||
@config[key] = value
|
||||
end
|
||||
end
|
||||
|
||||
def_delegators :@config, :[], :[]=, :each, :each_pair, :values, :keys, :to_a
|
||||
|
||||
def initialize(config)
|
||||
@config = config
|
||||
end
|
||||
|
||||
def self.default
|
||||
new(DEFAULT_OPTIONS.dup)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,75 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Ls < Pry::ClassCommand
|
||||
class Constants < Pry::Command::Ls::Formatter
|
||||
DEPRECATED_CONSTANTS = %i[
|
||||
Data Fixnum Bignum TimeoutError NIL FALSE TRUE
|
||||
].tap do |constants|
|
||||
constants << :JavaPackageModuleTemplate if Helpers::Platform.jruby?
|
||||
end
|
||||
include Pry::Command::Ls::Interrogatable
|
||||
|
||||
def initialize(interrogatee, no_user_opts, opts, pry_instance)
|
||||
super(pry_instance)
|
||||
@interrogatee = interrogatee
|
||||
@no_user_opts = no_user_opts
|
||||
@default_switch = opts[:constants]
|
||||
@verbose_switch = opts[:verbose]
|
||||
@dconstants = opts.dconstants?
|
||||
end
|
||||
|
||||
def correct_opts?
|
||||
super || (@no_user_opts && interrogating_a_module?)
|
||||
end
|
||||
|
||||
def output_self
|
||||
mod = interrogatee_mod
|
||||
constants = WrappedModule.new(mod).constants(@verbose_switch)
|
||||
output_section('constants', grep.regexp[format(mod, constants)])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def show_deprecated_constants?
|
||||
@dconstants == true
|
||||
end
|
||||
|
||||
def format(mod, constants)
|
||||
constants.sort_by(&:downcase).map do |name|
|
||||
if Object.respond_to?(:deprecate_constant) &&
|
||||
DEPRECATED_CONSTANTS.include?(name) &&
|
||||
!show_deprecated_constants?
|
||||
next
|
||||
end
|
||||
|
||||
if (const = (begin
|
||||
!mod.autoload?(name) && (mod.const_get(name) || true)
|
||||
rescue StandardError
|
||||
nil
|
||||
end))
|
||||
if begin
|
||||
const < Exception
|
||||
rescue StandardError
|
||||
false
|
||||
end
|
||||
color(:exception_constant, name)
|
||||
elsif begin
|
||||
mod.const_get(name).is_a?(Module)
|
||||
rescue StandardError
|
||||
false
|
||||
end
|
||||
color(:class_constant, name)
|
||||
else
|
||||
color(:constant, name)
|
||||
end
|
||||
else
|
||||
color(:unloaded_constant, name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,55 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Ls < Pry::ClassCommand
|
||||
class Formatter
|
||||
attr_writer :grep
|
||||
attr_reader :pry_instance
|
||||
|
||||
def initialize(pry_instance)
|
||||
@pry_instance = pry_instance
|
||||
@target = pry_instance.current_context
|
||||
@default_switch = nil
|
||||
end
|
||||
|
||||
def write_out
|
||||
return false unless correct_opts?
|
||||
|
||||
output_self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def color(type, str)
|
||||
Pry::Helpers::Text.send pry_instance.config.ls.send("#{type}_color"), str
|
||||
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?
|
||||
|
||||
fancy_heading = Pry::Helpers::Text.bold(color(:heading, heading))
|
||||
Pry::Helpers.tablify_or_one_line(fancy_heading, body, @pry_instance)
|
||||
end
|
||||
|
||||
def format_value(value)
|
||||
Pry::ColorPrinter.pp(value, ''.dup)
|
||||
end
|
||||
|
||||
def correct_opts?
|
||||
@default_switch
|
||||
end
|
||||
|
||||
def output_self
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def grep
|
||||
@grep || proc { |x| x }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,50 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Ls < Pry::ClassCommand
|
||||
class Globals < Pry::Command::Ls::Formatter
|
||||
# Taken from "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].freeze
|
||||
|
||||
# `$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].freeze
|
||||
|
||||
def initialize(opts, pry_instance)
|
||||
super(pry_instance)
|
||||
@default_switch = opts[:globals]
|
||||
end
|
||||
|
||||
def output_self
|
||||
variables = format(@target.eval('global_variables'))
|
||||
output_section('global variables', grep.regexp[variables])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def format(globals)
|
||||
globals.map(&:to_s).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
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,23 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Ls < Pry::ClassCommand
|
||||
class Grep
|
||||
def initialize(grep_regexp)
|
||||
@grep_regexp = grep_regexp
|
||||
end
|
||||
|
||||
def regexp
|
||||
proc { |x|
|
||||
if x.instance_of?(Array)
|
||||
x.grep(@grep_regexp)
|
||||
else
|
||||
x =~ @grep_regexp
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,40 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Ls < Pry::ClassCommand
|
||||
class InstanceVars < Pry::Command::Ls::Formatter
|
||||
include Pry::Command::Ls::Interrogatable
|
||||
|
||||
def initialize(interrogatee, no_user_opts, opts, pry_instance)
|
||||
super(pry_instance)
|
||||
@interrogatee = interrogatee
|
||||
@no_user_opts = no_user_opts
|
||||
@default_switch = opts[:ivars]
|
||||
end
|
||||
|
||||
def correct_opts?
|
||||
super || @no_user_opts
|
||||
end
|
||||
|
||||
def output_self
|
||||
ivars = if Object === @interrogatee # rubocop:disable Style/CaseEquality
|
||||
Pry::Method.safe_send(@interrogatee, :instance_variables)
|
||||
else
|
||||
[] # TODO: BasicObject support
|
||||
end
|
||||
kvars = Pry::Method.safe_send(interrogatee_mod, :class_variables)
|
||||
ivars_out = output_section('instance variables', format(:instance_var, ivars))
|
||||
kvars_out = output_section('class variables', format(:class_var, kvars))
|
||||
ivars_out + kvars_out
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def format(type, vars)
|
||||
vars.sort_by { |var| var.to_s.downcase }.map { |var| color(type, var) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,24 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Ls < Pry::ClassCommand
|
||||
module Interrogatable
|
||||
private
|
||||
|
||||
def interrogating_a_module?
|
||||
Module === @interrogatee # rubocop:disable Style/CaseEquality
|
||||
end
|
||||
|
||||
def interrogatee_mod
|
||||
if interrogating_a_module?
|
||||
@interrogatee
|
||||
else
|
||||
singleton = Pry::Method.singleton_class_of(@interrogatee)
|
||||
singleton.ancestors.grep(::Class).reject { |c| c == singleton }.first
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,55 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Ls < Pry::ClassCommand
|
||||
module JRubyHacks
|
||||
private
|
||||
|
||||
# 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.flat_map do |_key, values|
|
||||
values = values.sort_by do |m|
|
||||
rubbishness(m.name)
|
||||
end
|
||||
|
||||
found = []
|
||||
values.select do |x|
|
||||
(found.none? { |y| x == y }) && found << x
|
||||
end
|
||||
end
|
||||
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 do |x|
|
||||
case x
|
||||
when /[A-Z]/
|
||||
1
|
||||
when '?', '=', '!'
|
||||
-2
|
||||
else
|
||||
0
|
||||
end
|
||||
end.inject(&:+) + (name.size / 100.0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,37 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Ls < Pry::ClassCommand
|
||||
class LocalNames < Pry::Command::Ls::Formatter
|
||||
def initialize(no_user_opts, args, pry_instance)
|
||||
super(pry_instance)
|
||||
@no_user_opts = no_user_opts
|
||||
@args = args
|
||||
@sticky_locals = pry_instance.sticky_locals
|
||||
end
|
||||
|
||||
def correct_opts?
|
||||
super || (@no_user_opts && @args.empty?)
|
||||
end
|
||||
|
||||
def output_self
|
||||
local_vars = grep.regexp[@target.eval('local_variables')]
|
||||
output_section('locals', format(local_vars))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def format(locals)
|
||||
locals.sort_by(&:downcase).map do |name|
|
||||
if @sticky_locals.include?(name.to_sym)
|
||||
color(:pry_var, name)
|
||||
else
|
||||
color(:local_var, name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,47 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Ls < Pry::ClassCommand
|
||||
class LocalVars < Pry::Command::Ls::Formatter
|
||||
def initialize(opts, pry_instance)
|
||||
super(pry_instance)
|
||||
@default_switch = opts[:locals]
|
||||
@sticky_locals = pry_instance.sticky_locals
|
||||
end
|
||||
|
||||
def output_self
|
||||
locals = @target.eval('local_variables').reject do |e|
|
||||
@sticky_locals.key?(e.to_sym)
|
||||
end
|
||||
name_value_pairs = locals.map do |name|
|
||||
[name, @target.eval(name.to_s)]
|
||||
end
|
||||
format(name_value_pairs).join('')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def format(name_value_pairs)
|
||||
sorted = name_value_pairs.sort_by do |_name, value|
|
||||
value.to_s.size
|
||||
end
|
||||
sorted.reverse.map do |name, value|
|
||||
colorized_assignment_style(name, format_value(value))
|
||||
end
|
||||
end
|
||||
|
||||
def colorized_assignment_style(lhs, rhs, desired_width = 7)
|
||||
colorized_lhs = color(:local_var, lhs)
|
||||
color_escape_padding = colorized_lhs.size - lhs.size
|
||||
pad = desired_width + color_escape_padding
|
||||
Kernel.format(
|
||||
"%-#{pad}<name>s = %<value>s",
|
||||
name: color(:local_var, colorized_lhs),
|
||||
value: rhs
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,65 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Ls < Pry::ClassCommand
|
||||
class LsEntity
|
||||
attr_reader :pry_instance
|
||||
|
||||
def initialize(opts)
|
||||
@interrogatee = opts[:interrogatee]
|
||||
@no_user_opts = opts[:no_user_opts]
|
||||
@opts = opts[:opts]
|
||||
@args = opts[:args]
|
||||
@grep = Grep.new(Regexp.new(opts[:opts][:G] || '.'))
|
||||
@pry_instance = opts.delete(:pry_instance)
|
||||
end
|
||||
|
||||
def entities_table
|
||||
entities.map(&:write_out).select { |o| o }.join('')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def grep(entity)
|
||||
entity.tap { |o| o.grep = @grep }
|
||||
end
|
||||
|
||||
def globals
|
||||
grep Globals.new(@opts, pry_instance)
|
||||
end
|
||||
|
||||
def constants
|
||||
grep Constants.new(@interrogatee, @no_user_opts, @opts, pry_instance)
|
||||
end
|
||||
|
||||
def methods
|
||||
grep(Methods.new(@interrogatee, @no_user_opts, @opts, pry_instance))
|
||||
end
|
||||
|
||||
def self_methods
|
||||
grep SelfMethods.new(@interrogatee, @no_user_opts, @opts, pry_instance)
|
||||
end
|
||||
|
||||
def instance_vars
|
||||
grep InstanceVars.new(@interrogatee, @no_user_opts, @opts, pry_instance)
|
||||
end
|
||||
|
||||
def local_names
|
||||
grep LocalNames.new(@no_user_opts, @args, pry_instance)
|
||||
end
|
||||
|
||||
def local_vars
|
||||
LocalVars.new(@opts, pry_instance)
|
||||
end
|
||||
|
||||
def entities
|
||||
[
|
||||
globals, constants, methods, self_methods, instance_vars, local_names,
|
||||
local_vars
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,55 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Ls < Pry::ClassCommand
|
||||
class Methods < Pry::Command::Ls::Formatter
|
||||
include Pry::Command::Ls::Interrogatable
|
||||
include Pry::Command::Ls::MethodsHelper
|
||||
|
||||
def initialize(interrogatee, no_user_opts, opts, pry_instance)
|
||||
super(pry_instance)
|
||||
@interrogatee = interrogatee
|
||||
@no_user_opts = no_user_opts
|
||||
@default_switch = opts[:methods]
|
||||
@instance_methods_switch = opts['instance-methods']
|
||||
@ppp_switch = opts[:ppp]
|
||||
@jruby_switch = opts['all-java']
|
||||
@quiet_switch = opts[:quiet]
|
||||
@verbose_switch = opts[:verbose]
|
||||
end
|
||||
|
||||
def output_self
|
||||
methods = all_methods.group_by(&:owner)
|
||||
# Reverse the resolution order so that the most useful information
|
||||
# appears right by the prompt.
|
||||
resolution_order.take_while(&below_ceiling).reverse.map do |klass|
|
||||
methods_here = (methods[klass] || []).select { |m| grep.regexp[m.name] }
|
||||
heading = "#{Pry::WrappedModule.new(klass).method_prefix}methods"
|
||||
output_section(heading, format(methods_here))
|
||||
end.join('')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def correct_opts?
|
||||
super || @instance_methods_switch || @ppp_switch || @no_user_opts
|
||||
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
|
||||
ceiling = if @quiet_switch
|
||||
[Pry::Method.safe_send(interrogatee_mod, :ancestors)[1]] +
|
||||
pry_instance.config.ls.ceiling
|
||||
elsif @verbose_switch
|
||||
[]
|
||||
else
|
||||
pry_instance.config.ls.ceiling.dup
|
||||
end
|
||||
->(klass) { !ceiling.include?(klass) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,50 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Ls < Pry::ClassCommand
|
||||
module MethodsHelper
|
||||
include Pry::Command::Ls::JRubyHacks
|
||||
|
||||
private
|
||||
|
||||
# Get all the methods that we'll want to output.
|
||||
def all_methods(instance_methods = false)
|
||||
methods = if instance_methods || @instance_methods_switch
|
||||
Pry::Method.all_from_class(@interrogatee)
|
||||
else
|
||||
Pry::Method.all_from_obj(@interrogatee)
|
||||
end
|
||||
|
||||
if Pry::Helpers::Platform.jruby? && !@jruby_switch
|
||||
methods = trim_jruby_aliases(methods)
|
||||
end
|
||||
|
||||
methods.select { |method| @ppp_switch || method.visibility == :public }
|
||||
end
|
||||
|
||||
def resolution_order
|
||||
if @instance_methods_switch
|
||||
Pry::Method.instance_resolution_order(@interrogatee)
|
||||
else
|
||||
Pry::Method.resolution_order(@interrogatee)
|
||||
end
|
||||
end
|
||||
|
||||
def format(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
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,34 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Ls < Pry::ClassCommand
|
||||
class SelfMethods < Pry::Command::Ls::Formatter
|
||||
include Pry::Command::Ls::Interrogatable
|
||||
include Pry::Command::Ls::MethodsHelper
|
||||
|
||||
def initialize(interrogatee, no_user_opts, opts, pry_instance)
|
||||
super(pry_instance)
|
||||
@interrogatee = interrogatee
|
||||
@no_user_opts = no_user_opts
|
||||
@ppp_switch = opts[:ppp]
|
||||
@jruby_switch = opts['all-java']
|
||||
end
|
||||
|
||||
def output_self
|
||||
methods = all_methods(true).select do |m|
|
||||
m.owner == @interrogatee && grep.regexp[m.name]
|
||||
end
|
||||
heading = "#{Pry::WrappedModule.new(@interrogatee).method_prefix}methods"
|
||||
output_section(heading, format(methods))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def correct_opts?
|
||||
@no_user_opts && interrogating_a_module?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Nesting < Pry::ClassCommand
|
||||
match 'nesting'
|
||||
group 'Navigating Pry'
|
||||
description 'Show nesting information.'
|
||||
|
||||
banner <<-'BANNER'
|
||||
Show nesting information.
|
||||
BANNER
|
||||
|
||||
def process
|
||||
output.puts 'Nesting status:'
|
||||
output.puts '--'
|
||||
pry_instance.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
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::Nesting)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,113 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Play < Pry::ClassCommand
|
||||
match 'play'
|
||||
group 'Editing'
|
||||
description 'Playback a string variable, method, line, or file as input.'
|
||||
|
||||
banner <<-'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.
|
||||
|
||||
play --lines 149..153 # assumes current context
|
||||
play -i 20 --lines 1..3 # assumes lines of the input expression at 20
|
||||
play -o 4 # the output of an expression at 4
|
||||
play Pry#repl -l 1..-1 # play the contents of Pry#repl method
|
||||
play -e 2 # play from specified line until end of valid expression
|
||||
play hello.rb # play a file
|
||||
play Rakefile -l 5 # play line 5 of a file
|
||||
play -d hi # play documentation of hi method
|
||||
play hi --open # play hi method and leave it open
|
||||
|
||||
https://github.com/pry/pry/wiki/User-Input#wiki-Play
|
||||
BANNER
|
||||
|
||||
def options(opt)
|
||||
CodeCollector.inject_options(opt)
|
||||
|
||||
opt.on :open, 'Plays the selected content except the last line. Useful' \
|
||||
' for replaying methods and leaving the method definition' \
|
||||
' "open". `amend-line` can then be used to' \
|
||||
' modify the method.'
|
||||
|
||||
opt.on :e, :expression=, 'Executes until end of valid expression', as: Integer
|
||||
opt.on :p, :print, 'Prints executed code'
|
||||
end
|
||||
|
||||
def process
|
||||
@cc = CodeCollector.new(args, opts, pry_instance)
|
||||
|
||||
perform_play
|
||||
show_input
|
||||
end
|
||||
|
||||
def perform_play
|
||||
eval_string << content_after_options
|
||||
run "fix-indent"
|
||||
end
|
||||
|
||||
def show_input
|
||||
return unless opts.present?(:print)
|
||||
return unless Pry::Code.complete_expression?(eval_string)
|
||||
|
||||
run 'show-input'
|
||||
end
|
||||
|
||||
def content_after_options
|
||||
if opts.present?(:open)
|
||||
restrict_to_lines(content, (0..-2))
|
||||
elsif opts.present?(:expression)
|
||||
content_at_expression
|
||||
else
|
||||
content
|
||||
end
|
||||
end
|
||||
|
||||
def content_at_expression
|
||||
code_object.expression_at(opts[:expression])
|
||||
end
|
||||
|
||||
def code_object
|
||||
Pry::Code.new(content)
|
||||
end
|
||||
|
||||
def should_use_default_file?
|
||||
!args.first && !opts.present?(:in) && !opts.present?(:out)
|
||||
end
|
||||
|
||||
def content
|
||||
if should_use_default_file?
|
||||
file_content
|
||||
else
|
||||
@cc.content
|
||||
end
|
||||
end
|
||||
|
||||
# The file to play from when no code object is specified.
|
||||
# e.g `play --lines 4..10`
|
||||
def default_file
|
||||
file =
|
||||
if target.respond_to?(:source_location)
|
||||
target.source_location.first
|
||||
else
|
||||
target.eval("__FILE__")
|
||||
end
|
||||
file && File.expand_path(file)
|
||||
end
|
||||
|
||||
def file_content
|
||||
if !default_file || !File.exist?(default_file)
|
||||
raise CommandError, "File does not exist! File was: #{default_file.inspect}"
|
||||
end
|
||||
|
||||
@cc.restrict_to_lines(File.read(default_file), @cc.line_range)
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::Play)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,30 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class PryBacktrace < Pry::ClassCommand
|
||||
match 'pry-backtrace'
|
||||
group 'Context'
|
||||
description 'Show the backtrace for the Pry session.'
|
||||
|
||||
banner <<-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
|
||||
BANNER
|
||||
|
||||
def process
|
||||
text = "#{bold('Backtrace:')}\n--\n#{pry_instance.backtrace.join("\n")}"
|
||||
pry_instance.pager.page(text)
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::PryBacktrace)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Version < Pry::ClassCommand
|
||||
match 'pry-version'
|
||||
group 'Misc'
|
||||
description 'Show Pry version.'
|
||||
|
||||
banner <<-'BANNER'
|
||||
Show Pry version.
|
||||
BANNER
|
||||
|
||||
def process
|
||||
output.puts "Pry version: #{Pry::VERSION} on Ruby #{RUBY_VERSION}."
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::Version)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,38 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
# N.B. using a regular expression here so that "raise-up 'foo'" does the right thing.
|
||||
class Command
|
||||
class RaiseUp < Pry::ClassCommand
|
||||
match(/raise-up(!?\b.*)/)
|
||||
group 'Context'
|
||||
description 'Raise an exception out of the current pry instance.'
|
||||
command_options listing: 'raise-up'
|
||||
|
||||
banner <<-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_`).
|
||||
|
||||
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.
|
||||
|
||||
raise-up "get-me-out-of-here"
|
||||
|
||||
# This is equivalent to the command above.
|
||||
raise "get-me-out-of-here"
|
||||
raise-up
|
||||
BANNER
|
||||
|
||||
def process
|
||||
return _pry.pager.page help if captures[0] =~ /(-h|--help)\b/
|
||||
|
||||
# Handle 'raise-up', 'raise-up "foo"', 'raise-up RuntimeError, 'farble'
|
||||
# in a rubyesque manner
|
||||
target.eval("pry_instance.raise_up#{captures[0]}")
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::RaiseUp)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,74 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class ReloadCode < Pry::ClassCommand
|
||||
match 'reload-code'
|
||||
group 'Misc'
|
||||
description 'Reload the source file that contains the specified code object.'
|
||||
|
||||
banner <<-'BANNER'
|
||||
Reload the source file that contains the specified code object.
|
||||
|
||||
e.g reload-code MyClass#my_method #=> reload a method
|
||||
reload-code MyClass #=> reload a class
|
||||
reload-code my-command #=> reload a pry command
|
||||
reload-code self #=> reload the current object
|
||||
reload-code #=> reload the current file or object
|
||||
BANNER
|
||||
|
||||
def process
|
||||
if !args.empty?
|
||||
reload_object(args.join(" "))
|
||||
elsif internal_binding?(target)
|
||||
reload_object("self")
|
||||
else
|
||||
reload_current_file
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def current_file
|
||||
file =
|
||||
if target.respond_to?(:source_location)
|
||||
target.source_location.first
|
||||
else
|
||||
target.eval("__FILE__")
|
||||
end
|
||||
File.expand_path file
|
||||
end
|
||||
|
||||
def reload_current_file
|
||||
unless File.exist?(current_file)
|
||||
raise CommandError, "Current file: #{current_file} cannot be found on disk!"
|
||||
end
|
||||
|
||||
load current_file
|
||||
output.puts "The current file: #{current_file} was reloaded!"
|
||||
end
|
||||
|
||||
def reload_object(identifier)
|
||||
code_object = Pry::CodeObject.lookup(identifier, pry_instance)
|
||||
check_for_reloadability(code_object, identifier)
|
||||
load code_object.source_file
|
||||
output.puts "#{identifier} was reloaded!"
|
||||
end
|
||||
|
||||
def check_for_reloadability(code_object, identifier)
|
||||
if !code_object || !code_object.source_file
|
||||
raise CommandError, "Cannot locate #{identifier}!"
|
||||
end
|
||||
|
||||
return if File.exist?(code_object.source_file)
|
||||
|
||||
raise CommandError,
|
||||
"Cannot reload #{identifier} as it has no associated file on disk. " \
|
||||
"File found was: #{code_object.source_file}"
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::ReloadCode)
|
||||
Pry::Commands.alias_command 'reload-method', 'reload-code'
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Reset < Pry::ClassCommand
|
||||
match 'reset'
|
||||
group 'Context'
|
||||
description 'Reset the REPL to a clean state.'
|
||||
|
||||
banner <<-'BANNER'
|
||||
Reset the REPL to a clean state.
|
||||
BANNER
|
||||
|
||||
def process
|
||||
output.puts 'Pry reset.'
|
||||
exec 'pry'
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::Reset)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,75 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'stringio'
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Ri < Pry::ClassCommand
|
||||
match 'ri'
|
||||
group 'Introspection'
|
||||
description 'View ri documentation.'
|
||||
|
||||
banner <<-'BANNER'
|
||||
Usage: ri [spec]
|
||||
|
||||
View ri documentation. Relies on the "rdoc" gem being installed.
|
||||
See also "show-doc" command.
|
||||
|
||||
ri Array#each
|
||||
BANNER
|
||||
|
||||
def process(spec)
|
||||
unless spec
|
||||
return output.puts(
|
||||
"Please provide a class, module, or method name (e.g: ri Array#push)"
|
||||
)
|
||||
end
|
||||
|
||||
# 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 initialize(pager, opts)
|
||||
@pager = pager
|
||||
super opts
|
||||
end
|
||||
|
||||
def page
|
||||
paging_text = StringIO.new
|
||||
yield paging_text
|
||||
@pager.page(paging_text.string)
|
||||
end
|
||||
|
||||
def formatter(_io)
|
||||
if @formatter_klass
|
||||
@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 instance.
|
||||
ri = RDoc::RI::PryDriver.new(
|
||||
pry_instance.pager, 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
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::Ri)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,63 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class SaveFile < Pry::ClassCommand
|
||||
match 'save-file'
|
||||
group 'Input and Output'
|
||||
description 'Export to a file using content from the REPL.'
|
||||
|
||||
banner <<-'BANNER'
|
||||
Usage: save-file [OPTIONS] --to [FILE]
|
||||
|
||||
Export to a file using content from the REPL.
|
||||
|
||||
save-file my_method --to hello.rb
|
||||
save-file -i 1..10 --to hello.rb --append
|
||||
save-file show-method --to my_command.rb
|
||||
save-file sample_file.rb --lines 2..10 --to output_file.rb
|
||||
BANNER
|
||||
|
||||
def options(opt)
|
||||
CodeCollector.inject_options(opt)
|
||||
|
||||
opt.on :to=, "Specify the output file path"
|
||||
opt.on :a, :append, "Append output to file"
|
||||
end
|
||||
|
||||
def process
|
||||
@cc = CodeCollector.new(args, opts, pry_instance)
|
||||
raise CommandError, "Found no code to save." if @cc.content.empty?
|
||||
|
||||
if !file_name
|
||||
display_content
|
||||
else
|
||||
save_file
|
||||
end
|
||||
end
|
||||
|
||||
def file_name
|
||||
opts[:to] || nil
|
||||
end
|
||||
|
||||
def save_file
|
||||
File.open(file_name, mode) do |f|
|
||||
f.puts @cc.content
|
||||
end
|
||||
output.puts "#{file_name} successfully saved"
|
||||
end
|
||||
|
||||
def display_content
|
||||
output.puts @cc.content
|
||||
output.puts "\n\n--\nPlease use `--to FILE` to export to a file."
|
||||
output.puts "No file saved!\n--"
|
||||
end
|
||||
|
||||
def mode
|
||||
opts.present?(:append) ? "a" : "w"
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::SaveFile)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,75 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class ShellCommand < Pry::ClassCommand
|
||||
match(/\.(.*)/)
|
||||
group 'Input and Output'
|
||||
description "All text following a '.' is forwarded to the shell."
|
||||
command_options listing: '.<shell command>', use_prefix: false,
|
||||
takes_block: true, state: %i[old_pwd]
|
||||
|
||||
banner <<-'BANNER'
|
||||
Usage: .COMMAND_NAME
|
||||
|
||||
All text following a "." is forwarded to the shell.
|
||||
|
||||
.ls -aF
|
||||
.uname
|
||||
BANNER
|
||||
|
||||
def process(cmd)
|
||||
if cmd =~ /^cd\s*(.*)/i
|
||||
process_cd parse_destination(Regexp.last_match(1))
|
||||
else
|
||||
pass_block(cmd)
|
||||
if command_block
|
||||
command_block.call `#{cmd}`
|
||||
else
|
||||
pry_instance.config.system.call(output, cmd, pry_instance)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_destination(dest)
|
||||
return "~" if dest.empty?
|
||||
return dest unless dest == "-"
|
||||
|
||||
state.old_pwd || raise(CommandError, "No prior directory available")
|
||||
end
|
||||
|
||||
def process_cd(dest)
|
||||
state.old_pwd = Dir.pwd
|
||||
Dir.chdir(File.expand_path(path_from_cd_path(dest) || dest))
|
||||
rescue Errno::ENOENT
|
||||
raise CommandError, "No such directory: #{dest}"
|
||||
end
|
||||
|
||||
def cd_path_env
|
||||
Pry::Env['CDPATH']
|
||||
end
|
||||
|
||||
def cd_path_exists?
|
||||
cd_path_env && cd_path_env.length.nonzero?
|
||||
end
|
||||
|
||||
def path_from_cd_path(dest)
|
||||
return if !(dest && cd_path_exists?) || special_case_path?(dest)
|
||||
|
||||
cd_path_env.split(File::PATH_SEPARATOR).each do |path|
|
||||
return path if File.directory?(path) && path.split(File::SEPARATOR).last == dest
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def special_case_path?(dest)
|
||||
['.', '..', '-'].include?(dest) || dest =~ /\A[#{File::PATH_SEPARATOR}~]/
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::ShellCommand)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,30 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class ShellMode < Pry::ClassCommand
|
||||
match 'shell-mode'
|
||||
group 'Input and Output'
|
||||
description 'Toggle shell mode. Bring in pwd prompt and file completion.'
|
||||
command_options state: %i[disabled prev_prompt]
|
||||
|
||||
banner <<-'BANNER'
|
||||
Toggle shell mode. Bring in pwd prompt and file completion.
|
||||
BANNER
|
||||
|
||||
def process
|
||||
state.disabled ^= true
|
||||
|
||||
if state.disabled
|
||||
state.prev_prompt = pry_instance.prompt
|
||||
pry_instance.prompt = Pry::Prompt[:shell]
|
||||
else
|
||||
pry_instance.prompt = state.prev_prompt
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::ShellMode)
|
||||
Pry::Commands.alias_command 'file-mode', 'shell-mode'
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,93 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class ShowDoc < Command::ShowInfo
|
||||
include Pry::Helpers::DocumentationHelpers
|
||||
|
||||
match 'show-doc'
|
||||
group 'Introspection'
|
||||
description 'Show the documentation for a method or class.'
|
||||
|
||||
banner <<-BANNER
|
||||
Usage: show-doc [OPTIONS] [METH]
|
||||
Aliases: ?
|
||||
|
||||
Show the documentation for a method or class. Tries instance methods first and
|
||||
then methods by default.
|
||||
|
||||
show-doc hi_method # docs for hi_method
|
||||
show-doc Pry # for Pry class
|
||||
show-doc Pry -a # for all definitions of Pry class (all monkey patches)
|
||||
BANNER
|
||||
|
||||
def process
|
||||
super
|
||||
|
||||
output.puts(
|
||||
"\nWARNING: the show-doc command is deprecated. It will be removed " \
|
||||
"from future Pry versions.\nPlease use 'show-source' with the -d " \
|
||||
"(or --doc) switch instead\nExample: show-source #{obj_name} -d"
|
||||
)
|
||||
end
|
||||
|
||||
# The docs for code_object prepared for display.
|
||||
def content_for(code_object)
|
||||
Code.new(
|
||||
render_doc_markup_for(code_object),
|
||||
start_line_for(code_object),
|
||||
:text
|
||||
).with_line_numbers(use_line_numbers?).to_s
|
||||
end
|
||||
|
||||
# process the markup (if necessary) and apply colors
|
||||
def render_doc_markup_for(code_object)
|
||||
docs = docs_for(code_object)
|
||||
|
||||
if code_object.command?
|
||||
# command '--help' shouldn't use markup highlighting
|
||||
docs
|
||||
else
|
||||
if docs.empty?
|
||||
raise CommandError, "No docs found for: #{obj_name || 'current context'}"
|
||||
end
|
||||
|
||||
process_comment_markup(docs)
|
||||
end
|
||||
end
|
||||
|
||||
# Return docs for the code_object, adjusting for whether the code_object
|
||||
# has yard docs available, in which case it returns those.
|
||||
# (note we only have to check yard docs for modules since they can
|
||||
# have multiple docs, but methods can only be doc'd once so we
|
||||
# dont need to check them)
|
||||
def docs_for(code_object)
|
||||
if code_object.module_with_yard_docs?
|
||||
# yard docs
|
||||
code_object.yard_doc
|
||||
else
|
||||
# normal docs (i.e comments above method/module/command)
|
||||
code_object.doc
|
||||
end
|
||||
end
|
||||
|
||||
# Which sections to include in the 'header', can toggle: :owner,
|
||||
# :signature and visibility.
|
||||
def header_options
|
||||
super.merge signature: true
|
||||
end
|
||||
|
||||
# figure out start line of docs by back-calculating based on
|
||||
# number of lines in the comment and the start line of the code_object
|
||||
# @return [Fixnum] start line of docs
|
||||
def start_line_for(code_object)
|
||||
return 1 if code_object.command? || opts.present?(:'base-one')
|
||||
return 1 unless code_object.source_line
|
||||
|
||||
code_object.source_line - code_object.doc.lines.count
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::ShowDoc)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,234 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class ShowInfo < Pry::ClassCommand
|
||||
extend Pry::Helpers::BaseHelpers
|
||||
|
||||
command_options shellwords: false, interpolate: false
|
||||
|
||||
def initialize(*)
|
||||
super
|
||||
|
||||
@used_super = nil
|
||||
end
|
||||
|
||||
def options(opt)
|
||||
opt.on :s, :super, "Select the 'super' method. Can be repeated to " \
|
||||
"traverse the ancestors", as: :count
|
||||
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 :a, :all, "Show all definitions and monkeypatches of the " \
|
||||
"module/class"
|
||||
end
|
||||
|
||||
def process
|
||||
code_object = Pry::CodeObject.lookup(obj_name, pry_instance, super: opts[:super])
|
||||
raise CommandError, no_definition_message unless code_object
|
||||
|
||||
@original_code_object = code_object
|
||||
|
||||
if !obj_name && code_object.c_module? && !opts[:all]
|
||||
result = "You're inside an object, whose class is defined by means of " \
|
||||
"the C Ruby API.\nPry cannot display the information for this " \
|
||||
"class."
|
||||
if code_object.candidates.any?
|
||||
result += "\nHowever, you can view monkey-patches applied to this " \
|
||||
"class.\n.Just execute the same command with the '--all' " \
|
||||
"switch."
|
||||
end
|
||||
elsif show_all_modules?(code_object)
|
||||
# show all monkey patches for a module
|
||||
|
||||
result = content_and_headers_for_all_module_candidates(code_object)
|
||||
else
|
||||
# show a specific code object
|
||||
co = code_object_with_accessible_source(code_object)
|
||||
result = content_and_header_for_code_object(co)
|
||||
end
|
||||
|
||||
set_file_and_dir_locals(code_object.source_file)
|
||||
pry_instance.pager.page result
|
||||
end
|
||||
|
||||
# This method checks whether the `code_object` is a WrappedModule, if it
|
||||
# is, then it returns the first candidate (monkeypatch) with accessible
|
||||
# source (or docs). If `code_object` is not a WrappedModule (i.e a method
|
||||
# or a command) then the `code_object` itself is just returned.
|
||||
#
|
||||
# @return [Pry::WrappedModule, Pry::Method, Pry::Command]
|
||||
def code_object_with_accessible_source(code_object)
|
||||
return code_object unless code_object.is_a?(WrappedModule)
|
||||
|
||||
candidate = code_object.candidates.find(&:source)
|
||||
return candidate if candidate
|
||||
|
||||
raise CommandError, no_definition_message unless valid_superclass?(code_object)
|
||||
|
||||
@used_super = true
|
||||
code_object_with_accessible_source(code_object.super)
|
||||
end
|
||||
|
||||
def valid_superclass?(code_object)
|
||||
code_object.super && code_object.super.wrapped != Object
|
||||
end
|
||||
|
||||
def content_and_header_for_code_object(code_object)
|
||||
header(code_object) + content_for(code_object)
|
||||
end
|
||||
|
||||
def content_and_headers_for_all_module_candidates(mod)
|
||||
result = "Found #{mod.number_of_candidates} candidates for " \
|
||||
"`#{mod.name}` definition:\n"
|
||||
mod.number_of_candidates.times do |v|
|
||||
candidate = mod.candidate(v)
|
||||
begin
|
||||
result += "\nCandidate #{v + 1}/#{mod.number_of_candidates}: " \
|
||||
"#{candidate.source_file}:#{candidate.source_line}\n"
|
||||
content = content_for(candidate)
|
||||
|
||||
result += "Number of lines: #{content.lines.count}\n\n" + content
|
||||
rescue Pry::RescuableException
|
||||
result += "\nNo content found.\n"
|
||||
next
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
def no_definition_message
|
||||
"Couldn't locate a definition for #{obj_name}"
|
||||
end
|
||||
|
||||
# Generate a header (meta-data information) for all the code
|
||||
# object types: methods, modules, commands, procs...
|
||||
def header(code_object)
|
||||
file_name, line_num = file_and_line_for(code_object)
|
||||
content = content_for(code_object)
|
||||
|
||||
h = "\n#{bold('From:')} #{file_name}"
|
||||
h += code_object_header(code_object, line_num)
|
||||
h += "\n#{bold('Number of lines:')} " + "#{content.lines.count}\n\n"
|
||||
if @used_super
|
||||
h += bold('** Warning:')
|
||||
h += " Cannot find code for #{@original_code_object.nonblank_name}. " \
|
||||
"Showing superclass #{code_object.nonblank_name} instead. **\n\n"
|
||||
end
|
||||
|
||||
if content.lines.none?
|
||||
h += bold('** Warning:')
|
||||
h += " Cannot find code for '#{code_object.name}' (source_location is nil)"
|
||||
end
|
||||
|
||||
h
|
||||
end
|
||||
|
||||
def code_object_header(code_object, line_num)
|
||||
if code_object.real_method_object?
|
||||
method_header(code_object, line_num)
|
||||
|
||||
# It sucks we have to test for both Pry::WrappedModule and
|
||||
# WrappedModule::Candidate, probably indicates a deep refactor needs
|
||||
# to happen in those classes.
|
||||
elsif code_object.is_a?(Pry::WrappedModule) ||
|
||||
code_object.is_a?(Pry::WrappedModule::Candidate)
|
||||
module_header(code_object, line_num)
|
||||
else
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
def method_header(code_object, line_num)
|
||||
h = ""
|
||||
h += (code_object.c_method? ? ' (C Method):' : ":#{line_num}:")
|
||||
h += method_sections(code_object)[:owner]
|
||||
h += method_sections(code_object)[:visibility]
|
||||
h += method_sections(code_object)[:signature]
|
||||
h
|
||||
end
|
||||
|
||||
def module_header(code_object, line_num)
|
||||
h = ""
|
||||
h += ":#{line_num}\n"
|
||||
h += bold(code_object.module? ? "Module" : "Class")
|
||||
h += " #{bold('name:')} #{code_object.nonblank_name}"
|
||||
|
||||
if code_object.number_of_candidates > 1
|
||||
h += bold("\nNumber of monkeypatches: ")
|
||||
h += code_object.number_of_candidates.to_s
|
||||
h += ". Use the `-a` option to display all available monkeypatches"
|
||||
end
|
||||
h
|
||||
end
|
||||
|
||||
def method_sections(code_object)
|
||||
{
|
||||
owner: "\n#{bold('Owner:')} #{code_object.owner || 'N/A'}\n",
|
||||
visibility: "#{bold('Visibility:')} #{code_object.visibility}",
|
||||
signature: "\n#{bold('Signature:')} #{code_object.signature}"
|
||||
}.merge(header_options) { |_key, old, new| (new && old).to_s }
|
||||
end
|
||||
|
||||
def header_options
|
||||
{
|
||||
owner: true,
|
||||
visibility: true,
|
||||
signature: nil
|
||||
}
|
||||
end
|
||||
|
||||
def show_all_modules?(code_object)
|
||||
code_object.is_a?(Pry::WrappedModule) && opts.present?(:all)
|
||||
end
|
||||
|
||||
def obj_name
|
||||
@obj_name ||= args.empty? ? nil : args.join(' ')
|
||||
end
|
||||
|
||||
def use_line_numbers?
|
||||
opts.present?(:b) || opts.present?(:l)
|
||||
end
|
||||
|
||||
def start_line_for(code_object)
|
||||
if opts.present?(:'base-one')
|
||||
1
|
||||
else
|
||||
code_object.source_line || 1
|
||||
end
|
||||
end
|
||||
|
||||
# takes into account possible yard docs, and returns yard_file / yard_line
|
||||
# Also adjusts for start line of comments (using start_line_for), which it
|
||||
# has to infer by subtracting number of lines of comment from start line
|
||||
# of code_object
|
||||
def file_and_line_for(code_object)
|
||||
if code_object.module_with_yard_docs?
|
||||
[code_object.yard_file, code_object.yard_line]
|
||||
else
|
||||
[code_object.source_file, start_line_for(code_object)]
|
||||
end
|
||||
end
|
||||
|
||||
def complete(input)
|
||||
if input =~ /([^ ]*)#([a-z0-9_]*)\z/
|
||||
prefix = Regexp.last_match(1)
|
||||
search = Regexp.last_match(2)
|
||||
methods =
|
||||
begin
|
||||
# rubocop:disable Security/Eval
|
||||
Pry::Method.all_from_class(binding.eval(prefix))
|
||||
# rubocop:enable Security/Eval
|
||||
rescue RescuableException
|
||||
return super
|
||||
end
|
||||
methods.map do |method|
|
||||
[prefix, method.name].join('#') if method.name.start_with?(search)
|
||||
end.compact
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class ShowInput < Pry::ClassCommand
|
||||
match 'show-input'
|
||||
group 'Editing'
|
||||
description 'Show the contents of the input buffer for the current ' \
|
||||
'multi-line expression.'
|
||||
|
||||
banner <<-'BANNER'
|
||||
Show the contents of the input buffer for the current multi-line expression.
|
||||
BANNER
|
||||
|
||||
def process
|
||||
output.puts Code.new(eval_string).with_line_numbers
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::ShowInput)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,118 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class ShowSource < Command::ShowInfo
|
||||
include Pry::Helpers::DocumentationHelpers
|
||||
|
||||
match 'show-source'
|
||||
group 'Introspection'
|
||||
description 'Show the source for a method or class.'
|
||||
|
||||
banner <<-'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.
|
||||
|
||||
show-source hi_method
|
||||
show-source hi_method
|
||||
show-source Pry#rep # source for Pry#rep method
|
||||
show-source Pry # for Pry class
|
||||
show-source Pry -a # for all Pry class definitions (all monkey patches)
|
||||
show-source Pry.foo -e # for class of the return value of expression `Pry.foo`
|
||||
show-source Pry --super # for superclass of Pry (Object class)
|
||||
show-source Pry -d # include documentation
|
||||
|
||||
https://github.com/pry/pry/wiki/Source-browsing#wiki-Show_method
|
||||
BANNER
|
||||
|
||||
def options(opt)
|
||||
opt.on :e, :eval, "evaluate the command's argument as a ruby " \
|
||||
"expression and show the class its return value"
|
||||
opt.on :d, :doc, 'include documentation in the output'
|
||||
super(opt)
|
||||
end
|
||||
|
||||
def process
|
||||
if opts.present?(:e)
|
||||
obj = target.eval(args.first)
|
||||
self.args = Array.new(1) { obj.is_a?(Module) ? obj.name : obj.class.name }
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
# The source for code_object prepared for display.
|
||||
def content_for(code_object)
|
||||
content = ''
|
||||
if opts.present?(:d)
|
||||
code = Code.new(
|
||||
render_doc_markup_for(code_object), start_line_for(code_object), :text
|
||||
)
|
||||
content += code.with_line_numbers(use_line_numbers?).to_s
|
||||
content += "\n"
|
||||
end
|
||||
|
||||
code = Code.new(
|
||||
code_object.source || [], start_line_for(code_object)
|
||||
)
|
||||
content += code.with_line_numbers(use_line_numbers?).highlighted
|
||||
content
|
||||
end
|
||||
|
||||
# process the markup (if necessary) and apply colors
|
||||
def render_doc_markup_for(code_object)
|
||||
docs = docs_for(code_object)
|
||||
|
||||
if code_object.command?
|
||||
# command '--help' shouldn't use markup highlighting
|
||||
docs
|
||||
else
|
||||
if docs.empty?
|
||||
raise CommandError, "No docs found for: #{obj_name || 'current context'}"
|
||||
end
|
||||
|
||||
process_comment_markup(docs)
|
||||
end
|
||||
end
|
||||
|
||||
# Return docs for the code_object, adjusting for whether the code_object
|
||||
# has yard docs available, in which case it returns those.
|
||||
# (note we only have to check yard docs for modules since they can
|
||||
# have multiple docs, but methods can only be doc'd once so we
|
||||
# dont need to check them)
|
||||
def docs_for(code_object)
|
||||
if code_object.module_with_yard_docs?
|
||||
# yard docs
|
||||
code_object.yard_doc
|
||||
else
|
||||
# normal docs (i.e comments above method/module/command)
|
||||
code_object.doc
|
||||
end
|
||||
end
|
||||
|
||||
# Which sections to include in the 'header', can toggle: :owner,
|
||||
# :signature and visibility.
|
||||
def header_options
|
||||
super.merge signature: true
|
||||
end
|
||||
|
||||
# figure out start line of docs by back-calculating based on
|
||||
# number of lines in the comment and the start line of the code_object
|
||||
# @return [Fixnum] start line of docs
|
||||
def start_line_for(code_object)
|
||||
return 1 if code_object.command? || opts.present?(:'base-one')
|
||||
return 1 unless code_object.source_line
|
||||
|
||||
code_object.source_line - code_object.doc.lines.count
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::ShowSource)
|
||||
Pry::Commands.alias_command 'show-method', 'show-source'
|
||||
Pry::Commands.alias_command '$', 'show-source'
|
||||
Pry::Commands.alias_command '?', 'show-source -d'
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,44 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Stat < Pry::ClassCommand
|
||||
match 'stat'
|
||||
group 'Introspection'
|
||||
description 'View method information and set _file_ and _dir_ locals.'
|
||||
command_options shellwords: false
|
||||
|
||||
banner <<-'BANNER'
|
||||
Usage: stat [OPTIONS] [METH]
|
||||
|
||||
Show method information for method METH and set _file_ and _dir_ locals.
|
||||
|
||||
stat hello_method
|
||||
BANNER
|
||||
|
||||
def options(opt)
|
||||
method_options(opt)
|
||||
end
|
||||
|
||||
def process
|
||||
meth = method_object
|
||||
aliases = meth.aliases
|
||||
|
||||
output.puts(unindent(<<-OUTPUT))
|
||||
Method Information:
|
||||
--
|
||||
Name: #{meth.name}
|
||||
Alias#{'es' if aliases.length > 1}: #{aliases.any? ? aliases.join(', ') : 'None.'}
|
||||
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.'}
|
||||
OUTPUT
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::Stat)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class SwitchTo < Pry::ClassCommand
|
||||
match 'switch-to'
|
||||
group 'Navigating Pry'
|
||||
description 'Start a new subsession on a binding in the current stack.'
|
||||
|
||||
banner <<-'BANNER'
|
||||
Start a new subsession on a binding in the current stack (numbered by nesting).
|
||||
BANNER
|
||||
|
||||
def process(selection)
|
||||
selection = selection.to_i
|
||||
|
||||
if selection < 0 || selection > pry_instance.binding_stack.size - 1
|
||||
raise CommandError,
|
||||
"Invalid binding index #{selection} - use `nesting` command " \
|
||||
"to view valid indices."
|
||||
else
|
||||
Pry.start(pry_instance.binding_stack[selection])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::SwitchTo)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,28 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class ToggleColor < Pry::ClassCommand
|
||||
match 'toggle-color'
|
||||
group 'Misc'
|
||||
description 'Toggle syntax highlighting.'
|
||||
|
||||
banner <<-'BANNER'
|
||||
Usage: toggle-color
|
||||
|
||||
Toggle syntax highlighting.
|
||||
BANNER
|
||||
|
||||
def process
|
||||
pry_instance.color = color_toggle
|
||||
output.puts "Syntax highlighting #{pry_instance.color ? 'on' : 'off'}"
|
||||
end
|
||||
|
||||
def color_toggle
|
||||
!pry_instance.color
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,108 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class WatchExpression < Pry::ClassCommand
|
||||
match 'watch'
|
||||
group 'Context'
|
||||
description 'Watch the value of an expression and print a notification ' \
|
||||
'whenever it changes.'
|
||||
command_options use_prefix: false, state: %i[watch_expressions]
|
||||
|
||||
banner <<-'BANNER'
|
||||
Usage: watch [EXPRESSION]
|
||||
watch
|
||||
watch --delete [INDEX]
|
||||
|
||||
watch [EXPRESSION] adds an expression to the list of those being watched.
|
||||
It will be re-evaluated every time you hit enter in pry. If its value has
|
||||
changed, the new value will be printed to the console.
|
||||
|
||||
This is useful if you are step-through debugging and want to see how
|
||||
something changes over time. It's also useful if you're trying to write
|
||||
a method inside pry and want to check that it gives the right answers
|
||||
every time you redefine it.
|
||||
|
||||
watch on its own displays all the currently watched expressions and their
|
||||
values, and watch --delete [INDEX] allows you to delete expressions from
|
||||
the list being watched.
|
||||
BANNER
|
||||
|
||||
def options(opt)
|
||||
opt.on :d, :delete,
|
||||
"Delete the watch expression with the given index. If no index " \
|
||||
"is given; clear all watch expressions.",
|
||||
optional_argument: true, as: Integer
|
||||
opt.on :l, :list,
|
||||
"Show all current watch expressions and their values. Calling " \
|
||||
"watch with no expressions or options will also show the watch " \
|
||||
"expressions."
|
||||
end
|
||||
|
||||
def process
|
||||
if opts.present?(:delete)
|
||||
delete opts[:delete]
|
||||
elsif opts.present?(:list) || args.empty?
|
||||
list
|
||||
else
|
||||
add_hook
|
||||
add_expression
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def expressions
|
||||
state.watch_expressions ||= []
|
||||
end
|
||||
|
||||
def delete(index)
|
||||
if index
|
||||
output.puts "Deleting watch expression ##{index}: #{expressions[index - 1]}"
|
||||
expressions.delete_at(index - 1)
|
||||
else
|
||||
output.puts "Deleting all watched expressions"
|
||||
expressions.clear
|
||||
end
|
||||
end
|
||||
|
||||
def list
|
||||
if expressions.empty?
|
||||
output.puts "No watched expressions"
|
||||
else
|
||||
pry_instance.pager.open do |pager|
|
||||
pager.puts "Listing all watched expressions:"
|
||||
pager.puts ""
|
||||
expressions.each_with_index do |expr, index|
|
||||
pager.print with_line_numbers(expr.to_s, index + 1)
|
||||
end
|
||||
pager.puts ""
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def eval_and_print_changed(output)
|
||||
expressions.each do |expr|
|
||||
expr.eval!
|
||||
output.puts "#{blue 'watch'}: #{expr}" if expr.changed?
|
||||
end
|
||||
end
|
||||
|
||||
def add_expression
|
||||
expressions << Expression.new(pry_instance, target, arg_string)
|
||||
output.puts "Watching #{Code.new(arg_string).highlighted}"
|
||||
end
|
||||
|
||||
def add_hook
|
||||
hook = %i[after_eval watch_expression]
|
||||
return if pry_instance.hooks.hook_exists?(*hook)
|
||||
|
||||
pry_instance.hooks.add_hook(*hook) do |_, pry_instance|
|
||||
eval_and_print_changed pry_instance.output
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::WatchExpression)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class WatchExpression
|
||||
class Expression
|
||||
attr_reader :target, :source, :value, :previous_value, :pry_instance
|
||||
|
||||
def initialize(pry_instance, target, source)
|
||||
@pry_instance = pry_instance
|
||||
@target = target
|
||||
@source = Code.new(source).strip
|
||||
end
|
||||
|
||||
def eval!
|
||||
@previous_value = value
|
||||
@value = Pry::ColorPrinter.pp(target_eval(target, source), ''.dup)
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#{Code.new(source).highlighted.strip} => #{value}"
|
||||
end
|
||||
|
||||
# Has the value of the expression changed?
|
||||
#
|
||||
# We use the pretty-printed string representation to detect differences
|
||||
# as this avoids problems with dup (causes too many differences) and ==
|
||||
# (causes too few)
|
||||
def changed?
|
||||
(value != previous_value)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def target_eval(target, source)
|
||||
target.eval(source)
|
||||
rescue StandardError => e
|
||||
e
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,205 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'method_source'
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Whereami < Pry::ClassCommand
|
||||
def initialize(*)
|
||||
super
|
||||
|
||||
@method_code = nil
|
||||
end
|
||||
|
||||
class << self
|
||||
attr_accessor :method_size_cutoff
|
||||
end
|
||||
|
||||
@method_size_cutoff = 30
|
||||
|
||||
match 'whereami'
|
||||
description 'Show code surrounding the current context.'
|
||||
group 'Context'
|
||||
|
||||
banner <<-'BANNER'
|
||||
Usage: whereami [-qn] [LINES]
|
||||
|
||||
Describe the current location. If you use `binding.pry` inside a method then
|
||||
whereami will print out the source for that method.
|
||||
|
||||
If a number is passed, then LINES lines before and after the current line will be
|
||||
shown instead of the method itself.
|
||||
|
||||
The `-q` flag can be used to suppress error messages in the case that there's
|
||||
no code to show. This is used by pry in the default before_session hook to show
|
||||
you when you arrive at a `binding.pry`.
|
||||
|
||||
The `-n` flag can be used to hide line numbers so that code can be copy/pasted
|
||||
effectively.
|
||||
|
||||
When pry was started on an Object and there is no associated method, whereami
|
||||
will instead output a brief description of the current object.
|
||||
BANNER
|
||||
|
||||
def setup
|
||||
if target.respond_to?(:source_location)
|
||||
file, @line = target.source_location
|
||||
@file = expand_path(file)
|
||||
else
|
||||
@file = expand_path(target.eval('__FILE__'))
|
||||
@line = target.eval('__LINE__')
|
||||
end
|
||||
@method = Pry::Method.from_binding(target)
|
||||
end
|
||||
|
||||
def options(opt)
|
||||
opt.on :q, :quiet, "Don't display anything in case of an error"
|
||||
opt.on :n, :"no-line-numbers", "Do not display line numbers"
|
||||
opt.on :m, :method, "Show the complete source for the current method."
|
||||
opt.on :c, :class, "Show the complete source for the current class or module."
|
||||
opt.on :f, :file, "Show the complete source for the current file."
|
||||
end
|
||||
|
||||
def code
|
||||
@code ||= if opts.present?(:m)
|
||||
method_code || raise(CommandError, "Cannot find method code.")
|
||||
elsif opts.present?(:c)
|
||||
class_code || raise(CommandError, "Cannot find class code.")
|
||||
elsif opts.present?(:f)
|
||||
Pry::Code.from_file(@file)
|
||||
elsif args.any?
|
||||
code_window
|
||||
else
|
||||
default_code
|
||||
end
|
||||
end
|
||||
|
||||
def code?
|
||||
!!code
|
||||
rescue MethodSource::SourceNotFoundError
|
||||
false
|
||||
end
|
||||
|
||||
def bad_option_combination?
|
||||
[opts.present?(:m), opts.present?(:f),
|
||||
opts.present?(:c), args.any?].count(true) > 1
|
||||
end
|
||||
|
||||
def location
|
||||
"#{@file}:#{@line} #{@method && @method.name_with_owner}"
|
||||
end
|
||||
|
||||
def process
|
||||
if bad_option_combination?
|
||||
raise CommandError, "Only one of -m, -c, -f, and LINES may be specified."
|
||||
end
|
||||
|
||||
return if nothing_to_do?
|
||||
|
||||
if internal_binding?(target)
|
||||
handle_internal_binding
|
||||
return
|
||||
end
|
||||
|
||||
set_file_and_dir_locals(@file)
|
||||
|
||||
pretty_code = code.with_line_numbers(use_line_numbers?)
|
||||
.with_marker(marker)
|
||||
.highlighted
|
||||
pry_instance.pager.page(
|
||||
"\n#{bold('From:')} #{location}:\n\n" + pretty_code + "\n"
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def nothing_to_do?
|
||||
opts.quiet? && (internal_binding?(target) || !code?)
|
||||
end
|
||||
|
||||
def use_line_numbers?
|
||||
!opts.present?(:n)
|
||||
end
|
||||
|
||||
def marker
|
||||
!opts.present?(:n) && @line
|
||||
end
|
||||
|
||||
def top_level?
|
||||
target_self == Pry.main
|
||||
end
|
||||
|
||||
def handle_internal_binding
|
||||
if top_level?
|
||||
output.puts "At the top level."
|
||||
else
|
||||
output.puts "Inside #{Pry.view_clip(target_self)}."
|
||||
end
|
||||
end
|
||||
|
||||
def small_method?
|
||||
@method.source_range.count < self.class.method_size_cutoff
|
||||
end
|
||||
|
||||
def default_code
|
||||
if method_code && small_method?
|
||||
method_code
|
||||
else
|
||||
code_window
|
||||
end
|
||||
end
|
||||
|
||||
def code_window
|
||||
Pry::Code.from_file(@file).around(@line, window_size)
|
||||
end
|
||||
|
||||
def method_code
|
||||
return @method_code if @method_code
|
||||
|
||||
@method_code = Pry::Code.from_method(@method) if valid_method?
|
||||
end
|
||||
|
||||
# This either returns the `target_self`
|
||||
# or it returns the class of `target_self` if `target_self` is not a class.
|
||||
# @return [Pry::WrappedModule]
|
||||
def target_class
|
||||
return Pry::WrappedModule(target_self) if target_self.is_a?(Module)
|
||||
|
||||
Pry::WrappedModule(target_self.class)
|
||||
end
|
||||
|
||||
def class_code
|
||||
@class_code ||=
|
||||
begin
|
||||
mod = @method ? Pry::WrappedModule(@method.owner) : target_class
|
||||
idx = mod.candidates.find_index { |v| expand_path(v.source_file) == @file }
|
||||
idx && Pry::Code.from_module(mod, idx)
|
||||
end
|
||||
end
|
||||
|
||||
def valid_method?
|
||||
@method && @method.source? && expand_path(@method.source_file) == @file &&
|
||||
@method.source_range.include?(@line)
|
||||
end
|
||||
|
||||
def expand_path(filename)
|
||||
return unless filename
|
||||
return filename if Pry.eval_path == filename
|
||||
|
||||
File.expand_path(filename)
|
||||
end
|
||||
|
||||
def window_size
|
||||
if args.empty?
|
||||
pry_instance.config.default_window_size
|
||||
else
|
||||
args.first.to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::Whereami)
|
||||
Pry::Commands.alias_command '@', 'whereami'
|
||||
Pry::Commands.alias_command(/whereami[!?]+/, 'whereami')
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,95 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Command
|
||||
class Wtf < Pry::ClassCommand
|
||||
match(/wtf([?!]*)/)
|
||||
group 'Context'
|
||||
description 'Show the backtrace of the most recent exception.'
|
||||
options listing: 'wtf?'
|
||||
|
||||
banner <<-'BANNER'
|
||||
Usage: wtf[?|!]
|
||||
|
||||
Shows 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.
|
||||
|
||||
wtf?
|
||||
wtf?!???!?!?
|
||||
|
||||
# To see the entire backtrace, pass the `-v` or `--verbose` flag.
|
||||
wtf -v
|
||||
BANNER
|
||||
|
||||
RUBY_FRAME_PATTERN = /\A(?<file>(.+)):(?<line>(\d+))/.freeze
|
||||
|
||||
def options(opt)
|
||||
opt.on :v, :verbose, "Show the full backtrace"
|
||||
opt.on :c, :code, "Show code corresponding to the backtrace frame"
|
||||
end
|
||||
|
||||
def process
|
||||
unless pry_instance.last_exception
|
||||
raise Pry::CommandError, "No most-recent exception"
|
||||
end
|
||||
|
||||
text = ''.dup
|
||||
unwind_exceptions.each_with_index do |exception, i|
|
||||
title = (i == 0 ? 'Exception' : 'Caused by')
|
||||
text << format_header(title, exception)
|
||||
text << format_backtrace(exception.backtrace)
|
||||
end
|
||||
output.puts(text)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def unwind_exceptions
|
||||
exception_list = []
|
||||
exception = pry_instance.last_exception
|
||||
|
||||
while exception
|
||||
exception_list << exception
|
||||
exception = (exception.cause if exception.respond_to?(:cause))
|
||||
end
|
||||
|
||||
exception_list
|
||||
end
|
||||
|
||||
def format_header(title, exception)
|
||||
"#{bold(title + ':')} #{exception.class}: #{exception}\n--\n"
|
||||
end
|
||||
|
||||
def format_backtrace(backtrace)
|
||||
lines = trim_backtrace(backtrace).map do |frame|
|
||||
next frame unless opts.code?
|
||||
|
||||
match = frame.match(RUBY_FRAME_PATTERN)
|
||||
code = read_line(match[:file], match[:line].to_i)
|
||||
[bold(frame), code].join("\n")
|
||||
end
|
||||
|
||||
Pry::Code.new(lines.compact, 0, :text).with_line_numbers.to_s
|
||||
end
|
||||
|
||||
def trim_backtrace(backtrace)
|
||||
return backtrace if opts.verbose?
|
||||
|
||||
size_of_backtrace = [captures[0].size, 0.5].max * 10
|
||||
backtrace.first(size_of_backtrace)
|
||||
end
|
||||
|
||||
def read_line(file, line)
|
||||
File.open(file, 'r') do |f|
|
||||
(line - 1).times { f.gets }
|
||||
f.gets
|
||||
end
|
||||
rescue Errno::ENOENT
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
Pry::Commands.add_command(Pry::Command::Wtf)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,314 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
# @api private
|
||||
class Config
|
||||
extend Attributable
|
||||
|
||||
# @return [IO, #readline] the object from which Pry retrieves its lines of
|
||||
# input
|
||||
attribute :input
|
||||
|
||||
# @return [IO, #puts] where Pry should output results provided by {input}
|
||||
attribute :output
|
||||
|
||||
# @return [Pry::CommandSet]
|
||||
attribute :commands
|
||||
|
||||
# @return [Proc] the printer for Ruby expressions (not commands)
|
||||
attribute :print
|
||||
|
||||
# @return [Proc] the printer for exceptions
|
||||
attribute :exception_handler
|
||||
|
||||
# @return [Array] Exception that Pry shouldn't rescue
|
||||
attribute :unrescued_exceptions
|
||||
|
||||
# @return [Integer] The number of lines of context to show before and after
|
||||
# exceptions
|
||||
attribute :default_window_size
|
||||
|
||||
# @return [Pry::Hooks]
|
||||
attribute :hooks
|
||||
|
||||
# @return [Pry::Prompt]
|
||||
attribute :prompt
|
||||
|
||||
# @return [String] The display name that is part of the prompt
|
||||
attribute :prompt_name
|
||||
|
||||
# @return [Array<Object>] the list of objects that are known to have a
|
||||
# 1-line #inspect output suitable for prompt
|
||||
attribute :prompt_safe_contexts
|
||||
|
||||
# If it is a String, then that String is used as the shell
|
||||
# command to invoke the editor.
|
||||
#
|
||||
# If it responds to #call is callable then `file`, `line`, and `reloading`
|
||||
# are passed to it. `reloading` indicates whether Pry will be reloading code
|
||||
# after the shell command returns. All parameters are optional.
|
||||
# @return [String, #call]
|
||||
attribute :editor
|
||||
|
||||
# A string that must precede all commands. For example, if is is
|
||||
# set to "%", the "cd" command must be invoked as "%cd").
|
||||
# @return [String]
|
||||
attribute :command_prefix
|
||||
|
||||
# @return [Boolean]
|
||||
attribute :color
|
||||
|
||||
# @return [Boolean]
|
||||
attribute :pager
|
||||
|
||||
# @return [Boolean]
|
||||
attribute :multiline
|
||||
|
||||
# @return [Boolean] whether the global ~/.pryrc should be loaded
|
||||
attribute :should_load_rc
|
||||
|
||||
# @return [Boolean] whether the local ./.pryrc should be loaded
|
||||
attribute :should_load_local_rc
|
||||
|
||||
# @return [Boolean] whether to load files specified with the -r flag
|
||||
attribute :should_load_requires
|
||||
|
||||
# @return [Boolean] whether to disable edit-method's auto-reloading behavior
|
||||
attribute :disable_auto_reload
|
||||
|
||||
# Whether Pry should trap SIGINT and cause it to raise an Interrupt
|
||||
# exception. This is only useful on JRuby, MRI does this for us.
|
||||
# @return [Boolean]
|
||||
attribute :should_trap_interrupts
|
||||
|
||||
# @return [Pry::History]
|
||||
attribute :history
|
||||
|
||||
# @return [Boolean]
|
||||
attribute :history_save
|
||||
|
||||
# @return [Boolean]
|
||||
attribute :history_load
|
||||
|
||||
# @return [String]
|
||||
attribute :history_file
|
||||
|
||||
# @return [Array<String,Regexp>]
|
||||
attribute :history_ignorelist
|
||||
|
||||
# @return [Array<String>] Ruby files to be required
|
||||
attribute :requires
|
||||
|
||||
# @return [Integer] how many input/output lines to keep in memory
|
||||
attribute :memory_size
|
||||
|
||||
# @return [Proc] The proc that runs system commands
|
||||
attribute :system
|
||||
|
||||
# @return [Boolean]
|
||||
attribute :auto_indent
|
||||
|
||||
# @return [Boolean]
|
||||
attribute :correct_indent
|
||||
|
||||
# @return [Boolean] whether or not display a warning when a command name
|
||||
# collides with a method/local in the current context.
|
||||
attribute :collision_warning
|
||||
|
||||
# @return [Hash{Symbol=>Proc}]
|
||||
attribute :extra_sticky_locals
|
||||
|
||||
# @return [#build_completion_proc] a completer to use
|
||||
attribute :completer
|
||||
|
||||
# @return [Boolean] suppresses whereami output on `binding.pry`
|
||||
attribute :quiet
|
||||
|
||||
# @return [Boolean] displays a warning about experience improvement on
|
||||
# Windows
|
||||
attribute :windows_console_warning
|
||||
|
||||
# @return [Proc]
|
||||
attribute :command_completions
|
||||
|
||||
# @return [Proc]
|
||||
attribute :file_completions
|
||||
|
||||
# @return [Hash]
|
||||
attribute :ls
|
||||
|
||||
# @return [String] a line of code to execute in context before the session
|
||||
# starts
|
||||
attribute :exec_string
|
||||
|
||||
# @return [String]
|
||||
attribute :output_prefix
|
||||
|
||||
# @return [String]
|
||||
# @since v0.13.0
|
||||
attribute :rc_file
|
||||
|
||||
def initialize
|
||||
merge!(
|
||||
input: MemoizedValue.new { choose_input },
|
||||
output: $stdout.tap { |out| out.sync = true },
|
||||
commands: Pry::Commands,
|
||||
prompt_name: 'pry',
|
||||
prompt: Pry::Prompt[:default],
|
||||
prompt_safe_contexts: [String, Numeric, Symbol, nil, true, false],
|
||||
print: Pry::ColorPrinter.method(:default),
|
||||
quiet: false,
|
||||
exception_handler: Pry::ExceptionHandler.method(:handle_exception),
|
||||
|
||||
unrescued_exceptions: [
|
||||
::SystemExit, ::SignalException, Pry::TooSafeException
|
||||
],
|
||||
|
||||
hooks: Pry::Hooks.default,
|
||||
pager: true,
|
||||
system: Pry::SystemCommandHandler.method(:default),
|
||||
multiline: true,
|
||||
color: Pry::Helpers::BaseHelpers.use_ansi_codes?,
|
||||
default_window_size: 5,
|
||||
editor: Pry::Editor.default,
|
||||
rc_file: default_rc_file,
|
||||
should_load_rc: true,
|
||||
should_load_local_rc: true,
|
||||
should_trap_interrupts: Pry::Helpers::Platform.jruby?,
|
||||
disable_auto_reload: false,
|
||||
command_prefix: '',
|
||||
auto_indent: Pry::Helpers::BaseHelpers.use_ansi_codes?,
|
||||
correct_indent: true,
|
||||
collision_warning: false,
|
||||
output_prefix: '=> ',
|
||||
requires: [],
|
||||
should_load_requires: true,
|
||||
windows_console_warning: true,
|
||||
control_d_handler: Pry::ControlDHandler.method(:default),
|
||||
memory_size: 100,
|
||||
extra_sticky_locals: {},
|
||||
command_completions: proc { commands.keys },
|
||||
file_completions: proc { Dir['.'] },
|
||||
ls: Pry::Command::Ls::Config.default,
|
||||
completer: Pry::InputCompleter,
|
||||
history_save: true,
|
||||
history_load: true,
|
||||
history_file: Pry::History.default_file,
|
||||
history_ignorelist: [],
|
||||
history: MemoizedValue.new do
|
||||
if defined?(input::HISTORY)
|
||||
Pry::History.new(history: input::HISTORY)
|
||||
else
|
||||
Pry::History.new
|
||||
end
|
||||
end,
|
||||
exec_string: ''
|
||||
)
|
||||
|
||||
@custom_attrs = {}
|
||||
end
|
||||
|
||||
def merge!(config_hash)
|
||||
config_hash.each_pair { |attr, value| __send__("#{attr}=", value) }
|
||||
self
|
||||
end
|
||||
|
||||
def merge(config_hash)
|
||||
dup.merge!(config_hash)
|
||||
end
|
||||
|
||||
def []=(attr, value)
|
||||
@custom_attrs[attr.to_s] = Config::Value.new(value)
|
||||
end
|
||||
|
||||
def [](attr)
|
||||
@custom_attrs[attr.to_s].call
|
||||
end
|
||||
|
||||
# rubocop:disable Style/MethodMissingSuper
|
||||
def method_missing(method_name, *args, &_block)
|
||||
name = method_name.to_s
|
||||
|
||||
if name.end_with?('=')
|
||||
self[name[0..-2]] = args.first
|
||||
elsif @custom_attrs.key?(name)
|
||||
self[name]
|
||||
end
|
||||
end
|
||||
# rubocop:enable Style/MethodMissingSuper
|
||||
|
||||
def respond_to_missing?(method_name, include_all = false)
|
||||
@custom_attrs.key?(method_name.to_s.tr('=', '')) || super
|
||||
end
|
||||
|
||||
def initialize_dup(other)
|
||||
super
|
||||
@custom_attrs = @custom_attrs.dup
|
||||
end
|
||||
|
||||
attr_reader :control_d_handler
|
||||
def control_d_handler=(value)
|
||||
proxy_proc =
|
||||
if value.arity == 2
|
||||
Pry::Warning.warn(
|
||||
"control_d_handler's arity of 2 parameters was deprecated " \
|
||||
'(eval_string, pry_instance). Now it gets passed just 1 ' \
|
||||
'parameter (pry_instance)'
|
||||
)
|
||||
proc do |*args|
|
||||
if args.size == 2
|
||||
value.call(args.first, args[1])
|
||||
else
|
||||
value.call(args.first.eval_string, args.first)
|
||||
end
|
||||
end
|
||||
else
|
||||
proc do |*args|
|
||||
if args.size == 2
|
||||
value.call(args[1])
|
||||
else
|
||||
value.call(args.first)
|
||||
end
|
||||
end
|
||||
end
|
||||
@control_d_handler = proxy_proc
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def choose_input
|
||||
input = load_readline
|
||||
|
||||
if Pry::Env['TERM'] == 'dumb' && (defined?(Reline) && input == Reline)
|
||||
input = Pry::Input::SimpleStdio
|
||||
end
|
||||
|
||||
input
|
||||
end
|
||||
|
||||
def load_readline
|
||||
require 'readline'
|
||||
::Readline
|
||||
rescue LoadError
|
||||
output.puts(
|
||||
"Sorry, you can't use Pry without Readline or a compatible library. \n" \
|
||||
"Possible solutions: \n" \
|
||||
" * Rebuild Ruby with Readline support using `--with-readline` \n" \
|
||||
" * Use the rb-readline gem, which is a pure-Ruby port of Readline \n" \
|
||||
" * Use the pry-coolline gem, a pure-ruby alternative to Readline"
|
||||
)
|
||||
raise
|
||||
end
|
||||
|
||||
def default_rc_file
|
||||
[Pry::Env['PRYRC'],
|
||||
# See XDG Base Directory Specification at
|
||||
# https://specifications.freedesktop.org/basedir-spec/latest/
|
||||
"#{Pry::Env['XDG_CONFIG_HOME']}/pry/pryrc",
|
||||
File.expand_path('~/.pryrc'),
|
||||
File.expand_path('~/.config/pry/pryrc')]
|
||||
.compact
|
||||
.find { |file| File.exist?(file) }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Config
|
||||
# Attributable provides the ability to create "attribute"
|
||||
# accessors. Attribute accessors create a standard "attr_writer" and a
|
||||
# customised "attr_reader". This reader is Proc-aware (lazy).
|
||||
#
|
||||
# @since v0.13.0
|
||||
# @api private
|
||||
module Attributable
|
||||
def attribute(attr_name)
|
||||
define_method(attr_name) do
|
||||
value = Config::Value.new(instance_variable_get("@#{attr_name}"))
|
||||
value.call
|
||||
end
|
||||
|
||||
attr_writer(attr_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Config
|
||||
# LazyValue is a Proc (block) wrapper. It is meant to be used as a
|
||||
# configuration value. Subsequent `#call` calls always evaluate the given
|
||||
# block.
|
||||
#
|
||||
# @example
|
||||
# num = 19
|
||||
# value = Pry::Config::LazyValue.new { num += 1 }
|
||||
# value.foo # => 20
|
||||
# value.foo # => 21
|
||||
# value.foo # => 22
|
||||
#
|
||||
# @api private
|
||||
# @since v0.13.0
|
||||
# @see Pry::Config::MemoizedValue
|
||||
class LazyValue
|
||||
def initialize(&block)
|
||||
@block = block
|
||||
end
|
||||
|
||||
def call
|
||||
@block.call
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,34 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Config
|
||||
# MemoizedValue is a Proc (block) wrapper. It is meant to be used as a
|
||||
# configuration value. Subsequent `#call` calls return the same memoized
|
||||
# result.
|
||||
#
|
||||
# @example
|
||||
# num = 19
|
||||
# value = Pry::Config::MemoizedValue.new { num += 1 }
|
||||
# value.call # => 20
|
||||
# value.call # => 20
|
||||
# value.call # => 20
|
||||
#
|
||||
# @api private
|
||||
# @since v0.13.0
|
||||
# @see Pry::Config::LazyValue
|
||||
class MemoizedValue
|
||||
def initialize(&block)
|
||||
@block = block
|
||||
@called = false
|
||||
@call = nil
|
||||
end
|
||||
|
||||
def call
|
||||
return @call if @called
|
||||
|
||||
@called = true
|
||||
@call = @block.call
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,24 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
class Config
|
||||
# Value holds a value for the given attribute and decides how it should
|
||||
# be read. Procs get called, other values are returned as is.
|
||||
#
|
||||
# @since v0.13.0
|
||||
# @api private
|
||||
class Value
|
||||
def initialize(value)
|
||||
@value = value
|
||||
end
|
||||
|
||||
def call
|
||||
unless [Config::MemoizedValue, Config::LazyValue].include?(@value.class)
|
||||
return @value
|
||||
end
|
||||
|
||||
@value.call
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,28 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
# @api private
|
||||
# @since v0.13.0
|
||||
module ControlDHandler
|
||||
# Deal with the ^D key being pressed. Different behaviour in different
|
||||
# cases:
|
||||
# 1. In an expression behave like `!` command.
|
||||
# 2. At top-level session behave like `exit` command.
|
||||
# 3. In a nested session behave like `cd ..`.
|
||||
def self.default(pry_instance)
|
||||
if !pry_instance.eval_string.empty?
|
||||
# Clear input buffer.
|
||||
pry_instance.eval_string = ''
|
||||
elsif pry_instance.binding_stack.one?
|
||||
pry_instance.binding_stack.clear
|
||||
throw(:breakout)
|
||||
else
|
||||
# Otherwise, saves current binding stack as old stack and pops last
|
||||
# binding out of binding stack (the old stack still has that binding).
|
||||
cd_state = Pry::CommandState.default.state_for(Pry::Command::Cd)
|
||||
cd_state.old_stack = pry_instance.binding_stack.dup
|
||||
pry_instance.binding_stack.pop
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,144 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
# @return [Array] Code of the method used when implementing Pry's
|
||||
# __binding__, along with line indication to be used with instance_eval (and
|
||||
# friends).
|
||||
#
|
||||
# @see Object#__binding__
|
||||
BINDING_METHOD_IMPL = [<<-METHOD, __FILE__, __LINE__ + 1].freeze
|
||||
# Get a binding with 'self' set to self, and no locals.
|
||||
#
|
||||
# The default definee is determined by the context in which the
|
||||
# definition is eval'd.
|
||||
#
|
||||
# Please don't call this method directly, see {__binding__}.
|
||||
#
|
||||
# @return [Binding]
|
||||
def __pry__
|
||||
binding
|
||||
end
|
||||
METHOD
|
||||
end
|
||||
|
||||
class Object
|
||||
# Start a Pry REPL on self.
|
||||
#
|
||||
# If `self` is a Binding then that will be used to evaluate expressions;
|
||||
# otherwise a new binding will be created.
|
||||
#
|
||||
# @param [Object] object the object or binding to pry
|
||||
# (__deprecated__, use `object.pry`)
|
||||
# @param [Hash] hash the options hash
|
||||
# @example With a binding
|
||||
# binding.pry
|
||||
# @example On any object
|
||||
# "dummy".pry
|
||||
# @example With options
|
||||
# def my_method
|
||||
# binding.pry :quiet => true
|
||||
# end
|
||||
# my_method()
|
||||
# @see Pry.start
|
||||
def pry(object = nil, hash = {})
|
||||
if object.nil? || Hash === object # rubocop:disable Style/CaseEquality
|
||||
Pry.start(self, object || {})
|
||||
else
|
||||
Pry.start(object, hash)
|
||||
end
|
||||
end
|
||||
|
||||
# Return a binding object for the receiver.
|
||||
#
|
||||
# The `self` of the binding is set to the current object, and it contains no
|
||||
# local variables.
|
||||
#
|
||||
# The default definee (http://yugui.jp/articles/846) is set such that:
|
||||
#
|
||||
# * If `self` is a class or module, then new methods created in the binding
|
||||
# will be defined in that class or module (as in `class Foo; end`).
|
||||
# * If `self` is a normal object, then new methods created in the binding will
|
||||
# be defined on its singleton class (as in `class << self; end`).
|
||||
# * If `self` doesn't have a real singleton class (i.e. it is a Fixnum, Float,
|
||||
# Symbol, nil, true, or false), then new methods will be created on the
|
||||
# object's class (as in `self.class.class_eval{ }`)
|
||||
#
|
||||
# Newly created constants, including classes and modules, will also be added
|
||||
# to the default definee.
|
||||
#
|
||||
# @return [Binding]
|
||||
def __binding__
|
||||
# If you ever feel like changing this method, be careful about variables
|
||||
# that you use. They shouldn't be inserted into the binding that will
|
||||
# eventually be returned.
|
||||
|
||||
# When you're cd'd into a class, methods you define should be added to it.
|
||||
if is_a?(Module)
|
||||
# A special case, for JRuby.
|
||||
# Module.new.class_eval("binding") has different behaviour than CRuby,
|
||||
# where this is not needed: class_eval("binding") vs class_eval{binding}.
|
||||
# Using a block works around the difference of behaviour on JRuby.
|
||||
# The scope is clear of local variables. Don't add any.
|
||||
#
|
||||
# This fixes the following two spec failures, at https://travis-ci.org/pry/pry/jobs/274470002
|
||||
# 1) ./spec/pry_spec.rb:360:in `block in (root)'
|
||||
# 2) ./spec/pry_spec.rb:366:in `block in (root)'
|
||||
return class_eval { binding } if Pry::Helpers::Platform.jruby? && name.nil?
|
||||
|
||||
# class_eval sets both self and the default definee to this class.
|
||||
return class_eval("binding", __FILE__, __LINE__)
|
||||
end
|
||||
|
||||
unless self.class.method_defined?(:__pry__)
|
||||
# The easiest way to check whether an object has a working singleton class
|
||||
# is to try and define a method on it. (just checking for the presence of
|
||||
# the singleton class gives false positives for `true` and `false`).
|
||||
# __pry__ is just the closest method we have to hand, and using
|
||||
# it has the nice property that we can memoize this check.
|
||||
begin
|
||||
# instance_eval sets the default definee to the object's singleton class
|
||||
instance_eval(*Pry::BINDING_METHOD_IMPL)
|
||||
|
||||
# If we can't define methods on the Object's singleton_class. Then we fall
|
||||
# back to setting the default definee to be the Object's class. That seems
|
||||
# nicer than having a REPL in which you can't define methods.
|
||||
rescue TypeError, Pry::FrozenObjectException
|
||||
# class_eval sets the default definee to self.class
|
||||
self.class.class_eval(*Pry::BINDING_METHOD_IMPL)
|
||||
end
|
||||
end
|
||||
|
||||
__pry__
|
||||
end
|
||||
end
|
||||
|
||||
class BasicObject
|
||||
# Return a binding object for the receiver.
|
||||
#
|
||||
# The `self` of the binding is set to the current object, and it contains no
|
||||
# local variables.
|
||||
#
|
||||
# The default definee (http://yugui.jp/articles/846) is set such that new
|
||||
# methods defined will be added to the singleton class of the BasicObject.
|
||||
#
|
||||
# @return [Binding]
|
||||
def __binding__
|
||||
# BasicObjects don't have respond_to?, so we just define the method
|
||||
# every time. As they also don't have `.freeze`, this call won't
|
||||
# fail as it can for normal Objects.
|
||||
(class << self; self; end).class_eval(<<-METHOD, __FILE__, __LINE__ + 1)
|
||||
# Get a binding with 'self' set to self, and no locals.
|
||||
#
|
||||
# The default definee is determined by the context in which the
|
||||
# definition is eval'd.
|
||||
#
|
||||
# Please don't call this method directly, see {__binding__}.
|
||||
#
|
||||
# @return [Binding]
|
||||
def __pry__
|
||||
::Kernel.binding
|
||||
end
|
||||
METHOD
|
||||
__pry__
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,157 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'shellwords'
|
||||
|
||||
class Pry
|
||||
class Editor
|
||||
def self.default
|
||||
if (visual = Pry::Env['VISUAL'])
|
||||
return visual
|
||||
end
|
||||
|
||||
if (editor = Pry::Env['EDITOR'])
|
||||
return editor
|
||||
end
|
||||
|
||||
return 'notepad' if Helpers::Platform.windows?
|
||||
|
||||
%w[editor nano vi].find do |editor_exe|
|
||||
Kernel.system("which #{editor_exe} > /dev/null 2>&1")
|
||||
end
|
||||
end
|
||||
|
||||
include Pry::Helpers::CommandHelpers
|
||||
|
||||
attr_reader :pry_instance
|
||||
|
||||
def initialize(pry_instance)
|
||||
@pry_instance = pry_instance
|
||||
end
|
||||
|
||||
def edit_tempfile_with_content(initial_content, line = 1)
|
||||
temp_file do |f|
|
||||
f.puts(initial_content)
|
||||
f.flush
|
||||
f.close(false)
|
||||
invoke_editor(f.path, line, true)
|
||||
File.read(f.path)
|
||||
end
|
||||
end
|
||||
|
||||
def invoke_editor(file, line, blocking = true)
|
||||
unless pry_instance.config.editor
|
||||
raise CommandError,
|
||||
"Please set Pry.config.editor or export $VISUAL or $EDITOR"
|
||||
end
|
||||
|
||||
editor_invocation = build_editor_invocation_string(file, line, blocking)
|
||||
return nil unless editor_invocation
|
||||
|
||||
if Helpers::Platform.jruby?
|
||||
open_editor_on_jruby(editor_invocation)
|
||||
else
|
||||
open_editor(editor_invocation)
|
||||
end
|
||||
end
|
||||
|
||||
# Generate the string that's used to start the editor. This includes
|
||||
# all the flags we want as well as the file and line number we
|
||||
# want to open at.
|
||||
def build_editor_invocation_string(file, line, blocking)
|
||||
if pry_instance.config.editor.respond_to?(:call)
|
||||
args = [file, line, blocking][0...(pry_instance.config.editor.arity)]
|
||||
pry_instance.config.editor.call(*args)
|
||||
else
|
||||
sanitized_file = Helpers::Platform.windows? ? file : Shellwords.escape(file)
|
||||
editor = pry_instance.config.editor
|
||||
flag = blocking_flag_for_editor(blocking)
|
||||
start_line = start_line_syntax_for_editor(sanitized_file, line)
|
||||
"#{editor} #{flag} #{start_line}"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Start the editor running, using the calculated invocation string
|
||||
def open_editor(editor_invocation)
|
||||
# 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(*Shellwords.split(editor_invocation)) ||
|
||||
raise(
|
||||
CommandError,
|
||||
"`#{editor_invocation}` gave exit status: #{$CHILD_STATUS.exitstatus}"
|
||||
)
|
||||
end
|
||||
|
||||
# We need JRuby specific code here cos just shelling out using
|
||||
# system() appears to be pretty broken :/
|
||||
def open_editor_on_jruby(editor_invocation)
|
||||
require 'spoon'
|
||||
pid = Spoon.spawnp(*Shellwords.split(editor_invocation))
|
||||
Process.waitpid(pid)
|
||||
rescue FFI::NotFoundError
|
||||
system(editor_invocation)
|
||||
end
|
||||
|
||||
# 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.
|
||||
def blocking_flag_for_editor(blocking)
|
||||
case editor_name
|
||||
when /^emacsclient/
|
||||
'--no-wait' unless blocking
|
||||
when /^[gm]vim/
|
||||
'--nofork' if blocking
|
||||
when /^jedit/
|
||||
'-wait' if blocking
|
||||
when /^mate/, /^subl/, /^redcar/, /^code/
|
||||
'-w' if blocking
|
||||
end
|
||||
end
|
||||
|
||||
# Return the syntax for a given editor for starting the editor
|
||||
# and moving to a particular line within that file
|
||||
def start_line_syntax_for_editor(file_name, line_number)
|
||||
# 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 /^code/
|
||||
"-g #{file_name}:#{line_number}"
|
||||
when /^subl/
|
||||
"#{file_name}:#{line_number}"
|
||||
when /^uedit32/
|
||||
"#{file_name}/#{line_number}"
|
||||
when /^jedit/
|
||||
"#{file_name} +line:#{line_number}"
|
||||
when /^redcar/
|
||||
"-l#{line_number} #{file_name}"
|
||||
else
|
||||
if Helpers::Platform.windows?
|
||||
file_name.to_s
|
||||
else
|
||||
"+#{line_number} #{file_name}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# 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.
|
||||
#
|
||||
# @example
|
||||
# Pry.config.editor="/home/conrad/bin/textmate -w"
|
||||
# editor_name
|
||||
# # => textmate
|
||||
#
|
||||
def editor_name
|
||||
File.basename(pry_instance.config.editor).split(" ").first
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
# Env is a helper module to work with environment variables.
|
||||
#
|
||||
# @since v0.13.0
|
||||
# @api private
|
||||
module Env
|
||||
def self.[](key)
|
||||
return unless ENV.key?(key)
|
||||
|
||||
value = ENV[key]
|
||||
return if value == ''
|
||||
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,48 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
# @api private
|
||||
# @since v0.13.0
|
||||
module ExceptionHandler
|
||||
class << self
|
||||
# Will only show the first line of the backtrace.
|
||||
def handle_exception(output, exception, _pry_instance)
|
||||
if exception.is_a?(UserError) && exception.is_a?(SyntaxError)
|
||||
output.puts "SyntaxError: #{exception.message.sub(/.*syntax error, */m, '')}"
|
||||
else
|
||||
output.puts standard_error_text_for(exception)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def standard_error_text_for(exception)
|
||||
text = exception_text(exception)
|
||||
return text unless exception.respond_to?(:cause)
|
||||
|
||||
cause = exception.cause
|
||||
while cause
|
||||
text += cause_text(cause)
|
||||
cause = cause.cause
|
||||
end
|
||||
|
||||
text
|
||||
end
|
||||
|
||||
def exception_text(exception)
|
||||
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.2')
|
||||
"#{exception.class}: #{exception.message}\n" \
|
||||
"from #{exception.backtrace.first}\n"
|
||||
else
|
||||
"#{exception.class}: #{exception.detailed_message}\n" \
|
||||
"from #{exception.backtrace.first}\n"
|
||||
end
|
||||
end
|
||||
|
||||
def cause_text(cause)
|
||||
"Caused by #{cause.class}: #{cause}\n" \
|
||||
"from #{cause.backtrace.first}\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,73 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
# As a REPL, we often want to catch any unexpected exceptions that may have
|
||||
# been raised; however we don't want to go overboard and prevent the user
|
||||
# from exiting Pry when they want to.
|
||||
module RescuableException
|
||||
def self.===(exception)
|
||||
case exception
|
||||
# Catch when the user hits ^C (Interrupt < SignalException), and assume
|
||||
# that they just wanted to stop the in-progress command (just like bash
|
||||
# etc.)
|
||||
when Interrupt
|
||||
true
|
||||
# Don't catch signals (particularly not SIGTERM) as these are unlikely
|
||||
# to be intended for pry itself. We should also make sure that
|
||||
# Kernel#exit works.
|
||||
when *Pry.config.unrescued_exceptions
|
||||
false
|
||||
# All other exceptions will be caught.
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Catches SecurityErrors if $SAFE is set
|
||||
module TooSafeException
|
||||
def self.===(exception)
|
||||
if Pry::HAS_SAFE_LEVEL
|
||||
$SAFE > 0 && exception.is_a?(SecurityError)
|
||||
else
|
||||
exception.is_a?(SecurityError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# An Exception Tag (cf. Exceptional Ruby) that instructs Pry to show the error
|
||||
# in a more user-friendly manner. This should be used when the exception
|
||||
# happens within Pry itself as a direct consequence of the user typing
|
||||
# something wrong.
|
||||
#
|
||||
# This allows us to distinguish between the user typing:
|
||||
#
|
||||
# pry(main)> def )
|
||||
# SyntaxError: unexpected )
|
||||
#
|
||||
# pry(main)> method_that_evals("def )")
|
||||
# SyntaxError: (eval):1: syntax error, unexpected ')'
|
||||
# from ./a.rb:2 in `eval'
|
||||
module UserError; end
|
||||
|
||||
# When we try to get a binding for an object, we try to define a method on
|
||||
# that Object's singleton class. This doesn't work for "frozen" Object's, and
|
||||
# the exception is just a vanilla RuntimeError.
|
||||
module FrozenObjectException
|
||||
def self.===(exception)
|
||||
[
|
||||
"can't modify frozen class/module",
|
||||
"can't modify frozen Class",
|
||||
"can't modify frozen object"
|
||||
].include?(exception.message)
|
||||
end
|
||||
end
|
||||
|
||||
# CommandErrors are caught by the REPL loop and displayed to the user. They
|
||||
# indicate an exceptional condition that's fatal to the current command.
|
||||
class CommandError < StandardError; end
|
||||
class MethodNotFound < CommandError; end
|
||||
|
||||
# indicates obsolete API
|
||||
class ObsoleteError < StandardError; end
|
||||
end
|
||||
@@ -0,0 +1,27 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
module Forwardable
|
||||
require 'forwardable'
|
||||
include ::Forwardable
|
||||
|
||||
#
|
||||
# Since Ruby 2.4, Forwardable will print a warning when
|
||||
# calling a method that is private on a delegate, and
|
||||
# in the future it could be an error: https://bugs.ruby-lang.org/issues/12782#note-3
|
||||
#
|
||||
# That's why we revert to a custom implementation for delegating one
|
||||
# private method to another.
|
||||
#
|
||||
def def_private_delegators(target, *private_delegates)
|
||||
private_delegates.each do |private_delegate|
|
||||
define_method(private_delegate) do |*a, &b|
|
||||
instance_variable_get(target).__send__(private_delegate, *a, &b)
|
||||
end
|
||||
end
|
||||
class_eval do
|
||||
private(*private_delegates) # rubocop:disable Style/AccessModifierDeclarations
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "pry/helpers/base_helpers"
|
||||
require "pry/helpers/options_helpers"
|
||||
require "pry/helpers/command_helpers"
|
||||
require "pry/helpers/text"
|
||||
require "pry/helpers/table"
|
||||
require "pry/helpers/platform"
|
||||
@@ -0,0 +1,71 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
module Helpers
|
||||
module BaseHelpers
|
||||
extend self
|
||||
|
||||
def silence_warnings
|
||||
old_verbose = $VERBOSE
|
||||
$VERBOSE = nil
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
$VERBOSE = old_verbose
|
||||
end
|
||||
end
|
||||
|
||||
# Acts like send but ignores any methods defined below Object or Class in the
|
||||
# inheritance hierarchy.
|
||||
# This is required to introspect methods on objects like Net::HTTP::Get that
|
||||
# have overridden the `method` method.
|
||||
def safe_send(obj, method, *args, &block)
|
||||
(obj.is_a?(Module) ? Module : Object).instance_method(method)
|
||||
.bind(obj).call(*args, &block)
|
||||
end
|
||||
|
||||
def find_command(name, set = Pry::Commands)
|
||||
command_match = set.find do |_, command|
|
||||
(listing = command.options[:listing]) == name && !listing.nil?
|
||||
end
|
||||
command_match.last if command_match
|
||||
end
|
||||
|
||||
def not_a_real_file?(file)
|
||||
file =~ /^(\(.*\))$|^<.*>$/ || file =~ /__unknown__/ || file == "" || file == "-e"
|
||||
end
|
||||
|
||||
def use_ansi_codes?
|
||||
Pry::Helpers::Platform.windows_ansi? ||
|
||||
((term = Pry::Env['TERM']) && term != "dumb")
|
||||
end
|
||||
|
||||
def colorize_code(code)
|
||||
SyntaxHighlighter.highlight(code)
|
||||
end
|
||||
|
||||
def highlight(string, regexp, highlight_color = :bright_yellow)
|
||||
string.gsub(regexp) do |match|
|
||||
"<#{highlight_color}>#{match}</#{highlight_color}>"
|
||||
end
|
||||
end
|
||||
|
||||
# formatting
|
||||
def heading(text)
|
||||
text = "#{text}\n--"
|
||||
"\e[1m#{text}\e[0m"
|
||||
end
|
||||
|
||||
# Send the given text through the best available pager (if Pry.config.pager is
|
||||
# enabled). Infers where to send the output if used as a mixin.
|
||||
# DEPRECATED.
|
||||
def stagger_output(text, _out = nil)
|
||||
if defined?(pry_instance) && pry_instance
|
||||
pry_instance.pager.page text
|
||||
else
|
||||
Pry.new.pager.page text
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,146 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'tempfile'
|
||||
|
||||
class Pry
|
||||
module Helpers
|
||||
module CommandHelpers
|
||||
include OptionsHelpers
|
||||
|
||||
extend self
|
||||
|
||||
# Open a temp file and yield it to the block, closing it after
|
||||
# @return [String] The path of the temp file
|
||||
def temp_file(ext = '.rb')
|
||||
file = Tempfile.new(['pry', ext])
|
||||
yield(file)
|
||||
ensure
|
||||
file.close(true)
|
||||
end
|
||||
|
||||
def internal_binding?(context)
|
||||
method_name = context.eval("::Kernel.__method__").to_s
|
||||
# class_eval is here because of http://jira.codehaus.org/browse/JRUBY-6753
|
||||
%w[__binding__ __pry__ class_eval].include?(method_name)
|
||||
# TODO: codehaus is dead, there was no test for this and the
|
||||
# description for the commit doesn't exist. Probably a candidate for
|
||||
# removal so we have a chance to introduce a regression and document it
|
||||
# properly.
|
||||
end
|
||||
|
||||
def get_method_or_raise(method_name, context, opts = {})
|
||||
method = Pry::Method.from_str(method_name, context, opts)
|
||||
if !method && method_name
|
||||
raise Pry::MethodNotFound, "method '#{method_name}' could not be found."
|
||||
end
|
||||
|
||||
(opts[:super] || 0).times do
|
||||
if method.super
|
||||
method = method.super
|
||||
else
|
||||
raise Pry::MethodNotFound,
|
||||
"'#{method.name_with_owner}' has no super method"
|
||||
end
|
||||
end
|
||||
|
||||
if !method || (!method_name && internal_binding?(context))
|
||||
raise Pry::MethodNotFound,
|
||||
'no method name given, and context is not a method'
|
||||
end
|
||||
|
||||
set_file_and_dir_locals(method.source_file)
|
||||
method
|
||||
end
|
||||
|
||||
# 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.
|
||||
#
|
||||
# @example
|
||||
# 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
|
||||
#
|
||||
# @param [String] dirty_text The text from which to remove indentation
|
||||
# @return [String] the text with indentation stripped
|
||||
def unindent(dirty_text, left_padding = 0)
|
||||
text = dirty_text.sub(/\A[ \t]+\z/, '') # Empty blank lines.
|
||||
|
||||
# Find the longest common whitespace to all indented lines. Ignore lines
|
||||
# containing just -- or ++ as these seem to be used by comment authors
|
||||
# as delimiters.
|
||||
scanned_text = text.scan(/^[ \t]*(?!--\n|\+\+\n)(?=[^ \t\n])/)
|
||||
margin = scanned_text.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}/, ' ' * left_padding)
|
||||
end
|
||||
|
||||
# Restrict a string to the given range of lines (1-indexed)
|
||||
# @param [String] content The string.
|
||||
# @param [Range, Integer] lines The line(s) to restrict it to.
|
||||
# @return [String] The resulting string.
|
||||
def restrict_to_lines(content, lines)
|
||||
line_range = one_index_range_or_number(lines)
|
||||
Array(content.lines.to_a[line_range]).join
|
||||
end
|
||||
|
||||
def one_index_number(line_number)
|
||||
line_number > 0 ? line_number - 1 : line_number
|
||||
end
|
||||
|
||||
# convert a 1-index range to a 0-indexed one
|
||||
def one_index_range(range)
|
||||
Range.new(one_index_number(range.begin), one_index_number(range.end))
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
def absolute_index_number(line_number, array_length)
|
||||
if line_number >= 0
|
||||
line_number
|
||||
else
|
||||
[array_length + line_number, 0].max
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
def set_file_and_dir_locals(file_name, pry = pry_instance, ctx = target)
|
||||
return if !ctx || !file_name
|
||||
|
||||
pry.last_file = File.expand_path(file_name)
|
||||
pry.inject_local("_file_", pry.last_file, ctx)
|
||||
|
||||
pry.last_dir = File.dirname(pry.last_file)
|
||||
pry.inject_local("_dir_", pry.last_dir, ctx)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,84 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
module Helpers
|
||||
# This class contains methods useful for extracting
|
||||
# documentation from methods and classes.
|
||||
module DocumentationHelpers
|
||||
YARD_TAGS = %w[
|
||||
param return option yield attr attr_reader attr_writer deprecate example
|
||||
raise
|
||||
].freeze
|
||||
|
||||
module_function
|
||||
|
||||
def process_rdoc(comment)
|
||||
comment = comment.dup
|
||||
last_match_ruby = proc do
|
||||
SyntaxHighlighter.highlight(Regexp.last_match(1))
|
||||
end
|
||||
|
||||
comment.gsub(%r{<code>(?:\s*\n)?(.*?)\s*</code>}m, &last_match_ruby)
|
||||
.gsub(%r{<em>(?:\s*\n)?(.*?)\s*</em>}m) { "\e[1m#{Regexp.last_match(1)}\e[0m" }
|
||||
.gsub(%r{<i>(?:\s*\n)?(.*?)\s*</i>}m) { "\e[1m#{Regexp.last_match(1)}\e[0m" }
|
||||
.gsub(%r{<tt>(?:\s*\n)?(.*?)\s*</tt>}m, &last_match_ruby)
|
||||
.gsub(/\B\+(\w+?)\+\B/) { "\e[32m#{Regexp.last_match(1)}\e[0m" }
|
||||
.gsub(/((?:^[ \t]+(?:(?!.+\e\[)).+(?:\n+|\Z))+)/, &last_match_ruby)
|
||||
.gsub(/`(?:\s*\n)?([^\e]*?)\s*`/) { "`#{last_match_ruby.call}`" }
|
||||
end
|
||||
|
||||
def process_yardoc_tag(comment, tag)
|
||||
in_tag_block = nil
|
||||
comment.lines.map do |v|
|
||||
if in_tag_block && v !~ /^\S/
|
||||
Pry::Helpers::Text.strip_color Pry::Helpers::Text.strip_color(v)
|
||||
elsif in_tag_block
|
||||
in_tag_block = false
|
||||
v
|
||||
else
|
||||
in_tag_block = true if v =~ /^@#{tag}/
|
||||
v
|
||||
end
|
||||
end.join
|
||||
end
|
||||
|
||||
def process_yardoc(comment)
|
||||
(YARD_TAGS - %w[example])
|
||||
.inject(comment) { |a, v| process_yardoc_tag(a, v) }
|
||||
.gsub(/^@(#{YARD_TAGS.join("|")})/) { "\e[33m#{Regexp.last_match(1)}\e[0m" }
|
||||
end
|
||||
|
||||
def process_comment_markup(comment)
|
||||
process_yardoc process_rdoc(comment)
|
||||
end
|
||||
|
||||
# @param [String] code
|
||||
# @return [String]
|
||||
def strip_comments_from_c_code(code)
|
||||
code.sub(%r{\A\s*/\*.*?\*/\s*}m, '')
|
||||
end
|
||||
|
||||
# Given a string that makes up a comment in a source-code file parse out the content
|
||||
# that the user is intended to read. (i.e. without leading indentation, #-characters
|
||||
# or shebangs)
|
||||
#
|
||||
# @param [String] comment
|
||||
# @return [String]
|
||||
def get_comment_content(comment)
|
||||
comment = comment.dup
|
||||
# Remove #!/usr/bin/ruby
|
||||
comment.gsub!(/\A\#!.*$/, '')
|
||||
# Remove leading empty comment lines
|
||||
comment.gsub!(/\A\#+?$/, '')
|
||||
comment.gsub!(/^\s*#/, '')
|
||||
strip_leading_whitespace(comment)
|
||||
end
|
||||
|
||||
# @param [String] text
|
||||
# @return [String]
|
||||
def strip_leading_whitespace(text)
|
||||
Pry::Helpers::CommandHelpers.unindent(text)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,34 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pry
|
||||
module Helpers
|
||||
module OptionsHelpers
|
||||
module_function
|
||||
|
||||
# Add method options to the Pry::Slop instance
|
||||
def method_options(opt)
|
||||
@method_target = target
|
||||
opt.on :M, "instance-methods", "Operate on instance methods."
|
||||
opt.on :m, :methods, "Operate on methods."
|
||||
opt.on :s, :super, "Select the 'super' method. Can be repeated to " \
|
||||
"traverse the ancestors.", as: :count
|
||||
opt.on(
|
||||
:c, :context, "Select object context to run under.", argument: true
|
||||
) do |context|
|
||||
@method_target = Pry.binding_for(target.eval(context))
|
||||
end
|
||||
end
|
||||
|
||||
# Get the method object parsed by the slop instance
|
||||
def method_object
|
||||
@method_object ||= get_method_or_raise(
|
||||
args.empty? ? nil : args.join(" "),
|
||||
@method_target,
|
||||
super: opts[:super],
|
||||
instance: opts.present?(:'instance-methods') && !opts.present?(:methods),
|
||||
methods: opts.present?(:methods) && !opts.present?(:'instance-methods')
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user