Computer >> कंप्यूटर ट्यूटोरियल >  >> प्रोग्रामिंग >> Ruby

डायनामिक वेब रेंडरिंग के लिए मजबूत Ruby टेम्पलेट लेक्सर बनाना

अपना स्कूबा डाइविंग सूट पहनें और अपने स्टेंसिल पैक करें, हम आज टेम्पलेट्स में गोता लगा रहे हैं!

वेब पेज रेंडर करने या ईमेल जनरेट करने वाले अधिकांश सॉफ़्टवेयर टेक्स्ट डॉक्यूमेंट में वेरिएबल डेटा एम्बेड करने के लिए टेम्पलेटिंग का उपयोग करते हैं। डॉक्यूमेंट की मुख्य संरचना अक्सर प्लेसहोल्डर्स के साथ एक स्टेटिक टेम्पलेट में सेट की जाती है। वेरिएबल डेटा, जैसे उपयोगकर्ता नाम या वेब पेज कंटेंट, पेज रेंडर करते समय प्लेसहोल्डर्स को प्रतिस्थापित करते हैं।

टेम्पलेटिंग में हमारे गोता के लिए, हम Mustache का एक सबसेट लागू करेंगे, एक टेम्पलेटिंग भाषा जो कई प्रोग्रामिंग भाषाओं में उपलब्ध है। इस एपिसोड में, हम टेम्पलेटिंग के विभिन्न तरीकों की जांच करेंगे। हम स्ट्रिंग कॉनकैटिनेशन को देखकर शुरू करेंगे, और अधिक जटिल टेम्पलेट्स की अनुमति देने के लिए अपना खुद का लेक्सर लिखकर समाप्त करेंगे।

नेटिव स्ट्रिंग इंटरपोलेशन का उपयोग करना

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

name = "Ruby Magic"
template = "Welcome to #{name}"
# => Welcome to Ruby Magic

बहुत बढ़िया! यह करने योग्य था। हालांकि, क्या होगा यदि हम टेम्पलेट को कई अवसरों के लिए पुन: उपयोग करना चाहते हैं, या अपने उपयोगकर्ताओं को टेम्पलेट अपडेट करने की अनुमति देना चाहते हैं?

इंटरपोलेशन तुरंत मूल्यांकन करता है। हम टेम्पलेट को पुन: उपयोग नहीं कर सकते (जब तक कि हम इसे पुनः परिभाषित न करें—उदाहरण के लिए, एक लूप में) और हम Welcome to #{name} टेम्पलेट को डेटाबेस में स्टोर नहीं कर सकते और बाद में eval फ़ंक्शन का उपयोग किए बिना इसे पॉप्युलेट कर सकते हैं।

सौभाग्य से, Ruby के पास स्ट्रिंग्स को इंटरपोलेट करने का एक अलग तरीका है: Kernel#sprintf या String#%। ये विधियां हमें टेम्पलेट को बदले बिना एक इंटरपोलेटेड स्ट्रिंग प्राप्त करने की अनुमति देती हैं। इस तरह, हम एक ही टेम्पलेट को कई बार पुन: उपयोग कर सकते हैं। यह मनमाने Ruby कोड के निष्पादन की अनुमति भी नहीं देता। आइए इसका उपयोग करें।

name = "Ruby Magic"
template = "Welcome to %{name}"
sprintf(template, name: name)
# => "Welcome to Ruby Magic"
template % { name: name }
# => "Welcome to Ruby Magic"

टेम्पलेटिंग के लिए Regexp दृष्टिकोण

जबकि उपरोक्त समाधान काम करता है, यह फूलप्रूफ नहीं है, और यह उस कार्यक्षमता को उजागर करता है जो हम आमतौर पर चाहते हैं उससे अधिक। आइए एक उदाहरण देखें:

name = "Ruby Magic"
template = "Welcome to %d"
sprintf(template, name: name)
# => TypeError (can't convert Hash into Integer)

Kernel#sprintf और String#% दोनों अलग-अलग प्रकार के डेटा को संभालने के लिए विशेष सिंटैक्स की अनुमति देते हैं। उनमें से सभी हमारे द्वारा पास किए गए डेटा के साथ संगत नहीं हैं। इस उदाहरण में, टेम्पलेट एक नंबर को फॉर्मेट करने की अपेक्षा करता है लेकिन एक Hash पास किया जाता है, जिससे TypeError उत्पन्न होता है।

लेकिन हमारे शेड में अधिक पावर टूल्स हैं: हम रेगुलर एक्सप्रेशन का उपयोग करके अपना खुद का इंटरपोलेशन लागू कर सकते हैं। रेगुलर एक्सप्रेशन का उपयोग करने से हम एक कस्टम सिंटैक्स को परिभाषित कर सकते हैं, जैसे Mustache/Handlebars प्रेरित शैली।

name = "Ruby Magic"
template = "Welcome to {{name}}"
assigns = { "name" => name }
template.gsub(/{{(\w+)}}/) { assigns[$1] }
# => Welcome to Ruby Magic

हम String#gsub का उपयोग सभी प्लेसहोल्डर्स (डबल कर्ली ब्रेसेस में शब्द) को assigns हैश में उनके मान से बदलने के लिए करते हैं। यदि कोई संबंधित मान नहीं है, तो यह विधि कुछ भी डाले बिना प्लेसहोल्डर को हटा देती है।

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

मान लें कि हमें टेम्पलेट में कंडीशनल्स की आवश्यकता है। परिणाम एक वेरिएबल के मान के आधार पर अलग होना चाहिए।

Welcome to {{name}}! {{#if subscribed}} Thank you for subscribing to our mailing list. {{else}} Please sign up for our mailing list to be notified about new articles! {{/if}} Your friends at {{company_name}}

रेगुलर एक्सप्रेशन इस उपयोग के मामले को आसानी से नहीं संभाल सकते। यदि आप पर्याप्त प्रयास करते हैं, तो आप शायद अभी भी कुछ हैक कर सकते हैं, लेकिन इस बिंदु पर, एक उचित टेम्पलेटिंग भाषा बनाना बेहतर है।

टेम्पलेटिंग भाषा बनाना

टेम्पलेटिंग भाषा को लागू करना अन्य प्रोग्रामिंग भाषाओं को लागू करने के समान है। स्क्रिप्टिंग भाषा की तरह, एक टेम्पलेट भाषा को तीन घटकों की आवश्यकता होती है: एक लेक्सर, एक पार्सर, और एक इंटरप्रेटर। हम इनको एक-एक करके देखेंगे।

लेक्सर

पहला कार्य जिसे हमें निपटाना है उसे टोकनाइज़ेशन, या लेक्सिकल एनालिसिस कहा जाता है। प्रक्रिया प्राकृतिक भाषाओं में शब्द श्रेणियों की पहचान करने के समान है।

Ruby is a lovely language जैसे उदाहरण को लें। वाक्य में विभिन्न श्रेणियों के पांच शब्द होते हैं। उनकी श्रेणी की पहचान करने के लिए, आप एक डिक्शनरी लेंगे और प्रत्येक शब्द की श्रेणी को देखेंगे, जिसके परिणामस्वरूप ऐसी सूची मिलेगी: Noun, Verb, Article, Adjective, Noun। प्राकृतिक भाषा प्रसंस्क इन्हें "Parts of Speech" कहता है। औपचारिक भाषाओं में—जैसे प्रोग्रामिंग भाषाएं—उन्हें टोकन कहा जाता है।

एक लेक्सर टेम्पलेट को पढ़कर और टेक्स्ट की स्ट्रीम को प्रत्येक श्रेणी के लिए रेगुलर एक्सप्रेशन के एक सेट के साथ दिए गए क्रम में मिलान करके काम करता है। पहला जो मेल खाता है वह टोकन की श्रेणी को परिभाषित करता है और उससे प्रासंगिक डेटा जोड़ता है।

थोड़े से सिद्धांत के साथ, आइए अपने टेम्पलेट भाषा के लिए एक लेक्सर लागू करें। चीजों को थोड़ा आसान बनाने के लिए, हम Ruby के मानक पुस्तकालय से strscan को require करके StringScanner का उपयोग करते हैं। (वैसे, हमारे पिछले संस्करणों में से एक में StringScanner का एक उत्कृष्ट परिचय है।) पहला कदम के रूप में, आइए एक न्यूनतम संस्करण बनाएं जो सब कुछ CONTENT के रूप में पहचानता है।

हम एक नया StringScanner इंस्टेंस बनाकर और इसे until लूप का उपयोग करके अपना काम करने देकर ऐसा करते हैं जो केवल तभी रुकता है जब स्कैनर स्ट्रिंग के अंत तक पहुंच जाता है।

अभी के लिए, हम इसे कई लाइनों ( m मोडिफायर) में हर वर्ण (.*) से मिलान करने देते हैं और इसके लिए एक CONTENT टोकन लौटाते हैं। हम एक टोकन को एक सरणी के रूप में दर्शाते हैं जिसमें टोकन नाम पहला तत्व और कोई भी डेटा दूसरा तत्व होता है। हमारा बहुत बुनियादी लेक्सर इस तरह दिखता है:

require 'strscan'
module Magicbars
  class Lexer
    def self.tokenize(code)
      new.tokenize(code)
    end
    
    def tokenize(code)
      scanner = StringScanner.new(code)
      tokens = []
      until scanner.eos?
        tokens << [:CONTENT, scanner.scan(/.*?/m)]
      end
      tokens
    end
  end
end

इस कोड को Welcome to {{name}} के साथ चलाने पर हमें ठीक एक CONTENT टोकन की सूची वापस मिलती है जिसमें इससे जुड़ा सभी कोड होता है।

Magicbars::Lexer.tokenize("Welcome to {{name}}")
=> [[:CONTENT, "Welcome to {{name}}"]]

अब, आइए एक्सप्रेशन का पता लगाएं। ऐसा करने के लिए, हम लूप के अंदर कोड को संशोधित करते हैं, ताकि यह {{ और }} को OPEN_EXPRESSION और CLOSE के रूप में मिलान करे।

हम एक कंडीशनल जोड़कर ऐसा करते हैं जो विभिन्न मामलों की जांच करता है।

until scanner.eos?
  if scanner.scan(/{/)
    tokens << [:OPEN_EXPRESSION]
  elsif scanner.scan(/}/)
    tokens << [:CLOSE]
  elsif scanner.scan(/.*?/m)
    tokens << [:CONTENT, scanner.matched]
  end
end

OPEN_EXPRESSION और CLOSE टोकन से कर्ली ब्रेसेस को जोड़ने में कोई अतिरिक्त मूल्य नहीं है, इसलिए हम उन्हें छोड़ देते हैं। चूंकि scan कॉल अब कंडीशन का हिस्सा हैं, हम scanner.matched का उपयोग अंतिम मिलान के परिणाम को CONTENT टोकन से जोड़ने के लिए करते हैं।

दुर्भाग्य से, लेक्सर को फिर से चलाने पर, हमें पहले की तरह केवल एक CONTENT टोकन मिलता है। हमें अभी भी अंतिम एक्सप्रेशन को संशोधित करना होगा ताकि यह ओपन एक्सप्रेशन तक सब कुछ मिलान करे। हम scan_until का उपयोग डबल कर्ली ब्रेसेस के लिए पॉजिटिव लुकअहेड एंकर के साथ करते हैं जो स्कैनर को उनके ठीक पहले रोक देता है। लूप के अंदर हमारा कोड अब इस तरह दिखता है:

until scanner.eos?
  if scanner.scan(/{/)
    tokens << [:OPEN_EXPRESSION]
  elsif scanner.scan(/}/)
    tokens << [:CLOSE]
  elsif scanner.scan_until(/.*?(?={{|}})/m)
    tokens << [:CONTENT, scanner.matched]
  end
end

लेक्सर को फिर से चलाने पर, अब चार टोकन परिणामित होते हैं:

Magicbars::Lexer.tokenize("Welcome to {{name}}")
=> [[:CONTENT, "Welcome to "], [:OPEN_EXPRESSION], [:CONTENT, "name"], [:CLOSE]]

हमारा लेक्सर हमारे इच्छित परिणाम के बहुत करीब दिखता है। हालांकि, name नियमित कंटेंट नहीं है; यह एक पहचानकर्ता है! डबल कर्ली ब्रेसेस के बीच के स्ट्रिंग्स को बाहर के स्ट्रिंग्स से अलग व्यवहार किया जाना चाहिए।

एक स्टेट मशीन

ऐसा करने के लिए, हम लेक्सर को दो अलग-अलग स्टेट्स के साथ एक स्टेट मशीन में बदल देते हैं। यह default स्टेट में शुरू होता है। जब यह एक OPEN_EXPRESSION टोकन से टकराता है, तो यह expression स्टेट में चला जाता है और वहां तब तक रहता है जब तक यह एक CLOSE टोकन से नहीं टकराता जो इसे default स्टेट पर वापस ट्रांजिशन कर देता है।

हम एक सरणी का उपयोग करके वर्तमान स्टेट को प्रबंधित करने वाले कुछ तरीकों को जोड़कर स्टेट मशीन को लागू करते हैं।

def stack
  @stack ||= []
end

def state
  stack.last || :default
end

def push_state(state)
  stack.push(state)
end

def pop_state
  stack.pop
end

state विधि या तो वर्तमान स्टेट या default लौटाएगी। push_state लेक्सर को स्टैक में इसे जोड़कर एक नए स्टेट में ले जाता है। pop_state लेक्सर को पिछले स्टेट पर वापस ले जाता है।

अब, हम लूप के भीतर कंडीशनल को विभाजित करते हैं और इसे एक कंडीशनल द्वारा लपेटते हैं जो वर्तमान स्टेट की जांच करता है। default स्टेट में, हम OPEN_EXPRESSION और CONTENT दोनों टोकन को संभालते हैं। इसका यह भी अर्थ है कि CONTENT के लिए रेगुलर एक्सप्रेशन को अब }} लुकअहेड की आवश्यकता नहीं है, इसलिए हम इसे छोड़ देते हैं। expression स्टेट में, हम CLOSE टोकन को संभालते हैं और IDENTIFIER के लिए एक नया रेगुलर एक्सप्रेशन जोड़ते हैं। बेशक, हम OPEN_EXPRESSION पर push_state कॉल और CLOSE पर pop_state कॉल जोड़कर स्टेट ट्रांजिशन को भी लागू करते हैं।

