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);
|
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;
|
static int namespace_experimental_warned = 0;
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -1061,6 +1078,7 @@ Init_Namespace(void)
|
||||||
rb_define_method(rb_cNamespace, "load", rb_namespace_load, -1);
|
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", rb_namespace_require, 1);
|
||||||
rb_define_method(rb_cNamespace, "require_relative", rb_namespace_require_relative, 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);
|
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, 'blank1.rb'))
|
||||||
assert !$LOADED_FEATURES.include?(File.join(namespace_dir, 'blank2.rb'))
|
assert !$LOADED_FEATURES.include?(File.join(namespace_dir, 'blank2.rb'))
|
||||||
end
|
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
|
end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue