У меня есть маленькая, но растущая платформа для создания систем .NET с рубином / грабли, что я продолжал работать некоторое время теперь. В этой кодовой базе у меня есть следующее:
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), и содержание выполнить метода. Каждая задача имеет свой собственный, выполняют реализацию метода.
Я постоянно исправляю ошибки в этом шаблоне кода, и я должен повторить фиксацию 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
Хорошо, вот некоторое метапрограммирование, которое сделает то, что вы хотите (в 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< - чтобы получить метакласс для любого объекта, чтобы вы могли определить методы этого объекта
define_method
- чтобы вы могли определить методы, используя текущие локальные переменныеТакже полезны
const_set
, const_get
: позволяют устанавливать/получать константыclass_eval
: позволяет определять методы, используя def
, как если бы вы находились в классе . . end
regionЧто-то вроде этого, проверено на 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", он: