1

为了用户方便和更简洁的代码,我想编写一个可以像这样使用的类:

Encoder::Theora.encode do
  infile = "path/to/infile"
  outfile = "path/to/outfile"
  passes = 2
  # ... more params
end

现在的挑战是,让这些参数在我的编码方法中可用。

module Encoder
  class Theora
    def self.encode(&proc)
      proc.call
      # do some fancy encoding stuff here
      # using the parameters from the proc
    end
  end
end

这种方法行不通。调用 Proc 时,不会在 Theora 类的上下文中评估变量。通常我想使用 method_missing 将每个参数放入 Theora 类的类变量中,但我没有找到正确的输入方式。

谁能指出我正确的方向?

4

4 回答 4

3

我不确定是否可以让 DSL 使用赋值,我认为 Ruby 解释器将始终假定infileininfile = 'path/to/something'是该上下文中的局部变量(但self.infile = 'path/to/something'可以使其工作)。但是,如果您可以在没有特定细节的情况下生活,您可以像这样实现您的 DSL:

module Encoder
  class Theora
    def self.encode(&block)
      instance = new
      instance.instance_eval(&block)
      instance
    end

    def infile(path=nil)
      @infile = path if path
      @infile
    end
  end
end

并像这样使用它:

Encoder::Theora.encode do
  infile 'path/somewhere'
end

(类似地实现其他属性)。

于 2011-01-21T08:28:44.990 回答
1

它不能按照你写的方式完成,AFAIK。proc 的主体有自己的范围,在该范围内创建的变量在其外部是不可见的。

惯用的方法是创建一个配置对象并将其传递到块中,该块描述使用该对象的方法或属性要完成的工作。然后在执行工作时读取这些设置。例如,这是create_table在 ActiveRecord 迁移中采用的方法。

所以你可以做这样的事情:

module Encoder
  class Theora
    Config = Struct.new(:infile, :outfile, :passes)

    def self.encode(&proc)
      config = Config.new
      proc.call(config)
      # use the config settings here
      fp = File.open(config.infile)       # for example
      # ...
    end
  end
end

# then use the method like this:
Encoder::Theora.encode do |config|
  config.infile = "path/to/infile"
  config.outfile = "path/to/outfile"
  config.passes = 2
  # ...
end
于 2011-01-21T08:25:56.730 回答
0

在解决这个问题时,我得到了以下内容,我不一定推荐它,并且不太符合所需的语法,但它确实允许您使用赋值(有点)。因此,本着完整的精神仔细阅读:

module Encoder
  class Theora
    def self.encode(&proc)
      infile = nil
      outfile = nil
      yield binding
    end
  end
end

Encoder::Theora.encode do |b|
  b.eval <<-ruby
    infile = "path/to/infile"
    outfile = "path/to/outfile"
  ruby
end

我相信 Binding.eval 只适用于 Ruby 1.9。此外,似乎局部变量需要在屈服之前声明,否则它将不起作用——有人知道为什么吗?

于 2011-01-21T09:02:20.150 回答
0

好的,首先我必须说 pmdboi 的答案非常优雅,几乎可以肯定是正确的。

不过,以防万一你想要一个超级精简的 DSL,比如

Encoder::Theora.encode do
  infile "path/to/infile"
  outfile "path/to/outfile"
  passes 2
end

你可以做一些像这样丑陋的事情:

require 'blockenspiel'
module Encoder
  class Theora
    # this replaces pmdboi's elegant Struct
    class Config
      include Blockenspiel::DSL
      def method_missing(method_id, *args, &blk)
        if args.length == 1
          instance_variable_set :"@#{method_id}", args[0]
        else
          instance_variable_get :"@#{method_id}"
        end
      end
    end

    def self.encode(&blk)
      config = Config.new
      Blockenspiel.invoke blk, config
      # now you can do things like
      puts config.infile
      puts config.outfile
      puts config.passes
    end
  end
end
于 2011-01-27T15:49:55.917 回答