কম্পিউটার

রুবির লুকানো রত্ন, স্ট্রিংস্ক্যানার

রুবি শুধুমাত্র একটি মজার ভাষা নয়, এটি একটি চমৎকার স্ট্যান্ডার্ড লাইব্রেরির সাথেও আসে। যার মধ্যে কিছু জানা নেই, এবং প্রায় লুকানো রত্ন। আজ অতিথি লেখক মাইকেল কোহল একটি প্রিয় হাইলাইট করেছেন:স্ট্রিংস্ক্যানার৷

রুবির লুকানো রত্ন:স্ট্রিংস্ক্যানার

ওপেনস্ট্রাক্ট এবং সেট ওভার CSV পার্সিং থেকে বেঞ্চমার্কিং-এর মতো ডেটা স্ট্রাকচার থেকে তৃতীয় পক্ষের রত্ন ইনস্টল করার অবলম্বন না করেই কেউ অনেক দূর যেতে পারে। যাইহোক, রুবির স্ট্যান্ডার্ড ইন্সটলেশনে কিছু কম পরিচিত লাইব্রেরি পাওয়া যায় যেগুলি খুব দরকারী হতে পারে, যার মধ্যে একটি হল StringScanner যা ডকুমেন্টেশন অনুযায়ী "একটি স্ট্রিং-এ আভিধানিক স্ক্যানিং অপারেশন প্রদান করে" .

স্ক্যানিং এবং পার্সিং

তাহলে "লেক্সিকাল স্ক্যানিং" এর মানে কি? মূলত এটি নির্দিষ্ট নিয়ম অনুসরণ করে একটি ইনপুট স্ট্রিং নেওয়া এবং এর থেকে অর্থপূর্ণ তথ্য বের করার প্রক্রিয়া বর্ণনা করে। উদাহরণস্বরূপ, এটি একটি কম্পাইলারের প্রথম পর্যায়ে দেখা যেতে পারে যা 2 + 1 এর মত একটি অভিব্যক্তি নেয় ইনপুট হিসাবে এবং এটিকে টোকেনগুলির নিম্নলিখিত অনুক্রমে পরিণত করে:

[{ number: "1" }, {operator: "+"}, { number: "1"}]

লেকসিকাল স্ক্যানারগুলি সাধারণত সসীম-স্টেট অটোমেটা হিসাবে প্রয়োগ করা হয় এবং সেখানে বেশ কিছু সুপরিচিত সরঞ্জাম উপলব্ধ রয়েছে যা আমাদের জন্য সেগুলি তৈরি করতে পারে (যেমন ANTLR বা Ragel)।

যাইহোক, কখনও কখনও আমাদের পার্সিং প্রয়োজনীয়তাগুলি এতটা বিস্তৃত নয় এবং একটি সহজ লাইব্রেরি যেমন রেগুলার এক্সপ্রেশন ভিত্তিক StringScanner এই ধরনের পরিস্থিতিতে খুব কাজে আসতে পারে। এটি একটি তথাকথিত স্ক্যান পয়েন্টার এর অবস্থান মনে রেখে কাজ করে যা স্ট্রিং এর মধ্যে একটি সূচক ছাড়া আর কিছুই নয়। স্ক্যানিং প্রক্রিয়া তারপর প্রদত্ত এক্সপ্রেশনের সাথে স্ক্যান পয়েন্টারের ঠিক পরে কোডটি মেলানোর চেষ্টা করে। ম্যাচিং অপারেশন ছাড়াও, StringScanner এছাড়াও স্ক্যান পয়েন্টার (স্ট্রিং এর মাধ্যমে সামনে বা পিছনে সরানো), সামনের দিকে তাকানো (এখনও স্ক্যান পয়েন্টার পরিবর্তন না করে পরবর্তী কী আছে তা দেখা) এবং সেইসাথে আমরা বর্তমানে যে স্ট্রিংটিতে আছি তা খুঁজে বের করার পদ্ধতিও প্রদান করে (এটি কি শুরু বা একটি লাইনের শেষ/সম্পূর্ণ স্ট্রিং ইত্যাদি)।

পার্সিং রেল লগ

যথেষ্ট তত্ত্ব, আসুন StringScanner দেখি কর্মে নিচের উদাহরণটি নিচের মত একটি Rails এর লগ এন্ট্রি নেবে,

log_entry = <<EOS
Started GET "/" for 127.0.0.1 at 2017-08-20 20:53:10 +0900
Processing by HomeController#index as HTML
  Rendered text template within layouts/application (0.0ms)
  Rendered layouts/_assets.html.erb (2.0ms)
  Rendered layouts/_top.html.erb (2.6ms)
  Rendered layouts/_about.html.erb (0.3ms)
  Rendered layouts/_google_analytics.html.erb (0.4ms)
Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms)
EOS

এবং এটিকে নিম্নলিখিত হ্যাশে পার্স করুন:

{
  method: "GET",
  path: "/"
  ip: "127.0.0.1",
  timestamp: "2017-08-20 20:53:10 +0900",
  success: true,
  response_code: "200",
  duration: "79ms",
}

NB:যদিও এটি StringScanner এর জন্য একটি ভাল উদাহরণ তৈরি করে একটি বাস্তব অ্যাপ্লিকেশন Lograge এবং এর JSON লগ ফরম্যাটার ব্যবহার করা ভাল হবে৷

StringScanner ব্যবহার করার জন্য আমাদের প্রথমে এটি প্রয়োজন:

require 'strscan'

এর পরে আমরা কনস্ট্রাক্টরের কাছে একটি যুক্তি হিসাবে লগ এন্ট্রি পাস করে একটি নতুন উদাহরণ শুরু করতে পারি। একই সময়ে আমরা আমাদের পার্সিং প্রচেষ্টার ফলাফল ধরে রাখতে একটি খালি হ্যাশও সংজ্ঞায়িত করব:

scanner = StringScanner.new(log_entry)
log = {}

আমরা এখন আমাদের স্ক্যান পয়েন্টারের বর্তমান অবস্থান পেতে স্ক্যানারের পোস পদ্ধতি ব্যবহার করতে পারি। প্রত্যাশিত হিসাবে, ফলাফল হল 0 , স্ট্রিং এর প্রথম অক্ষর:

scanner.pos #=> 0

আসুন এটিকে কল্পনা করি যাতে প্রক্রিয়াটি অনুসরণ করা সহজ হবে:

Started GET "/" for 127.0.0.1 at 2017-08-20 20:53:10 +0900
^
...
Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms)

স্ক্যানারের অবস্থার আরও আত্মদর্শনের জন্য আমরা beginning_of_line? ব্যবহার করতে পারি এবং eos? নিশ্চিত করতে যে স্ক্যান পয়েন্টারটি বর্তমানে একটি লাইনের শুরুতে রয়েছে এবং আমরা এখনও আমাদের ইনপুট পুরোপুরি গ্রহণ করিনি:

scanner.beginning_of_line? #=> true
scanner.eos? #=> false

প্রথম বিট তথ্য যা আমরা বের করতে চাই তা হল HTTP অনুরোধ পদ্ধতি, যা "Started" শব্দের পরে একটি স্পেস দিয়ে পাওয়া যাবে। স্ক্যান পয়েন্টারকে অগ্রসর করতে আমরা স্ক্যানারের যথাযথ নামযুক্ত স্কিপ পদ্ধতি ব্যবহার করতে পারি, যা উপেক্ষা করা অক্ষরের সংখ্যা ফেরত দেবে, যা আমাদের ক্ষেত্রে 8। উপরন্তু আমরা ম্যাচড ব্যবহার করতে পারি? সবকিছু আশানুরূপ কাজ করেছে তা নিশ্চিত করতে:

scanner.skip(/Started /) #=> 8
scanner.matched? #=> true

স্ক্যান পয়েন্টার এখন অনুরোধের পদ্ধতির ঠিক আগে:

Started GET "/" for 127.0.0.1 at 2017-08-20 20:53:10 +0900
          ^
...
Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms)

এখন আমরা প্রকৃত মান বের করতে scan_until ব্যবহার করতে পারি, যা পুরো রেগুলার এক্সপ্রেশন ম্যাচ রিটার্ন করে। যেহেতু অনুরোধের পদ্ধতিটি বড় হাতের অক্ষরে, তাই আমরা একটি সাধারণ অক্ষর শ্রেণী এবং + ব্যবহার করতে পারি। অপারেটর যা এক বা অক্ষরের সাথে মেলে:

log[:method] = scanner.scan_until(/[A-Z]+/) #=> "GET"

এই অপারেশনের পর স্ক্যান পয়েন্টারটি "GET" শব্দের চূড়ান্ত "T" এ থাকবে।

Started GET "/" for 127.0.0.1 at 2017-08-20 20:53:10 +0900
          ^
...
Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms)

অনুরোধ করা পথটি বের করতে, তাই আমাদের একটি স্পেস এড়িয়ে যেতে হবে এবং তারপরে ডবল উদ্ধৃতিতে আবদ্ধ সবকিছু বের করতে হবে। এটি অর্জনের বিভিন্ন উপায় রয়েছে, তাদের মধ্যে একটি হল একটি ক্যাপচার গ্রুপের মাধ্যমে (নিয়মিত অভিব্যক্তির অংশটি বন্ধনীতে অন্তর্ভুক্ত, যেমন (.+) ) যা এক বা একাধিক অক্ষরের সাথে মেলে:

scanner.scan(/\s"(.+)"/) #=> " \"/\""

যাইহোক, আমরা এই scan এর রিটার্ন মান ব্যবহার করব না সরাসরি অপারেশন, কিন্তু পরিবর্তে প্রথম ক্যাপচার গ্রুপের মান পেতে ক্যাপচার ব্যবহার করুন:

log[:path] =  scanner.captures.first #=> "/"

আমরা সফলভাবে পথটি বের করেছি এবং স্ক্যান পয়েন্টারটি এখন শেষের দ্বিগুণ উদ্ধৃতিতে রয়েছে:

Started GET "/" for 127.0.0.1 at 2017-08-20 20:53:10 +0900
          ^
...
Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms)

লগ থেকে IP ঠিকানা পার্স করতে, আমরা আবার skip ব্যবহার করি স্পেস দ্বারা বেষ্টিত স্ট্রিংটিকে উপেক্ষা করতে এবং তারপর scan_until ব্যবহার করুন এক বা একাধিক অ হোয়াইটস্পেস অক্ষর মেলে (\s হোয়াইটস্পেস এবং [^\s] প্রতিনিধিত্ব করে অক্ষর শ্রেণী এটির অস্বীকার):

scanner.skip(/ for /) #=> 5
log[:ip] = scanner.scan_until(/[^\s]+/) #=> "127.0.0.1"

স্ক্যান পয়েন্টার এখন কোথায় থাকবে বলতে পারবেন? এক মুহূর্তের জন্য এটি সম্পর্কে চিন্তা করুন এবং তারপর সমাধানের সাথে আপনার উত্তর তুলনা করুন:

Started GET "/" for 127.0.0.1 at 2017-08-20 20:53:10 +0900
          ^
...
Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms)

টাইমস্ট্যাম্প পার্স করা এখন খুব পরিচিত মনে করা উচিত. প্রথমে আমরা বিশ্বস্ত পুরানো skip ব্যবহার করি আক্ষরিক স্ট্রিংকে উপেক্ষা করতে " at " এবং তারপর scan_until ব্যবহার করুন বর্তমান লাইনের শেষ পর্যন্ত পড়তে, যা $ দ্বারা উপস্থাপিত হয় রেগুলার এক্সপ্রেশনে:

scanner.skip(/ at /) #=> 4
log[:timestamp] = scanner.scan_until(/$/) #=> "2017-08-20 20:53:10 +0900"

পরবর্তী তথ্যের যে অংশটিতে আমরা আগ্রহী তা হল শেষ লাইনে HTTP স্ট্যাটাস কোড, তাই আমরা skip_until ব্যবহার করব যাতে "সম্পূর্ণ" শব্দের পরে স্পেস পর্যন্ত পৌঁছে যায়।

scanner.skip_until(/Completed /) #=> 296

