diff --git a/lib/web_console.rb b/lib/web_console.rb index c1c549df..832bfb74 100644 --- a/lib/web_console.rb +++ b/lib/web_console.rb @@ -15,6 +15,7 @@ module WebConsole autoload :Whitelist autoload :Template autoload :Middleware + autoload :Context autoload_at 'web_console/errors' do autoload :Error diff --git a/lib/web_console/context.rb b/lib/web_console/context.rb new file mode 100644 index 00000000..b2249649 --- /dev/null +++ b/lib/web_console/context.rb @@ -0,0 +1,46 @@ +module WebConsole + # A context lets you get object names related to the current session binding. + class Context + def initialize(binding) + @binding = binding + end + + # Extracts entire objects which can be called by the current session unless the objpath is present. + # Otherwise, it extracts methods and constants of the object specified by the objpath. + def extract(objpath) + if objpath.present? + local(objpath) + else + global + end + end + + private + + GLOBAL_OBJECTS = [ + 'global_variables', + 'local_variables', + 'instance_variables', + 'instance_methods', + 'class_variables', + 'methods', + 'Object.constants', + 'Kernel.methods', + ] + + def global + GLOBAL_OBJECTS.map { |cmd| eval(cmd) }.flatten + end + + def local(objpath) + [ + eval("#{objpath}.methods").map { |m| "#{objpath}.#{m}" }, + eval("#{objpath}.constants").map { |c| "#{objpath}::#{c}" }, + ].flatten + end + + def eval(cmd) + @binding.eval(cmd) rescue [] + end + end +end diff --git a/lib/web_console/middleware.rb b/lib/web_console/middleware.rb index d0c36006..632eec71 100644 --- a/lib/web_console/middleware.rb +++ b/lib/web_console/middleware.rb @@ -103,7 +103,7 @@ def id_for_repl_session_stack_frame_change(request) def update_repl_session(id, request) json_response_with_session(id, request) do |session| - { output: session.eval(request.params[:input]) } + { output: session.eval(request.params[:input]), context: session.context(request.params[:context]) } end end diff --git a/lib/web_console/session.rb b/lib/web_console/session.rb index bf88e8cc..f6accd80 100644 --- a/lib/web_console/session.rb +++ b/lib/web_console/session.rb @@ -43,7 +43,7 @@ def from(storage) def initialize(bindings) @id = SecureRandom.hex(16) @bindings = bindings - @evaluator = Evaluator.new(bindings.first) + @evaluator = Evaluator.new(@current_binding = bindings.first) store_into_memory end @@ -59,7 +59,12 @@ def eval(input) # # Returns nothing. def switch_binding_to(index) - @evaluator = Evaluator.new(@bindings[index.to_i]) + @evaluator = Evaluator.new(@current_binding = @bindings[index.to_i]) + end + + # Returns context of the current binding + def context(objpath) + Context.new(@current_binding).extract(objpath) end private diff --git a/test/web_console/context_test.rb b/test/web_console/context_test.rb new file mode 100644 index 00000000..555366ed --- /dev/null +++ b/test/web_console/context_test.rb @@ -0,0 +1,28 @@ +require 'test_helper' + +module WebConsole + class ContextTest < ActiveSupport::TestCase + test '#extract(empty) includes local variables' do + local_var = 'local' + assert Context.new(binding).extract('').include?(:local_var) + end + + test '#extract(empty) includes instance variables' do + @instance_var = 'instance' + assert Context.new(binding).extract('').include?(:@instance_var) + end + + test '#extract(empty) includes global variables' do + $global_var = 'global' + assert Context.new(binding).extract('').include?(:$global_var) + end + + test '#extract(obj) returns methods' do + assert Context.new(binding).extract('Rails').include?('Rails.root') + end + + test '#extract(obj) returns constants' do + assert Context.new(binding).extract('WebConsole').include?('WebConsole::Middleware') + end + end +end