2012-01-03 16 views
5

Adım adım tanımlarının bir Salatalık adımı tanım dosyasında tanımlanmasına benzer şekilde veya bir Sinatra uygulamasında yollar tanımlanmasına benzer şekilde Ruby projem için bir tür "sınıfsız DSL" yaratmanın nasıl çalıştığını anlamaya çalışıyorum. Bunun olan yöntemlerin bir grup ile küresel (Kernel) ad kirletme için kötü bir uygulamadır varsayıyorumRuby'de sınıfsız bir DSL nasıl oluşturulur?

#sample.rb 

when_string_matches /hello (.+)/ do |name| 
    call_another_method(name) 
end 

:

Örneğin, ben bütün DSL fonksiyonları çağrılan bir dosyayı sahip olmak istiyorum projeme özel. Dolayısıyla, kitaplığımda when_string_matches ve call_another_method yöntemleri tanımlanacak ve sample.rb dosyası bir şekilde DSL yöntemlerimin bağlamında değerlendirilecektir.

Güncelleme: İşte bu DSL yöntemleri halen tanımlanmaktadır nasıl bir örnek:

yöntemleri sınıflandırma olan bir sınıfında tanımlanmıştır DSL (ı arasındaki bu yöntemleri yeniden bir yolunu bulmak istiyorum basit DSL ve sınıf örneklerini): programımın başlatma sırasında bir noktada sonra

module MyMod 
    class Action 
    def call_another_method(value) 
     puts value 
    end 

    def handle(text) 
     # a subclass would be expected to define 
     # this method (as an alternative to the 
     # simple DSL approach) 
    end 
    end 
end 

, ben sample.rb dosyayı ayrıştırmak ve bu eylemleri depolamak istediğiniz sonradan yürütülecek:

module MyMod 
    class Parser 

    # parse the file, saving the blocks and regular expressions to call later 
    def parse_it 
     file_contents = File.read('sample.rb') 
     instance_eval file_contents 
    end 

    # doesnt seem like this belongs here, but it won't work if it's not 
    def self.when_string_matches(regex, &block) 
     MyMod.blocks_for_executing_later << { regex: regex, block: block } 
    end 
    end 
end 

# Later... 

module MyMod 
    class Runner 

    def run 
     string = 'hello Andrew' 
     MyMod.blocks_for_executing_later.each do |action| 
     if string =~ action[:regex] 
      args = action[:regex].match(string).captures 
      action[:block].call(args) 
     end 
     end 
    end 

    end 
end 

Şu ana kadar sahip olduğum sorun (ve yukarıda bahsetmediğim çeşitli şeyler), dosyada bir blok tanımlandığında, örnek yöntemi kullanılamıyor. Şu anda farklı bir sınıfta). Ama benim yapmak istediğim, Parser sınıfında değerlendirmekten ziyade bir örnek oluşturma ve bu bağlamda değerlendirme yapma gibi. Ama bunu nasıl yapacağımı bilmiyorum.

Umarım bu mantıklıdır. Herhangi bir yardım, deneyim veya tavsiye takdir edilecektir.

def when_string_matches(regex) 
    # do whatever is required to produce `my_string` and `name` 
    yield(name) if my_string =~ regex 
end 

:

cevap

4

Size ne yapmak istediğinizi nasıl yapacağınıza dair bir cevap vermek biraz zor. Orada, muhtemelen size değerli olacak DSL'lerle ilgilenen birkaç bölüm olduğundan, Eloquent Ruby kitabına bir göz atmanızı tavsiye ederim. Bu diğer kütüphanelerin yaptıkları şeyi nasıl yaptıklarına dair bazı bilgiler sordunuz, bu yüzden size kısa bir genel bakış sunmayı deneyebilirim.

Sinatra

Eğer sinatra koduna sinatra/main.rb içine bakarsanız bu kodun temel hattına Sinatra::Delegator uzanır olduğunu göreceksiniz. Delegator oldukça ilginçtir ..

O

delegate :get, :patch, :put, :post, :delete, :head, :options, :template, :layout, 
     :before, :after, :error, :not_found, :configure, :set, :mime_type, 
     :enable, :disable, :use, :development?, :test?, :production?, 
     :helpers, :settings 

temsilci istiyor tüm yöntemleri kurar ve gerekirse bunu geçersiz kılınabilir, böylece bir sınıf değişkeni olarak temsilci sınıfı kurar ..

self.target = Application 

ve temsilci yöntemi güzel sen respond_to? kullanarak bu yöntemleri geçersiz kılmak için izin verir veya yöntem tanımlı değilse o target sınıfa seslenir ..

