Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

ผสมรหัสและข้อมูลใน Ruby กับ DATA และ __END__

คุณรู้หรือไม่ว่า Ruby จัดเตรียมวิธีให้สคริปต์ของคุณใช้ไฟล์ต้นฉบับของตัวเองเป็นแหล่งข้อมูล เป็นกลอุบายที่จะช่วยคุณประหยัดเวลาในการเขียนสคริปต์แบบใช้ครั้งเดียวและการพิสูจน์แนวคิด มาดูกันเลย!

ข้อมูลและ END

ในตัวอย่างด้านล่าง ฉันใช้คำหลักตลกๆ ชื่อ __END__ . ทุกอย่างด้านล่าง __END__ จะถูกละเว้นโดยล่าม Ruby แต่ที่น่าสนใจกว่านั้น ruby ​​ให้คุณมีอ็อบเจ็กต์ IO ชื่อ DATA ซึ่งให้คุณอ่านทุกอย่างด้านล่าง __END__ เหมือนกับที่คุณอ่านจากไฟล์อื่นๆ

ในตัวอย่างต่อไปนี้ เราวนซ้ำในแต่ละบรรทัดแล้วพิมพ์ออกมา

DATA.each_line do |line|
  puts line
end

__END__
Doom
Quake
Diablo

ตัวอย่างการปฏิบัติที่ฉันชอบของเทคนิคนี้ใช้ DATA เพื่อให้มีเทมเพลต ERB นอกจากนี้ยังใช้งานได้กับ YAML, CSV และอื่นๆ ม

require 'erb'

time = Time.now
renderer = ERB.new(DATA.read)
puts renderer.result()

__END__
The current time is <%= time %>.

คุณสามารถใช้ DATA . ได้จริง เพื่ออ่านเนื้อหาด้านบน __END__ คำสำคัญ. นั่นเป็นเพราะ DATA ที่จริงแล้วเป็นตัวชี้ไปยังไฟล์ต้นฉบับทั้งหมด กรอไปข้างหน้าอย่างรวดเร็วไปยัง __END__ คำสำคัญ. คุณสามารถดูสิ่งนี้ได้หากคุณกรอกลับออบเจกต์ IO ก่อนพิมพ์ ตัวอย่างด้านล่างจะพิมพ์ไฟล์ต้นฉบับทั้งหมดออกมา

DATA.rewind
puts DATA.read # prints the entire source file

__END__
meh

ปัญหาหลายไฟล์

ข้อเสียที่สำคัญอย่างหนึ่งของเทคนิคนี้คือใช้งานได้จริงก็ต่อเมื่อสคริปต์ของคุณพอดีกับไฟล์ต้นฉบับไฟล์เดียว และคุณกำลังเรียกใช้ไฟล์นั้นโดยตรง แทนที่จะรวมไว้ด้วย

ในตัวอย่างด้านล่าง ฉันมีไฟล์สองไฟล์ โดยแต่ละไฟล์มี __END__ . ของตัวเอง ส่วน. อย่างไรก็ตาม มี DATA . ได้เพียงตัวเดียวเท่านั้น ทั่วโลก. ดังนั้น __END__ ไม่สามารถเข้าถึงส่วนของไฟล์ที่สองได้

# first.rb
require "./second"

puts "First file\n----------------------"
puts DATA.read

print_second_data()

__END__
First end clause

# second.rb

def print_second_data
  puts "Second file\n----------------------"
  puts DATA.read # Won't output anything, since first.rb read the entire file
end

__END__

Second end clause

snhorne ~/tmp $ ruby first.rb
First file
----------------------
First end clause

Second file
----------------------

วิธีแก้ปัญหาสำหรับหลายไฟล์

Sinatra มีฟีเจอร์เจ๋งๆ ที่ช่วยให้คุณสามารถเพิ่มเทมเพลตอินไลน์หลายอันลงในแอปของคุณโดยวางไว้หลัง __END__ คำแถลง. ดูเหมือนว่านี้:

# This code is from the Sinatra docs at https://www.sinatrarb.com/intro.html
require 'sinatra'

get '/' do
  haml :index
end

__END__

@@ layout
%html
  = yield

@@ index
%div.title Hello world.

แต่ซินาตร้าทำสิ่งนี้ได้อย่างไร? ท้ายที่สุด แอปของคุณน่าจะโหลดโดยแร็ค คุณจะไม่เรียกใช้ ruby myapp.rb ในการผลิต! พวกเขาต้องคิดหาวิธีใช้ DATA ที่มีหลายไฟล์

แม้ว่าหากคุณเจาะลึกลงไปในแหล่งซินาตราเพียงเล็กน้อย คุณจะเห็นว่าพวกเขากำลังนอกใจ พวกเขาไม่ได้ใช้ DATA เลย แต่พวกเขากำลังทำสิ่งที่คล้ายกับโค้ดด้านล่างแทน

# I'm paraphrasing. See the original at https://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1284
app, data = File.read(__FILE__).split(/^__END__$/, 2)

จริงๆแล้วมันซับซ้อนกว่าเล็กน้อยเพราะพวกเขาไม่ต้องการอ่าน __FILE__ . นั่นก็เป็นเพียงไฟล์ sinatra/base.rb พวกเขาต้องการรับเนื้อหาของไฟล์ที่เรียกใช้ฟังก์ชันแทน พวกเขาได้รับสิ่งนี้โดยแยกวิเคราะห์ผลลัพธ์ของผู้โทร

ฟังก์ชันผู้โทรจะบอกคุณว่าฟังก์ชันที่กำลังทำงานอยู่ถูกเรียกใช้ที่ใด ต่อไปนี้คือตัวอย่างสั้นๆ:

def some_method
  puts caller
end

some_method # => caller.rb:5:in `<main>'

ตอนนี้ เป็นเรื่องง่ายมากที่จะดึงชื่อไฟล์ออกจากที่นั่น และดึงข้อมูลบางอย่างที่เทียบเท่ากับ DATA สำหรับไฟล์นั้น

def get_caller_data
  puts File.read(caller.first.split(":").first).split("__END__", 2).last
end

ใช้เพื่อความดี ไม่ใช่ความชั่ว

หวังว่าจะเห็นได้ชัดว่าลูกเล่นเหล่านี้ไม่ใช่สิ่งที่คุณต้องการใช้ทุกวัน ไม่ได้สร้างฐานโค้ดขนาดใหญ่ที่สะอาดและบำรุงรักษาได้อย่างแน่นอน

อย่างไรก็ตาม ในบางครั้งคุณต้องการบางสิ่งที่รวดเร็วและสกปรก ไม่ว่าจะเป็นสคริปต์ยูทิลิตี้แบบใช้ครั้งเดียวหรือเพื่อพิสูจน์แนวคิด ในกรณีนั้น DATA และ __END__ มีประโยชน์มาก