Как я могу преобразовать этот код в метапрограммирование, чтобы я мог перестать дублировать его? - PullRequest
8 голосов
/ 17 февраля 2010

У меня есть небольшая, но растущая среда для построения систем .net с ruby ​​/ rake , над которой я работаю некоторое время. В этой кодовой базе у меня есть следующее:

require 'rake/tasklib'

def assemblyinfo(name=:assemblyinfo, *args, &block)
  Albacore::AssemblyInfoTask.new(name, *args, &block)
end

module Albacore
  class AssemblyInfoTask < Albacore::AlbacoreTask
    def execute(name)
      asm = AssemblyInfo.new
      asm.load_config_by_task_name(name)
      call_task_block(asm)
      asm.write
      fail if asm.failed
    end
  end
end

шаблон, которому следует этот код, повторяется примерно 20 раз в рамках. Разница в каждой версии заключается в имени создаваемого / вызываемого класса (вместо AssemblyInfoTask, это может быть MSBuildTask или NUnitTask) и содержимом метода execute. Каждая задача имеет собственную реализацию метода execute.

Я постоянно исправляю ошибки в этом шаблоне кода, и мне приходится повторять исправление 20 раз, каждый раз, когда мне нужно исправление.

Я знаю, что можно сделать некоторую магию метапрограммирования и подключить этот код для каждой из моих задач из одного места ... но мне действительно тяжело заставить его работать.

моя идея в том, что я хочу иметь возможность вызывать что-то вроде этого:

create_task :assemblyinfo do |name|
  asm = AssemblyInfo.new
  asm.load_config_by_task_name(name)
  call_task_block(asm)
  asm.write
  fail if asm.failed
end

и это соединит все, что мне нужно.

Мне нужна помощь! советы, предложения, кто-то, кто хочет заняться этим ... как мне избежать повторения этого шаблона кода снова и снова?

Обновление: Вы можете получить полный исходный код здесь: http://github.com/derickbailey/Albacore/ предоставленный код /lib/rake/assemblyinfotask.rb

Ответы [ 2 ]

4 голосов
/ 17 февраля 2010

Хорошо, вот несколько метапрограммирований, которые будут делать то, что вы хотите (в ruby18 или ruby19)

def create_task(taskname, &execute_body)
  taskclass = :"#{taskname}Task"
  taskmethod = taskname.to_s.downcase.to_sym
  # open up the metaclass for main
  (class << self; self; end).class_eval do
    # can't pass a default to a block parameter in ruby18
    define_method(taskmethod) do |*args, &block|
      # set default name if none given
      args << taskmethod if args.empty?
      Albacore.const_get(taskclass).new(*args, &block)
    end
  end
  Albacore.const_set(taskclass, Class.new(Albacore::AlbacoreTask) do
    define_method(:execute, &execute_body)
  end)
end

create_task :AssemblyInfo do |name|
  asm = AssemblyInfo.new
  asm.load_config_by_task_name(name)
  call_task_block(asm)
  asm.write
  fail if asm.failed
end

Ключевыми инструментами в наборе инструментов метапрограммистов являются:

  • class<<self;self;end - чтобы получить метакласс для любого объекта, чтобы вы могли определить методы для этого объекта
  • define_method - чтобы вы могли определять методы, используя текущие локальные переменные

Также полезны

  • const_set, const_get: позволяет устанавливать / получать константы
  • class_eval: позволяет вам определять методы, используя def, как если бы вы были в class <Classname> ... end регионе
1 голос
/ 17 февраля 2010

Примерно так, проверено на ruby ​​1.8.6:

class String
  def camelize
    self.split(/[^a-z0-9]/i).map{|w| w.capitalize}.join
  end
end

class AlbacoreTask; end

def create_task(name, &block)
  klass = Class.new AlbacoreTask
  klass.send :define_method, :execute, &block
  Object.const_set "#{name.to_s.camelize}Task", klass
end

create_task :test do |name|
  puts "test: #{name}"
end

testing = TestTask.new
testing.execute 'me'

Основной частью является метод "create_task", это:

  • Создает новый класс
  • добавляет метод execute
  • Называет класс и выставляет его
...