if state == :default
  if scanner.scan(/{/)
    tokens << [:OPEN_EXPRESSION]
    push_state :expression
  elsif scanner.scan_until(/.*?(?={{)/m)
    tokens << [:CONTENT, scanner.matched]
  end
elsif state == :expression
  if scanner.scan(/}/)
    tokens << [:CLOSE]
    pop_state
  elsif scanner.scan(/[\w\-]+/)
    tokens << [:IDENTIFIER, scanner.matched]
  end
end

इन परिवर्तनों के साथ, लेक्सर अब हमारे उदाहरण को सही ढंग से टोकनाइज़ करता है।

Magicbars::Lexer.tokenize("Welcome to {{name}}")
# => [[:CONTENT, "Welcome to "], [:OPEN_EXPRESSION], [:IDENTIFIER, "name"], [:CLOSE]]

इसे अपने लिए कठिन बनाना

आइए एक अधिक उन्नत उदाहरण पर चलते हैं। यह कई एक्सप्रेशन के साथ-साथ एक ब्लॉक का उपयोग करता है।

Welcome to {{name}}! {{#if subscribed}} Thank you for subscribing to our mailing list. {{else}} Please sign up for our mailing list to be notified about new articles! {{/if}} Your friends at {{company_name}}

यह कोई आश्चर्य की बात नहीं है कि हमारा लेक्सर इस उदाहरण को पार्स करने में विफल रहता है। इसे काम करने के लिए, हमें लापता टोकन जोड़ने होंगे और इसे अंतिम एक्सप्रेशन के बाद कंटेंट को संभालने के लिए बनाना होगा। लूप के अंदर कोड इस तरह दिखता है:

if state == :default
  if scanner.scan(/{#/)
    tokens << [:OPEN_BLOCK]
    push_state :expression
  elsif scanner.scan(/{{\//)
    tokens << [:OPEN_END_BLOCK]
    push_state :expression
  elsif scanner.scan(/{else/)
    tokens << [:OPEN_INVERSE]
    push_state :expression
  elsif scanner.scan(/{/)
    tokens << [:OPEN_EXPRESSION]
    push_state :expression
  elsif scanner.scan_until(/.*?(?={{)/m)
    tokens << [:CONTENT, scanner.matched]
  else
    tokens << [:CONTENT, scanner.rest]
    scanner.terminate
  end
elsif state == :expression
  if scanner.scan(/\s+/)
    # Ignore whitespace
  elsif scanner.scan(/}/)
    tokens << [:CLOSE]
    pop_state
  elsif scanner.scan(/[\w\-]+/)
    tokens << [:IDENTIFIER, scanner.matched]
  else
    scanner.terminate
  end
end
कृपया ध्यान रखें कि कंडीशन का क्रम कुछ हद तक महत्वपूर्ण है। पहला रेगुलर एक्सप्रेशन जो मेल खाता है उसे असाइन किया जाता है। इस प्रकार, अधिक विशिष्ट एक्सप्रेशन को अधिक सामान्य से पहले आना होगा। इसका प्रमुख उदाहरण ब्लॉक के लिए विशेष ओपन टोकन का संग्रह है।

लेक्सर के अंतिम संस्करण का उपयोग करके, उदाहरण अब इसमें टोकनाइज़ होता है:

[
  [:CONTENT, "Welcome to "],
  [:OPEN_EXPRESSION],
  [:IDENTIFIER, "name"],
  [:CLOSE],
  [:CONTENT, "!\n\n"],
  [:OPEN_BLOCK],
  [:IDENTIFIER, "if"],
  [:IDENTIFIER, "subscribed"],
  [:CLOSE],
  [:CONTENT, "\n Thank you for subscribing to our mailing list.\n"],
  [:OPEN_INVERSE],
  [:CLOSE],
  [:CONTENT, "\n Please sign up for our mailing list to be notified about new articles!\n"],
  [:OPEN_END_BLOCK],
  [:IDENTIFIER, "if"],
  [:CLOSE],
  [:CONTENT, "\n\nYour friends at "],
  [:OPEN_EXPRESSION],
  [:IDENTIFIER, "company_name"],
  [:CLOSE],
  [:CONTENT, "\n"]
]

अब जब हम समाप्त कर चुके हैं, तो हमने सात अलग-अलग प्रकार के टोकन की पहचान की है:

टोकन उदाहरण
OPEN_BLOCK {{#
OPEN_END_BLOCK {{/
OPEN_INVERSE {{else
OPEN_EXPRESSION {{
CONTENT एक्सप्रेशन के बाहर कुछ भी (सामान्य HTML या टेक्स्ट)
CLOSE }}
IDENTIFIER पहचानकर्ता में वर्ड कैरेक्टर, नंबर, _, और - होते हैं

अगला कदम एक पार्सर को लागू करना है जो टोकन स्ट्रीम की संरचना का पता लगाने की कोशिश करता है और इसे एक abstract syntax tree में अनुवाद करता है, लेकिन यह दूसरी बार के लिए है।

आगे का रास्ता

हमने स्ट्रिंग इंटरपोलेशन का उपयोग करके एक बुनियादी टेम्पलेटिंग सिस्टम को लागू करने के विभिन्न तरीकों को देखकर अपनी टेम्पलेटिंग भाषा की दिशा में अपनी यात्रा शुरू की। जब हम पहले दृष्टिकोण की सीमाओं से टकराए, तो हमने एक उचित टेम्पलेटिंग सिस्टम को लागू करना शुरू किया।

अभी के लिए, हमने एक लेक्सर लागू किया जो टेम्पलेट का विश्लेषण करता है और विभिन्न प्रकार के टोकन का पता लगाता है। Ruby Magic के आगामी संस्करण में, हम एक पार्सर और साथ ही एक इंटरप्रेटर को लागू करके यात्रा जारी रखेंगे ताकि एक इंटरपोलेटेड स्ट्रिंग उत्पन्न हो सके।

  1. BASH में TXT टेम्पलेट स्क्रिप्ट कैसे बनाएं BASH में TXT टेम्पलेट स्क्रिप्ट कैसे बनाएं

    यदि आप खुद को दिन-रात टर्मिनल में समान टेक्स्ट बनाते हुए पाते हैं, तो क्यों न एक .txt टेम्पलेट स्क्रिप्ट बनाकर प्रक्रिया को सरल बनाया जाए और अपना समय बचाया जाए? यदि यह दिलचस्प लगता है, तो एक टर्मिनल चालू करें, अपना पसंदीदा टेक्स्ट एडिटर चुनें, और शुरू करें! एक नई टेम्पलेट स्क्रिप्ट बनाएं जिस भी तरी

  1. Ruby मेथड्स समझाए गए: आसानी से कस्टम फंक्शन्स बनाएं Ruby मेथड्स समझाए गए: आसानी से कस्टम फंक्शन्स बनाएं

    मेथड्स को कैसे परिभाषित करें Ruby प्रोग्रामिंग भाषा में कई शक्तिशाली बिल्ट-इन मेथड्स हैं जिनका आप उपयोग कर सकते हैं, लेकिन आप अपने खुद के भी बना सकते हैं। कैसे? आप def कीवर्ड का उपयोग करके अपनी खुद की इंस्टेंस मेथड को परिभाषित कर सकते हैं। यहाँ सिंटैक्स है: def gimme_bacon puts Bacon plz. end यहा

  1. Ruby 2.6 में 9 प्रमुख संवर्द्धन का अन्वेषण Ruby 2.6 में 9 प्रमुख संवर्द्धन का अन्वेषण

    Ruby का एक नया संस्करण नई सुविधाओं और प्रदर्शन सुधारों के साथ आ रहा है। क्या आप परिवर्तनों के साथ बने रहना चाहेंगे? आइए एक नज़र डालते हैं! अंतहीन रेंज (Endless Ranges) Ruby 2.5 और पुराने संस्करण पहले से ही अंतहीन रेंज का एक रूप (Float::INFINITY के साथ) का समर्थन करते हैं, लेकिन Ruby 2.6 इसे अगले स्त