Подтвердить что ты не робот

Как запустить IRB.start в контексте текущего класса

Я прохожу через PragProg Continuous Testing With Ruby, где они говорят о вызове IRB в контексте текущего класса для проверки кода вручную.

Однако они цитируют, что если вы вызываете IRB.start в классе, self предопределен и ссылается на объект, в котором мы были, когда start был вызван, что в моем случае неверно.

Даже для очень простого примера, например

a = "hello"
require 'irb'
ARGV.clear # otherwise all script parameters get passed to IRB
IRB.start

Когда я пытаюсь получить доступ к переменной a, я получаю очевидную

NameError: undefined local variable or method `a' for main:Object

Он работает только тогда, когда я изменяю a на глобальную переменную

$a = "hello"
require 'irb'
ARGV.clear # otherwise all script parameters get passed to IRB
IRB.start

тогда я могу получить к нему доступ

irb(main):001:0> $a
=> 1

Есть ли какой-либо путь для доступа к локальным и переменным экземпляра в текущем классе?

4b9b3361

Ответ 1

Я предлагаю попробовать это в ripl, альтернативе irb. Вышеприведенный пример работает:

a = 'hello'
require 'ripl'
Ripl.start :binding => binding

Обратите внимание, что локальные переменные работают потому, что вы передаете текущую привязку с опцией: binding.

Возможно, вы можете сделать то же самое в irb, но поскольку он плохо документирован и не проверен, ваши шансы сделать это чисто невелики.

Ответ 2

Как вы уже обнаружили, self не ссылается на объект, где был запущен IRB, а на TOPLEVEL_BINDING, который, кажется, является экземпляром самого класса Object.

Вы все еще можете запускать сеанс IRB с определенным классом или объектом в качестве контекста, но это не так просто, как только запуск IRB.

Если вам интересно, запускается IRB с конкретным контекстом, тогда это действительно легко сделать, когда вы запускаете IRB вручную. Просто запустите IRB, а затем вызовите метод irb, передав ему объект/класс, который вы хотите в качестве контекста.

$ irb
irb(main):002:0> require 'myclass'
=> true
irb(main):003:0> irb MyClass
irb#1(MyClass):001:0> self
=> MyClass

Вы также можете запустить сеанс IRB программно и указать контекст, но это не так просто, как должно быть, потому что вам нужно воспроизвести некоторый код запуска IRB. После много экспериментов и копания в исходном коде IRB я смог придумать что-то, что работает:

require 'irb'
IRB.setup nil
IRB.conf[:MAIN_CONTEXT] = IRB::Irb.new.context
require 'irb/ext/multi-irb'
IRB.irb nil, self

Ответ 3

Вместо глобального можно использовать переменные экземпляра, например:

require 'irb'
@a = "hello"
ARGV.clear
IRB.start

>> @a
=> "hello"

Ответ 4

Используйте Pry:

a = 'hello'
require 'pry'
binding.pry

Ответ 5

Здесь, как вызвать IRB из вашего script в контексте того, где вы вызываете IRB.start..

require 'irb'
class C
    def my_method
        @var = 'hi'
        $my_binding = binding
        IRB.start(__FILE__)
    end
end

C.new.my_method

Выполнение вашего script вызовет IRB. Когда вы дойдете до подсказки, вам нужно еще кое-что сделать...

% ./my_script.rb
irb(main):001:0> @var.nil?
=> true
irb(main):002:0> cb $my_binding
=> #<C:0x000000009da300 @var="hi">
irb(#<C:0x000000009da300>):003:0> @var.nil?
=> false
irb(#<C:0x000000009da300>):004:0> @var
=> "hi"

Наслаждайтесь!

Ответ 6

Мое решение для Ruby 2.2.3. Он очень похож на Bryant's

def to_s
  "Sample"
end

def interactive
  banana = "Hello"
  @banana = "There"
  require 'irb'
  IRB.setup(nil)
  workspace = IRB::WorkSpace.new(binding)
  irb = IRB::Irb.new(workspace)
  IRB.conf[:MAIN_CONTEXT] = irb.context
  irb.eval_input
end

irb(Sample):001:0> puts banana
Hello
=> nil
irb(Sample):002:0> puts @banana
There
=> nil
irb(Sample):003:0>

Я могу получить доступ к локальным переменным и переменным экземпляра. Конечно, require 'irb' может находиться в верхней части файла. Настройка banana и @banana заключается в том, чтобы доказать, что я могу получить к ним доступ из запроса irb. To_s - это один из способов получения довольно быстрой подсказки, но есть и другие варианты. И нет никакой реальной причины сделать отдельный метод, но в его нынешнем виде вы можете использовать это в любом месте, и он должен работать.

Ответ 7

Как и Ruby 2.4.0, вы можете сделать это:

require 'irb'
binding.irb

Это запустит IBR REPL, где у вас будет правильное значение для self, и вы сможете получить доступ ко всем локальным переменным и переменным экземпляра, находящимся в области видимости. Введите Ctrl + D или quit, чтобы возобновить свою программу Ruby.

Ответ 8

ruby-debug-base gem добавляет метод binding_n к модулю ядра, и это даст вам объект привязки доступа, который можно использовать в eval для доступа к переменным столбца вызова. Не забудьте запустить Debugger.start, чтобы включить отслеживание стека вызовов.

Вот пример, показывающий его использование, чтобы понять, что происходит внутри irb (программа Ruby). Можно также установить требование и Debugger.start внутри вашего собственного кода Ruby.

$ irb
ruby-1.8.7-p302 > require 'rubygems'
 => true 
ruby-1.8.7-p302 > require 'ruby-debug-base'
 => true 
ruby-1.8.7-p302 > Debugger.start
 => true 
ruby-1.8.7-p302 > puts caller
/tmp/.rvm/rubies/ruby-1.8.7-p302/lib/ruby/1.8/irb/workspace.rb:52  :i  n `irb_binding' #`
/tmp/.rvm/rubies/ruby-1.8.7-p302/lib/ruby/1.8/irb/workspace.rb:52
=> nil 
ruby-1.8.7-p302 > eval "main", binding_n(2)
 => #<Object:0xb7762958 @prompt={:PROMPT_I=>"ruby-1.8.7-p302 > ", :PROMPT_N=>"  ruby-1.8.7-p302 ?> ", :PROMPT_S=>"ruby-1.8.7-p302%l> ", :PROMPT_C=>"ruby-1.8.7-p302 > ", :AUTO_INDENT=>true, :RETURN=>" => %s \n"}> 
ruby-1.8.7-p302 > 

Если вы готовы запустить исправленную версию Ruby для 1.9.2, см. http://gitnub.com/rocky/rb-threadframe для лучшего, по моему мнению, доступа к стек вызовов. Rubinius предоставляет эту возможность, встроенную через Rubinius:: VM.backtrace.