Ruby HEREDOC에서 선행 공백 문자를 제거하려면 어떻게해야합니까?
내가 만들려고하는 Ruby heredoc에 문제가 있습니다. 모든 선행 공백 문자를 억제하는-연산자를 포함하더라도 각 줄에서 선행 공백을 반환합니다. 내 방법은 다음과 같습니다.
def distinct_count
<<-EOF
\tSELECT
\t CAST('#{name}' AS VARCHAR(30)) as COLUMN_NAME
\t,COUNT(DISTINCT #{name}) AS DISTINCT_COUNT
\tFROM #{table.call}
EOF
end
내 출력은 다음과 같습니다.
=> " \tSELECT\n \t CAST('SRC_ACCT_NUM' AS VARCHAR(30)) as
COLUMN_NAME\n \t,COUNT(DISTINCT SRC_ACCT_NUM) AS DISTINCT_COUNT\n
\tFROM UD461.MGMT_REPORT_HNB\n"
물론 이것은 첫 번째 "와 \ t 사이의 모든 공백을 제외하고는이 특정 인스턴스에서 옳습니다. 내가 여기서 뭘 잘못하고 있는지 아는 사람이 있습니까?
<<-
heredoc 의 형식은 끝 구분 기호의 선행 공백 만 무시합니다.
Ruby 2.3 이상에서는 구불 구불 한 heredoc ( <<~
)을 사용하여 콘텐츠 행의 선행 공백을 억제 할 수 있습니다 .
def test
<<~END
First content line.
Two spaces here.
No space here.
END
end
test
# => "First content line.\n Two spaces here.\nNo space here.\n"
Ruby 리터럴 문서에서 :
가장 적게 들여 쓰기 된 줄의 들여 쓰기가 콘텐츠의 각 줄에서 제거됩니다. 리터럴 탭과 공백으로 만 구성된 빈 줄과 줄은 들여 쓰기를 결정하기 위해 무시되지만 이스케이프 된 탭과 공백은 들여 쓰기가 아닌 문자로 간주됩니다.
Rails 3.0 이상을 사용하는 경우 #strip_heredoc
. 문서의이 예제는 들여 쓰기없이 처음 세 줄을 인쇄하고 마지막 두 줄의 두 칸 들여 쓰기를 유지합니다.
if options[:usage]
puts <<-USAGE.strip_heredoc
This command does such and such.
Supported options are:
-h This message
...
USAGE
end
문서는 또한 "기술적으로는 전체 문자열에서 들여 쓰기가 가장 적은 줄을 찾고 앞의 공백을 제거합니다."라고 설명합니다.
다음은 active_support / core_ext / string / strip.rb 의 구현입니다 .
class String
def strip_heredoc
indent = scan(/^[ \t]*(?=\S)/).min.try(:size) || 0
gsub(/^[ \t]{#{indent}}/, '')
end
end
And you can find the tests in test/core_ext/string_ext_test.rb.
Not much to do that I know of I'm afraid. I usually do:
def distinct_count
<<-EOF.gsub /^\s+/, ""
\tSELECT
\t CAST('#{name}' AS VARCHAR(30)) as COLUMN_NAME
\t,COUNT(DISTINCT #{name}) AS DISTINCT_COUNT
\tFROM #{table.call}
EOF
end
That works but is a bit of a hack.
EDIT: Taking inspiration from Rene Saarsoo below, I'd suggest something like this instead:
class String
def unindent
gsub(/^#{scan(/^\s*/).min_by{|l|l.length}}/, "")
end
end
def distinct_count
<<-EOF.unindent
\tSELECT
\t CAST('#{name}' AS VARCHAR(30)) as COLUMN_NAME
\t,COUNT(DISTINCT #{name}) AS DISTINCT_COUNT
\tFROM #{table.call}
EOF
end
This version should handle when the first line is not the one farthest to the left too.
Here's a far simpler version of the unindent script that I use:
class String
# Strip leading whitespace from each line that is the same as the
# amount of whitespace on the first line of the string.
# Leaves _additional_ indentation on later lines intact.
def unindent
gsub /^#{self[/\A[ \t]*/]}/, ''
end
end
Use it like so:
foo = {
bar: <<-ENDBAR.unindent
My multiline
and indented
content here
Yay!
ENDBAR
}
#=> {:bar=>"My multiline\n and indented\n content here\nYay!"}
If the first line may be indented more than others, and want (like Rails) to unindent based on the least-indented line, you may instead wish to use:
class String
# Strip leading whitespace from each line that is the same as the
# amount of whitespace on the least-indented line of the string.
def strip_indent
if mindent=scan(/^[ \t]+/).min_by(&:length)
gsub /^#{mindent}/, ''
end
end
end
Note that if you scan for \s+
instead of [ \t]+
you may end up stripping newlines from your heredoc instead of leading whitespace. Not desirable!
<<-
in Ruby will only ignore leading space for the ending delimiter, allowing it to be properly indented. It does not strip leading space on lines inside the string, despite what some documentation online might say.
You can strip leading whitespace yourself by using gsub
:
<<-EOF.gsub /^\s*/, ''
\tSELECT
\t CAST('#{name}' AS VARCHAR(30)) as COLUMN_NAME
\t,COUNT(DISTINCT #{name}) AS DISTINCT_COUNT
\tFROM #{table.call}
EOF
Or if you just want to strip spaces, leaving the tabs:
<<-EOF.gsub /^ */, ''
\tSELECT
\t CAST('#{name}' AS VARCHAR(30)) as COLUMN_NAME
\t,COUNT(DISTINCT #{name}) AS DISTINCT_COUNT
\tFROM #{table.call}
EOF
Some other answers find the indentation level of the least indented line, and delete that from all lines, but considering the nature of indentation in programming (that the first line is the least indented), I think you should look for the indentation level of the first line.
class String
def unindent; gsub(/^#{match(/^\s+/)}/, "") end
end
Like the original poster, I too discovered the <<-HEREDOC
syntax and was pretty damn disappointed that it didn't behave as I thought it should behave.
But instead of littering my code with gsub-s I extended the String class:
class String
# Removes beginning-whitespace from each line of a string.
# But only as many whitespace as the first line has.
#
# Ment to be used with heredoc strings like so:
#
# text = <<-EOS.unindent
# This line has no indentation
# This line has 2 spaces of indentation
# This line is also not indented
# EOS
#
def unindent
lines = []
each_line {|ln| lines << ln }
first_line_ws = lines[0].match(/^\s+/)[0]
re = Regexp.new('^\s{0,' + first_line_ws.length.to_s + '}')
lines.collect {|line| line.sub(re, "") }.join
end
end
Note: As @radiospiel pointed out, String#squish
is only available in the ActiveSupport
context.
I believe ruby's String#squish
is closer to what you're really looking for:
Here is how I would handle your example:
def distinct_count
<<-SQL.squish
SELECT
CAST('#{name}' AS VARCHAR(30)) as COLUMN_NAME,
COUNT(DISTINCT #{name}) AS DISTINCT_COUNT
FROM #{table.call}
SQL
end
another easy to remember option is to use unindent gem
require 'unindent'
p <<-end.unindent
hello
world
end
# => "hello\n world\n"
I needed to use something with system
whereby I could split long sed
commands across lines and then remove indentation AND newlines...
def update_makefile(build_path, version, sha1)
system <<-CMD.strip_heredoc(true)
\\sed -i".bak"
-e "s/GIT_VERSION[\ ]*:=.*/GIT_VERSION := 20171-2342/g"
-e "s/GIT_VERSION_SHA1[\ ]:=.*/GIT_VERSION_SHA1 := 2342/g"
"/tmp/Makefile"
CMD
end
So I came up with this:
class ::String
def strip_heredoc(compress = false)
stripped = gsub(/^#{scan(/^\s*/).min_by(&:length)}/, "")
compress ? stripped.gsub(/\n/," ").chop : stripped
end
end
Default behavior is to not strip newlines, just like all the other examples.
I collect answers and got this:
class Match < ActiveRecord::Base
has_one :invitation
scope :upcoming, -> do
joins(:invitation)
.where(<<-SQL_QUERY.strip_heredoc, Date.current, Date.current).order('invitations.date ASC')
CASE WHEN invitations.autogenerated_for_round IS NULL THEN invitations.date >= ?
ELSE (invitations.round_end_time >= ? AND match_plays.winner_id IS NULL) END
SQL_QUERY
end
end
It generates excellent SQL and do not go out of AR scopes.
'Programing' 카테고리의 다른 글
org.hibernate.proxy.pojo.javassist.Javassist 클래스에 대한 시리얼 라이저가 없습니까? (0) | 2020.09.22 |
---|---|
SQL Server 2005의 날짜 / 시간에서 월 및 연도 가져 오기 (0) | 2020.09.22 |
Xcode 10, Command CodeSign이 0이 아닌 종료 코드로 실패했습니다. (0) | 2020.09.22 |
jquery : 요소에 특정 CSS 클래스 / 스타일이 있는지 확인하는 방법 (0) | 2020.09.22 |
소수점 이하 두 자리 만 표시 (0) | 2020.09.22 |