2012-01-03 19 views
5

मैं अपने रूबी प्रोजेक्ट के लिए "क्लास-कम डीएसएल" का एक प्रकार बनाने का तरीका जानने का प्रयास कर रहा हूं, जैसे कि ककड़ी चरण परिभाषा फ़ाइल या मार्गों में चरण परिभाषाओं को परिभाषित किया गया है, सिनात्रा एप्लिकेशन में परिभाषित किया गया है। मुझे लगता है यह एक बुरा व्यवहार तरीके हैं कि के एक समूह के साथ वैश्विक (Kernel) नामस्थान को दूषित करने के लिएरूबी में कक्षा-कम डीएसएल कैसे बनाएं?

#sample.rb 

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

:

उदाहरण के लिए, मैं एक फ़ाइल जहां मेरे सारे डीएसएल कार्यों कहा जा रहा है करना चाहते हैं मेरी परियोजना के लिए विशिष्ट। तो when_string_matches और call_another_method विधियों को मेरी लाइब्रेरी में परिभाषित किया जाएगा और sample.rb फ़ाइल को मेरे डीएसएल विधियों के संदर्भ में किसी भी तरह मूल्यांकन किया जाएगा।

अद्यतन: यहाँ कैसे इन डीएसएल तरीकों वर्तमान में परिभाषित कर रहे हैं का एक उदाहरण है:

डीएसएल तरीकों एक वर्ग है कि subclassed किया जा रहा में परिभाषित कर रहे हैं (मैं के बीच इन तरीकों का पुन: उपयोग के लिए एक रास्ता खोजना चाहते हैं सरल DSL और वर्ग उदाहरण):

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 
तो कुछ बिंदु पर

, मेरे कार्यक्रम के प्रारंभ के दौरान, मैं sample.rb फ़ाइल को पार्स और इन कार्यों संग्रहीत करना चाहते हैं बाद में निष्पादित किया जाना है:

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 

मेरे पास अब तक की समस्या है (और मैंने जो विभिन्न चीजें मैंने कोशिश की है, मैंने ऊपर उल्लेख नहीं किया है) तब होता है जब फ़ाइल में एक ब्लॉक परिभाषित किया जाता है, उदाहरण विधि उपलब्ध नहीं है (मुझे पता है कि यह अभी एक अलग वर्ग में है)। लेकिन मैं जो करना चाहता हूं वह Parser कक्षा में eval'ing के बजाय उस संदर्भ में एक उदाहरण बनाने और eval'ing की तरह है। लेकिन मुझे नहीं पता कि यह कैसे करें।

मुझे उम्मीद है कि यह समझ में आता है। किसी भी मदद, अनुभव, या सलाह की सराहना की जाएगी।

उत्तर

4

आप जो करना चाहते हैं उसे करने के लिए आपको एक पेट जवाब देने के लिए थोड़ा चुनौतीपूर्ण है। मैं अनुशंसा करता हूं कि आप Eloquent Ruby पर एक नज़र डालें क्योंकि वहां डीएसएल से निपटने में कुछ अध्याय हैं जो शायद आपके लिए मूल्यवान होंगे। आपने कुछ अन्य पुस्तकालयों के बारे में कुछ जानकारी मांगी है कि वे क्या करते हैं, इसलिए मैं आपको संक्षिप्त रूप से एक सिंहावलोकन देने का प्रयास कर सकता हूं।

सिनात्रा

आप सिनात्रा कोड sinatra/main.rb में देखें, तो आप देखेंगे कि यह कोड का मुख्य लाइन में Sinatra::Delegator फैली देखेंगे। Delegator बहुत दिलचस्प है ..

यह सभी तरीकों कि यह सौंपने के लिए

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 

चाहता सेट और वर्ग एक वर्ग चर के रूप में करने के लिए सौंपने के लिए इतना है कि यह अगर जरूरत अधिरोहित जा सकता है सेट ..

self.target = Application 

और प्रतिनिधि विधि अच्छी तरह से आप respond_to? का उपयोग करके इन तरीकों को ओवरराइड करने देता है या अगर विधि से परिभाषित नहीं है यह target वर्ग के लिए बाहर कॉल ..

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 

ककड़ी

