Использование процедур с DSL Руби - PullRequest
1 голос
/ 21 января 2011

Для удобства пользователя и более чистого кода я хотел бы написать класс, который можно использовать следующим образом:

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 ]

3 голосов
/ 21 января 2011

Я не уверен, что можно заставить DSL использовать присваивание, я думаю, что интерпретатор Ruby всегда будет предполагать, что infile в infile = '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

(реализовать другие свойства аналогично).

1 голос
/ 21 января 2011

Это невозможно сделать так, как ты это написал, 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
0 голосов
/ 27 января 2011

Хорошо, сначала я должен сказать, что ответ 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
0 голосов
/ 21 января 2011

Играя с этим, я пришел к следующему, которое не обязательно рекомендовать, и которое не совсем соответствует требуемому синтаксису, но которое позволяет вам использовать присваивание (вроде).Так что читайте в духе полноты:

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.Кроме того, кажется, что локальные переменные должны быть объявлены перед выдачей, иначе это не сработает - кто-нибудь знает почему?

...