将 procs 与 Ruby 的 DSL 一起使用

发布于 2024-10-13 07:44:15 字数 549 浏览 9 评论 0原文

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

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 类的类变量中,但我找不到正确的条目方法。

有人能指出我正确的方向吗?

For user convenience and more clean code I would like to write a class that can be used like this:

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

The challenge now is, to have that parameters available in my encode method.

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

This approach does not work. When the Proc is called, the variables are not evaluated in the context of the Theora class. Usually I would like to use method_missing to put every parameter into a class variable of class Theora, but I do not find the right way for an entry.

Can anyone point me into the right direction?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

并安 2024-10-20 07:44:15

我不确定是否可以让 DSL 使用赋值,我认为 Ruby 解释器总是假设 infile = 'path/to/something' 中的 infile 是该上下文中的局部变量(但可以使 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

类似地实现其他属性)。

I'm not sure it's possible to get the DSL to use assignment, I think the Ruby interpreter will always assume that infile in infile = 'path/to/something' is a local variable in that context (but self.infile = 'path/to/something' can be made to work). However, if you can live without that particular detail, you can implement your DSL like this:

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

and use it like this:

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

(implement the other properties similarily).

自由范儿 2024-10-20 07:44:15

AFAIK,它不能按照您编写的方式完成。过程的主体有自己的作用域,在该作用域内创建的变量在其外部不可见。

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

所以你可以这样做:

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

It can't be done the way you've written it, AFAIK. The body of the proc has its own scope, and variables that are created within that scope are not visible outside it.

The idiomatic approach is to create a configuration object and pass it into the block, which describes the work to be done using methods or attributes of that object. Then those settings are read when doing the work. This is the approach taken by create_table in ActiveRecord migrations, for example.

So you can do something like this:

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
碍人泪离人颜 2024-10-20 07:44:15

在尝试这个过程中,我得到了以下内容,我不一定推荐它,并且它不太符合所需的语法,但它确实允许您使用赋值(某种程度)。因此,请本着完整的精神仔细阅读:

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。另外,似乎局部变量需要在屈服之前声明,否则它将不起作用——有人知道为什么吗?

In playing around with this I arrived at the following, which I don't necessarily recommend, and which doesn't quite fit the required syntax, but which does allow you to use assignment (sort of). So peruse in the spirit of completeness:

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

I believe Binding.eval only works in Ruby 1.9. Also, it seems the local variables need to be declared before yielding or it won't work -- anyone know why?

梨涡 2024-10-20 07:44:15

好吧,首先我必须说 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

OK, first I must say that pmdboi's answer is very elegant and almost certainly the right one.

Still, just in case you want a super cut-down DSL like

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

You can do something ugly like this:

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文