How do you make ruby variables and methods in scop

2019-08-02 06:16发布

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.

2条回答
地球回转人心会变
2楼-- · 2019-08-02 06:58

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

查看更多
我命由我不由天
3楼-- · 2019-08-02 07:09

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.

查看更多
登录 后发表回答