mirror of
https://github.com/ruby/ruby.git
synced 2025-08-15 13:39:04 +02:00
Introduce Namespace#eval
This commit adds an `eval` method to `Namespace` that takes a string and evaluates the string as Ruby code within the context of that namespace. For example: ```ruby n = Namespace.new n.eval("class TestClass; def hello; 'from namespace'; end; end") instance = n::TestClass.new instance.hello # => "from namespace" ``` [Feature #21365]
This commit is contained in:
parent
242343ff80
commit
3d5619c8b1
2 changed files with 100 additions and 0 deletions
18
namespace.c
18
namespace.c
|
@ -859,6 +859,23 @@ rb_namespace_require_relative(VALUE namespace, VALUE fname)
|
|||
return rb_ensure(rb_require_relative_entrypoint, fname, namespace_both_pop, (VALUE)&arg);
|
||||
}
|
||||
|
||||
static VALUE
|
||||
rb_namespace_eval_string(VALUE str)
|
||||
{
|
||||
return rb_eval_string(RSTRING_PTR(str));
|
||||
}
|
||||
|
||||
static VALUE
|
||||
rb_namespace_eval(VALUE namespace, VALUE str)
|
||||
{
|
||||
rb_thread_t *th = GET_THREAD();
|
||||
|
||||
StringValue(str);
|
||||
|
||||
namespace_push(th, namespace);
|
||||
return rb_ensure(rb_namespace_eval_string, str, namespace_pop, (VALUE)th);
|
||||
}
|
||||
|
||||
static int namespace_experimental_warned = 0;
|
||||
|
||||
void
|
||||
|
@ -1061,6 +1078,7 @@ Init_Namespace(void)
|
|||
rb_define_method(rb_cNamespace, "load", rb_namespace_load, -1);
|
||||
rb_define_method(rb_cNamespace, "require", rb_namespace_require, 1);
|
||||
rb_define_method(rb_cNamespace, "require_relative", rb_namespace_require_relative, 1);
|
||||
rb_define_method(rb_cNamespace, "eval", rb_namespace_eval, 1);
|
||||
|
||||
rb_define_method(rb_cNamespace, "inspect", rb_namespace_inspect, 0);
|
||||
|
||||
|
|
|
@ -533,4 +533,86 @@ class TestNamespace < Test::Unit::TestCase
|
|||
assert !$LOADED_FEATURES.include?(File.join(namespace_dir, 'blank1.rb'))
|
||||
assert !$LOADED_FEATURES.include?(File.join(namespace_dir, 'blank2.rb'))
|
||||
end
|
||||
|
||||
def test_eval_basic
|
||||
pend unless Namespace.enabled?
|
||||
|
||||
# Test basic evaluation
|
||||
result = @n.eval("1 + 1")
|
||||
assert_equal 2, result
|
||||
|
||||
# Test string evaluation
|
||||
result = @n.eval("'hello ' + 'world'")
|
||||
assert_equal "hello world", result
|
||||
end
|
||||
|
||||
def test_eval_with_constants
|
||||
pend unless Namespace.enabled?
|
||||
|
||||
# Define a constant in the namespace via eval
|
||||
@n.eval("TEST_CONST = 42")
|
||||
assert_equal 42, @n::TEST_CONST
|
||||
|
||||
# Constant should not be visible in main namespace
|
||||
assert_raise(NameError) { TEST_CONST }
|
||||
end
|
||||
|
||||
def test_eval_with_classes
|
||||
pend unless Namespace.enabled?
|
||||
|
||||
# Define a class in the namespace via eval
|
||||
@n.eval("class TestClass; def hello; 'from namespace'; end; end")
|
||||
|
||||
# Class should be accessible in the namespace
|
||||
instance = @n::TestClass.new
|
||||
assert_equal "from namespace", instance.hello
|
||||
|
||||
# Class should not be visible in main namespace
|
||||
assert_raise(NameError) { TestClass }
|
||||
end
|
||||
|
||||
def test_eval_isolation
|
||||
pend unless Namespace.enabled?
|
||||
|
||||
# Create another namespace
|
||||
n2 = Namespace.new
|
||||
|
||||
# Define different constants in each namespace
|
||||
@n.eval("ISOLATION_TEST = 'first'")
|
||||
n2.eval("ISOLATION_TEST = 'second'")
|
||||
|
||||
# Each namespace should have its own constant
|
||||
assert_equal "first", @n::ISOLATION_TEST
|
||||
assert_equal "second", n2::ISOLATION_TEST
|
||||
|
||||
# Constants should not interfere with each other
|
||||
assert_not_equal @n::ISOLATION_TEST, n2::ISOLATION_TEST
|
||||
end
|
||||
|
||||
def test_eval_with_variables
|
||||
pend unless Namespace.enabled?
|
||||
|
||||
# Test local variable access (should work within the eval context)
|
||||
result = @n.eval("x = 10; y = 20; x + y")
|
||||
assert_equal 30, result
|
||||
end
|
||||
|
||||
def test_eval_error_handling
|
||||
pend unless Namespace.enabled?
|
||||
|
||||
# Test syntax error
|
||||
assert_raise(SyntaxError) { @n.eval("1 +") }
|
||||
|
||||
# Test name error
|
||||
assert_raise(NameError) { @n.eval("undefined_variable") }
|
||||
|
||||
# Test that namespace is properly restored after error
|
||||
begin
|
||||
@n.eval("raise RuntimeError, 'test error'")
|
||||
rescue RuntimeError
|
||||
# Should be able to continue using the namespace
|
||||
result = @n.eval("2 + 2")
|
||||
assert_equal 4, result
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue