问题
I'm trying to use the Thor::Actions template method to generate some C++ test file templates, but erb keeps telling me that I have undefined variables and methods.
Here's the calling code:
def test (name, dir)
template "tasks/templates/new_test_file", "src/#{dir}/test/#{name}Test.cpp"
insert_into_file "src/#{dir}/test/CMakeLists.txt",
"#{dir}/test/#{name}Test ", :after => "set(Local "
end
Here's the template:
<% test_name = name + "Test" %>
#include <gtest/gtest.h>
#include "<%= dir %>/<%= name %>.h"
class <%= test_name %> : public testing::Test {
protected:
<%= test_name %> () {}
~<%= test_name %> () {}
virtual void SetUp () {}
virtual void TearDown () {}
};
// Don't forget to write your tests before you write your implementation!
TEST_F (<%= test_name %>, Sample) {
ASSERT_EQ(1 + 1, 3);
}
What do I have to do to get name and dir into scope here? I have more complex templates that I need this functionality for too.
回答1:
ERB uses ruby's binding object to retrieve the variables that you want. Every object in ruby has a binding, but access to the binding is limited to the object itself, by default. you can work around this, and pass the binding that you wish into your ERB template, by creating a module that exposes an object's binding, like this:
module GetBinding
def get_binding
binding
end
end
Then you need to extend any object that has the vars you want with this module.
something.extend GetBinding
and pass the binding of that object into erb
something.extend GetBinding
some_binding = something.get_binding
erb = ERB.new template
output = erb.result(some_binding)
for a complete example of working with ERB, see this wiki page for one of my projects: https://github.com/derickbailey/Albacore/wiki/Custom-Tasks
回答2:
I realize you already solved this, but I'm posting this answer in case someone else turns up looking for the solution to the question you asked (as I was).
Inside the class that #test belongs to, make an attr_accessor, then set its value in the same method that calls the template.
class MyGenerator < Thor
attr_accessor :name, :dir
def test (name, dir)
self.name = name
self.dir = dir
template "tasks/templates/new_test_file", "src/#{dir}/test/#{name}Test.cpp"
end
end
Note: that if you chain methods using #invoke, then a new instance of the class will be used for each invocation. Therefore you have to set the instance variable in the method with the template call. For example, the following wont work.
class MyGenerator < Thor
attr_accessor :name
def one (name)
self.name = name
invoke :two
end
def two (name)
# by the time we get here, this is another instance of MyGenerator, so @name is empty
template "tasks/templates/new_test_file", "src/#{name}Test.cpp"
end
end
You should put self.name = name
inside #two instead
For making generators, if you inherit from Thor::Group instead, all the methods are called in order, and the attr_accessor will be set up for you with the instance variables set for each method. In my case, I had to use Invocations instead of Thor::Group because I couldn't get Thor::Group classes to be recognized as subcommands of an executable.
来源:https://stackoverflow.com/questions/6629967/how-do-you-make-ruby-variables-and-methods-in-scope-using-thor-templates