নাম অনুসারে এটি একইভাবে কাজ করে scan_until কিন্তু মিলিত স্ট্রিং ফেরত দেওয়ার পরিবর্তে এটি এড়িয়ে যাওয়া অক্ষরের সংখ্যা প্রদান করে। এটি স্ক্যান পয়েন্টারটিকে HTTP স্ট্যাটাস কোডের সামনে রাখে যা আমরা আগ্রহী।

Started GET "/" for 127.0.0.1 at 2017-08-20 20:53:10 +0900
...
Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms)
            ^

এখন আমরা প্রকৃত HTTP প্রতিক্রিয়া কোডটি স্ক্যান করার আগে, আমরা যদি বলতে পারি যে HTTP প্রতিক্রিয়া কোডটি একটি সাফল্য (এই উদাহরণের খাতিরে 2xx রেঞ্জের যেকোন কোড) বা ব্যর্থতা (অন্য সমস্ত রেঞ্জ) নির্দেশ করে তা কি ভাল হবে না? এটি অর্জন করার জন্য আমরা স্ক্যান পয়েন্টারটি না সরিয়ে পরবর্তী অক্ষরটি দেখার জন্য পিক ব্যবহার করব৷

log[:success] = scanner.peek(1) == "2" #=> true

এখন আমরা পরবর্তী তিনটি অক্ষর পড়ার জন্য স্ক্যান ব্যবহার করতে পারি, যা রেগুলার এক্সপ্রেশন /\d{3}/ দ্বারা উপস্থাপিত হয়। :

log[:response_code] = scanner.scan(/\d{3}/) #=> "200"

আবার স্ক্যান পয়েন্টার পূর্বে মিলে যাওয়া রেগুলার এক্সপ্রেশনের শেষে ডানদিকে থাকবে:

Started GET "/" for 127.0.0.1 at 2017-08-20 20:53:10 +0900
...
Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms)
            ^

আমাদের লগ এন্ট্রি থেকে আমরা যে শেষ বিট তথ্য বের করতে চাই তা হল মিলিসেকেন্ডে এক্সিকিউশন টাইম, যা skip দ্বারা অর্জন করা যেতে পারে। " OK in " স্ট্রিং-এ পিং করুন এবং তারপর আক্ষরিক স্ট্রিং "ms" সহ সবকিছু পড়া .

scanner.skip(/ OK in /) #=> 7
log[:duration] = scanner.scan_until(/ms/) #=> "79ms"

এবং সেখানে শেষ বিট দিয়ে, আমরা যে হ্যাশ চেয়েছিলাম তা আছে।

{
  method: "GET",
  path: "/"
  ip: "127.0.0.1",
  timestamp: "2017-08-20 20:53:10 +0900",
  success: true,
  response_code: "200",
  duration: "79ms",
}

সারাংশ

রুবির StringScanner সাধারণ রেগুলার এক্সপ্রেশন এবং একটি পূর্ণ-বিকশিত লেক্সারের মধ্যে একটি চমৎকার মধ্যম স্থল দখল করে। এটি জটিল স্ক্যানিং এবং পার্সিং প্রয়োজনের জন্য সেরা পছন্দ নয়। কিন্তু এটি সহজবোধ্য প্রকৃতির জন্য প্রাথমিক রেগুলার এক্সপ্রেশন জ্ঞান থাকা প্রত্যেকের জন্য ইনপুট স্ট্রিং থেকে তথ্য বের করা সহজ করে তোলে এবং আমি অতীতে প্রোডাকশন কোডে সেগুলি সফলভাবে ব্যবহার করেছি। আমরা আশা করি আপনি এই লুকানো রত্নটি আবিষ্কার করবেন।

PS:আপনি কি লুকানো রত্ন বলে মনে করেন তা আমাদের জানান আমাদের পরবর্তীতে হাইলাইট করা উচিত!


  1. 7 দুর্দান্ত রুবি রত্ন বেশিরভাগ লোকেরা শুনেননি

  2. রুবি 2.6-এ 9টি নতুন বৈশিষ্ট্য

  3. আপনার রুবি প্রকল্পের জন্য সেরা রত্ন নির্বাচন করার জন্য একটি গাইড

  4. আমি রুবি 2.1 আপগ্রেড করা উচিত?