为了方便用户和更干净的代码,我想编写一个可以像这样使用的类:
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的类变量中,但是我没有找到正确的条目。
有人能指出我正确的方向吗?
答案 0 :(得分:3)
我不确定是否可以让DSL使用赋值,我认为Ruby解释器总是假设infile
中的infile = 'path/to/something'
是该上下文中的局部变量(但{{1}可以使其工作)。但是,如果您可以在没有这些特定细节的情况下生活,您可以像这样实现您的DSL:
self.infile = 'path/to/something'
并像这样使用它:
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
(类似地实现其他属性)。
答案 1 :(得分: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
答案 2 :(得分: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。此外,似乎需要在屈服之前声明局部变量,否则它将无法工作 - 任何人都知道为什么?
答案 3 :(得分:0)
好的,首先我必须说pmdboi的答案非常优雅,几乎肯定是正确答案。
但是,如果你想要一个像
这样的超级缩减DSLEncoder::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