Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

DATA 및 __END__를 사용하여 Ruby의 코드 및 데이터 혼합

Ruby는 스크립트가 자체 소스 파일을 데이터 소스로 사용할 수 있는 방법을 제공한다는 사실을 알고 계셨습니까? 일회성 스크립트와 개념 증명을 작성할 때 시간을 절약할 수 있는 멋진 트릭입니다. 확인해 봅시다!

DATA 및 END

아래 예에서는 __END__라는 재미있는 키워드를 사용하고 있습니다. . __END__ 아래의 모든 항목 Ruby 인터프리터는 무시합니다. 하지만 더 흥미롭게도, ruby는 DATA라는 IO 객체를 제공합니다. , __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를 사용하는 방법을 알아냈을 것입니다. 여러 파일로.

그러나 Sinatra 소스를 조금 파헤쳐 보면 그들이 일종의 속임수임을 알 수 있습니다. 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__ 꽤 유용할 수 있습니다.