def self.delegate(*methods) 
    methods.each do |method_name| 
    define_method(method_name) do |*args, &block| 
     return super(*args, &block) if respond_to? method_name 
     Delegator.target.send(method_name, *args, &block) 
    end 
    private method_name 
    end 
end 

Salatalık

Salatalık treetop language library kullanır. DSL'leri oluşturmak için güçlü (ve karmaşık - yani öğrenmesi gereken bir şey değildir) aracıdır. DSL'nizin çok fazla büyüdüğünü düşünüyorsanız, bu 'büyük silahı' kullanmayı öğrenmeye yatırım yapmak isteyebilirsiniz. Burada açıklamak için çok fazla.

HAML

Sen haml sormadın, ama buna Treetop kullanmaz yani 'el' uygulanmaktadır sadece başka DSL var. Temelde (burada brüt oversimplification) ben şimdi dosyayı ön işlenmesi ve bir yığın haline komutları sıkıştırıyor doğrudan yöntemlere dışarı derdik düşünüyorum, ama

def process_line(text, index) 
    @index = index + 1 

    case text[0] 
    when DIV_CLASS; push div(text) 
    when DIV_ID 
    return push plain(text) if text[1] == ?{ 
    push div(text) 
    when ELEMENT; push tag(text) 
    when COMMENT; push comment(text[1..-1].strip) 
    ... 

... haml dosyasını okur ve her satırı with a case statement işler çeşitler. Örneğin.

the plain method Bilginize definition of the constants şöyle .. Kernel` `üzerinde tanımlanan

# Designates an XHTML/XML element. 
ELEMENT   = ?% 
# Designates a `<div>` element with the given class. 
DIV_CLASS  = ?. 
# Designates a `<div>` element with the given id. 
DIV_ID   = ?# 
# Designates an XHTML/XML comment. 
COMMENT   = ?/ 
+0

Orada sindirmek için bir çok şey var, çünkü bazısı biraz başımdan geçti, ama yine de yararlı. Teşekkürler! – Andrew

2

Sadece ne olursa olsun "dizesi" karşı test eder bir argüman olarak bir düzenli ifade alır when_string_matches adlı bir yöntem tanımlamak onun bloğuna ne olursa olsun name geçen şartlı verimi bahsediyoruz ve konum Bu aslında tüm Ruby DSL'leri: Genellikle blokları kabul eden ilginç isimlere sahip yöntemler.

+1

.... – Reactormonk

+0

Daha sonra yürütme için herhangi bir durum değişkeniyle birlikte verilen bloğu saklamak için yöntem tanımınızı değiştirin. – meagar

+0

Tamam, sorunumu daha iyi açıklayacağımı umduğum bir kod örnekleri ile güncelledim. Sorun, bloğun ilk tanımlandığı yerde bulunmayan dosya ve çağrı örnekleme yöntemlerini ayrıştırma ve değerlendirme ile ilgilidir. – Andrew

3

Kodunuzu düzenlemek için Modülleri kullanabilirsiniz. DSL yöntemlerinizi Module#include yöntemini kullanarak Module sınıfına ekleyebilirsiniz. RSpec bunu nasıl yapıyor. Son iki satır, muhtemelen aradığınız şey. DSL'leri basit tutmakla ilgili +1'e kadar!

Ayrıca @UncleGene işaret ettiği gibi, RSpec DSL yöntemleriyle Kernel'i kirletiyor. Bunu nasıl halledeceğimi bilmiyorum. describe yöntemiyle başka bir DSL varsa, hangi describe modelinin kullanıldığını belirlemek zor olacaktır.

module RSpec 
    module Core 
    # Adds the `describe` method to the top-level namespace. 
    module DSL 
     # Generates a subclass of {ExampleGroup} 
     # 
     # ## Examples: 
     # 
     #  describe "something" do 
     #  it "does something" do 
     #   # example code goes here 
     #  end 
     #  end 
     # 
     # @see ExampleGroup 
     # @see ExampleGroup.describe 
     def describe(*args, &example_group_block) 
     RSpec::Core::ExampleGroup.describe(*args, &example_group_block).register 
     end 
    end 
    end 
end 
extend RSpec::Core::DSL 
Module.send(:include, RSpec::Core::DSL) 
+0

bu çok yardımcı, teşekkürler! – Andrew

+1

Kernel'i kirletmiyor mu? 'rspec' gerektirir; Kernel.methods.grep/describe/=> anlatır. Ve Modülü kirletmenin daha iyi olduğundan emin değilim (AFAIU OP, kirliliği önlemeye çalışıyordu) – UncleGene

+0

@UncleGene haklısınız. Bu noktayı eklemek için cevabımı düzenliyorum. – CubaLibre