ककड़ी treetop language library उपयोग करता है। यह डीएसएल बनाने के लिए एक शक्तिशाली (और जटिल-यानी सीखने के लिए गैर-तुच्छ) उपकरण है। यदि आप अपने डीएसएल को बहुत बढ़ने की उम्मीद करते हैं तो आप इस 'बड़ी बंदूक' का उपयोग करने के लिए सीखने में निवेश करना चाहेंगे। यहां वर्णन करने के लिए यह बहुत अधिक है।

Haml

आप Haml के बारे में पूछना नहीं था, लेकिन यह सिर्फ एक और डीएसएल कि 'मैन्युअल' कार्यान्वित किया जाता है, अर्थात यह पेड़ों का उपयोग नहीं करता है। मूल रूप से (यहाँ सकल अति सरलीकरण) यह haml फ़ाइल पढ़ता है और प्रत्येक पंक्ति with a case statement प्रक्रियाओं ...

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) 
    ... 

मैं इसे सीधे तरीकों के लिए बाहर कॉल करने के लिए इस्तेमाल किया लगता है, लेकिन अब यह फ़ाइल preprocessing और एक ढेर में आदेशों धक्का है प्रकार के। जैसे the plain method

FYI करें definition of the constants इस तरह दिखता है ..

# 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

मेरे लिए वहां पचाने के लिए बहुत कुछ है क्योंकि इसमें से कुछ मेरे सिर पर थोड़ा सा है, लेकिन यह अभी भी उपयोगी है। धन्यवाद! – Andrew

2

बस एक विधि when_string_matches कहा जाता है जो एक तर्क को किसी रेगुलर एक्सप्रेशन लेता है परिभाषित करते हैं, जो कुछ भी "स्ट्रिंग" के खिलाफ यह परीक्षण आप पैदावार के बारे में बात कर रहे हैं, और सशर्त, गुजर जो कुछ name अपने ब्लॉक करने के लिए है:

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

यह अनिवार्य रूप से सभी रूबी डीएसएल हैं: रोचक नामों के साथ तरीके जो अक्सर ब्लॉक स्वीकार करते हैं।

+1

... कि 'Kernel' पर परिभाषित कर रहे हैं। – Reactormonk

+0

फिर बाद में निष्पादन के लिए, किसी भी राज्य चर के साथ दिए गए ब्लॉक को संग्रहीत करने के लिए अपनी विधि परिभाषा को बदलें। – meagar

+0

ठीक है, मैंने कोड प्रश्नों के एक टन के साथ अपना प्रश्न अपडेट किया है, जो मुझे उम्मीद है कि मेरी स्थिति बेहतर ढंग से समझा जाए। समस्या फ़ाइल को पार्सिंग और eval'ing और उदाहरण उदाहरण विधियों के साथ निहित है जो उपलब्ध नहीं हैं जहां ब्लॉक पहली बार परिभाषित किया गया था। – Andrew

3

आप अपने कोड को व्यवस्थित करने के लिए मॉड्यूल का उपयोग कर सकते हैं। आप विधि का उपयोग कर Module कक्षा में अपने डीएसएल विधियों को जोड़ सकते हैं। यहां बताया गया है कि आरएसपीईसी कैसे करता है। आखिरी दो पंक्तियां जो आप शायद खोज रहे हैं। डीएसएल के सरल रखने के बारे में @ मीगर को +1!

साथ ही @UncleGene बताते हैं, आरएसपीसी डीएसएल विधियों के साथ कर्नेल को प्रदूषित करता है। मुझे यकीन नहीं है कि इसके आसपास कैसे जाना है। अगर describe विधि के साथ एक और डीएसएल था, तो यह निर्धारित करना मुश्किल होगा कि describe कोई इसका उपयोग कर रहा था।

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

यह बहुत उपयोगी है, धन्यवाद! – Andrew

+1

यहां प्रदूषण कर्नेल का विस्तार नहीं करता है? 'rspec' की आवश्यकता है; Kernel.methods.grep/description/=> वर्णन करता है। और मुझे यकीन नहीं है कि प्रदूषण मॉड्यूल को बेहतर तरीके से (AFAIU OP प्रदूषण से बचने की कोशिश कर रहा था) – UncleGene

+0

@UncleGene आप सही हैं। मैं इस बिंदु को जोड़ने के लिए अपना जवाब संपादित कर रहा हूं। – CubaLibre

संबंधित मुद्दे