2010-02-17 8 views
8

Tengo un marco pequeño pero creciente para building .net systems with ruby/rake, en el que he estado trabajando desde hace un tiempo. En esta base de código, tengo el siguiente:¿Cómo puedo convertir este código a meta-programación, así puedo dejar de duplicarlo?

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 

el patrón que este código sigue se repite alrededor de 20 veces en el marco. La diferencia en cada versión es el nombre de la clase que se está creando/llamando (en lugar de AssemblyInfoTask, puede ser MSBuildTask o NUnitTask) y el contenido del método de ejecución. Cada tarea tiene su propia implementación de método de ejecución.

Estoy constantemente arreglando errores en este patrón de código y tengo que repetir la corrección 20 veces, cada vez que necesito una solución.

Sé que es posible hacer magia de meta-programación y conectar este código para cada una de mis tareas desde una única ubicación ... pero me está costando trabajo hacerlo funcionar.

mi idea es que quiero ser capaz de llamar a algo como esto:

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 

y esto iba a cablear todo lo que necesito.

¡Necesito ayuda! consejos, sugerencias, alguien dispuesto a abordar esto ... ¿cómo puedo evitar tener que repetir este patrón de código una y otra vez?

Actualización: Puede obtener el código fuente completo aquí: http://github.com/derickbailey/Albacore/ el código proporcionado es /lib/rake/assemblyinfotask.rb

+0

+1 para usar Github, es muy bueno poder navegar por toda la fuente. – Xorlev

+0

¿Por qué no colocas todas las definiciones de clases en el archivo del módulo? El enfoque de metaprogramación solo le ahorra 1 línea de código real (no "final"), a cambio de una ofuscación adicional. A menos que necesite definir dinámicamente las tareas, solo usaría el enfoque de vanilla. – klochner

+0

. . . a menos que, por supuesto, este sea un ejercicio de aprendizaje más que un proyecto terminado. – klochner

Respuesta

4

Ok, aquí hay algunos metaprogramming que va a hacer lo que quiere (o en 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 

las herramientas clave en la caja de herramientas metaprogramadores son:

  • class<<self;self;end - ge t en la metaclase para cualquier objeto, por lo que puede definir métodos en ese objeto
  • define_method - por lo que puede definir los métodos de uso de variables locales actuales

También son útiles

  • const_set, const_get: permitir para establecer/obtener constantes
  • class_eval: le permite definir métodos usando def como si estuviera en una región class <Classname> ... end
+1

Si desea tener la capacidad de capitalizar automáticamente, etc., puede encontrar esa funcionalidad en ActiveSupport. – yfeldblum

+0

estoy usando Ruby 1.8.6 ... creo que esta solución es para 1.9+ –

+0

ActiveSupport solo puede autocapitalizarse cuando usa delimitadores de subrayado, que Derick no parecía querer. – rampion

1

Algo como esto, probado en 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' 

La pieza central es el método "create_task", que:

  • Crea nueva clase
  • añade método de ejecución
  • Nombres de la clase y lo expone
Cuestiones relacionadas