Ruby HEREDOC에서 선행 공백 문자를 제거하려면 어떻게해야합니까?


91

내가 만들려고하는 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 사이의 모든 공백을 제외하고는이 특정 인스턴스에서 옳습니다. 내가 여기서 뭘 잘못하고 있는지 아는 사람이 있습니까?

답변:


143

<<-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 리터럴 문서에서 :

가장 적게 들여 쓰기 된 줄의 들여 쓰기가 콘텐츠의 각 줄에서 제거됩니다. 리터럴 탭과 공백으로 만 구성된 빈 줄과 줄은 들여 쓰기를 결정하기 위해 무시되지만 이스케이프 된 탭과 공백은 들여 쓰기가 아닌 문자로 간주됩니다.


11
나는 질문을 한 지 5 년이 지난 지금도 이것이 여전히 관련 주제라는 점을 좋아합니다. 업데이트 된 응답에 감사드립니다!
Chris Drappier

1
@ChrisDrappier 이것이 가능한지 확실하지 않지만 요즘은 이것이 분명히 해결책 이므로이 질문에 대한 허용 된 대답을이 질문으로 변경하는 것이 좋습니다.
TheDeadSerious

123

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

그리고 test / core_ext / string_ext_test.rb 에서 테스트를 찾을 수 있습니다 .


2
Rails 3 외부에서도 계속 사용할 수 있습니다!
iconoclast 2014 년

3
iconoclast가 정확합니다. 단지 require "active_support/core_ext/string"첫번째
데이비드 J.

2
루비 1.8.7에서 작동하지 않는 것 같습니다 : try문자열에 대해 정의되지 않았습니다. 사실, 그것은 레일 특정 구조가 보인다
Otheus

45

할 일이 많지 않아 두려워요. 나는 보통 :

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

작동하지만 약간의 해킹입니다.

편집 : 아래의 Rene Saarsoo에서 영감을 받아 대신 다음과 같은 것을 제안합니다.

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

이 버전은 첫 번째 줄이 가장 왼쪽에있는 줄이 아닐 때 처리해야합니다.


1
나는 묻는 것에 더러움을 느낍니다. 그러나 EOF단순히 기본 행동을 해킹하는 것이 아니라 스스로를 해킹하는 것은 String어떻습니까?
patcon

1
분명히 EOF의 동작은 구문 분석 중에 결정되므로 @patcon이 제안하는 내용은 Ruby 자체의 소스 코드를 변경하는 것과 관련이 있으며 코드는 다른 Ruby 버전에서 다르게 동작 할 것이라고 생각합니다.
einarmagnus

2
Ruby의 대시 HEREDOC 구문이 bash에서 더 많이 작동했으면 좋겠습니다. 그러면이 문제가 발생하지 않을 것입니다! ( 이 bash 예제 참조 )
TrinitronX

전문가 팁 : 내용에 빈 줄을 사용하여이 중 하나를 시도한 다음 새 줄이 \s포함되어 있음을 기억하십시오 .
Phrogz 2015 년

나는 루비 2.2에서 그것을 시도했지만 아무런 문제가 없었습니다. 당신에게 무슨 일이 일어 났습니까? ( repl.it/B09p )
einarmagnus 2015

23

내가 사용하는 unindent 스크립트의 훨씬 간단한 버전은 다음과 같습니다.

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

다음과 같이 사용하십시오.

foo = {
  bar: <<-ENDBAR.unindent
    My multiline
      and indented
        content here
    Yay!
  ENDBAR
}
#=> {:bar=>"My multiline\n  and indented\n    content here\nYay!"}

첫 번째 줄이 다른 줄보다 더 많이 들여 쓰기 될 수 있고 (Rails와 같이) 가장 적게 들여 쓰기 된 줄을 기준으로 들여 쓰기를 취소하려면 다음을 대신 사용할 수 있습니다.

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

\s+대신 스캔 [ \t]+하면 선행 공백 대신 heredoc에서 줄 바꿈이 제거 될 수 있습니다. 바람직하지 않습니다!


8

<<-Ruby에서는 끝 구분 기호의 선행 공백 만 무시하므로 적절하게 들여 쓰기 할 수 있습니다. 일부 온라인 문서에서 설명하는 내용에도 불구하고 문자열 내부 행의 선행 공백을 제거하지 않습니다.

다음을 사용하여 선행 공백을 직접 제거 할 수 있습니다 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

또는 공백 만 제거하려면 탭을 그대로 둡니다.

<<-EOF.gsub /^ */, ''
    \tSELECT
    \t CAST('#{name}' AS VARCHAR(30)) as COLUMN_NAME
    \t,COUNT(DISTINCT #{name}) AS DISTINCT_COUNT
    \tFROM #{table.call}
EOF

1
-1 들여 쓰기 양 대신 모든 선행 공백을 제거합니다.
Phrogz 2011-04-13

7
@Phrogz OP는 그가 "모든 선행 공백 문자를 억제"할 것으로 예상했다고 언급 했으므로 나는 그렇게 한 대답과 그가 찾고있는 경우 탭이 아닌 공백 만 제거하는 대답을 제공했습니다. 몇 달 후, OP에 효과가 있었던 답변을 반대 투표하고 자신의 경쟁 답변을 게시하는 것은 다소 절름발이입니다.
Brian Campbell

@BrianCampbell 그렇게 느끼 셨다니 유감입니다. 어떤 공격도 의도하지 않았습니다. 나는 내 답변에 대한 투표를 얻기 위해 투표하지 않는다고 말할 때 나를 믿기를 바랍니다.하지만 단순히 유사한 기능에 대한 정직한 검색을 통해이 질문에 왔고 여기에서 차선책을 찾았 기 때문입니다. OP의 정확한 요구를 해결하는 것이 맞지만 더 많은 기능을 제공하는 약간 더 일반적인 솔루션도 마찬가지입니다. 또한 답변이 수락 된 후 게시 된 답변이 특히 개선 사항을 제공하는 경우 사이트 전체에 여전히 가치가 있다는 데 동의하시기 바랍니다.
Phrogz 2011

4
마지막으로 "경쟁하는 대답"이라는 문구를 다루고 싶었습니다. 당신도 나도 경쟁을해서는 안되며 우리가 그렇다고 믿지도 않습니다. (그렇다면 현재 27.4k로 이기고 있습니다. :) 우리는 개인적으로 (OP) 또는 익명으로 (Google을 통해 도착하는 사람들) 문제가있는 개인을 돕습니다. 더 많은 (유효한) 답변이 도움이됩니다. 그런 맥락에서 나는 내 반대표를 재고합니다. 귀하의 답변이 해롭거나 오도하거나 과대 평가되지 않았다는 것이 맞습니다. 나는 이제 내가 당신에게서 빼앗은 2 점의 담당자를 부여 할 수 있도록 귀하의 질문을 편집했습니다.
Phrogz 2011

1
@Phrogz 심술 궂은 것에 대해 죄송합니다. 나는 OP를 적절하게 처리하는 답변에 대해 "내가 싫어하는 것에 대해-1"응답에 문제가있는 경향이 있습니다. 당신이 원하는 것을 거의하지만 거의하지 않는 찬성 또는 수락 된 답변이 이미있는 경우, 반대 투표보다는 댓글에서 답변이 더 나을 수 있다고 생각하는 방법을 명확히하는 것이 미래에 누구에게나 더 도움이되는 경향이 있습니다. 멀리 아래에 표시되고 일반적으로 문제가있는 다른 사람이 볼 수없는 별도의 답변을 게시합니다. 답변이 실제로 잘못되었거나 오해의 소지가있는 경우에만 반대 투표합니다.
Brian Campbell

6

다른 답변은 들여 쓰기가 가장 적은 줄 의 들여 쓰기 수준을 찾아 모든 줄에서 삭제하지만 프로그래밍의 들여 쓰기 특성 (첫 번째 줄이 가장 적게 들여 씁니다)을 고려할 때 들여 쓰기 수준을 찾아야한다고 생각합니다 . 첫 번째 줄 .

class String
  def unindent; gsub(/^#{match(/^\s+/)}/, "") end
end

1
Psst : 첫 번째 줄이 비어 있으면 어떻게합니까?
Phrogz

3

원본 포스터처럼 저도 <<-HEREDOC구문을 발견했고 제가 생각했던대로 동작하지 않는다는 사실에 상당히 실망했습니다.

그러나 gsub-s로 코드를 버리는 대신 String 클래스를 확장했습니다.

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

3
monkeypatch의 경우 +1이고 들여 쓰기 공백 만 제거하지만 지나치게 복잡한 구현의 경우 -1입니다.
Phrogz 2011

Phrogz에 동의, 이건 정말 최고의 대답은 개념이지만, 구현은 너무 복잡하다
einarmagnus

2

참고 : @radiospiel이 지적했듯이 컨텍스트 String#squish에서만 사용할 수 있습니다 ActiveSupport.


나는 믿는다 루비 String#squish 실제로 찾고있는 것에 더 가깝습니다.

다음은 귀하의 예를 처리하는 방법입니다.

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

반대표를 보내 주셔서 감사합니다.하지만이 솔루션을 피해야하는 이유를 설명하는 의견이 있으면 더 나은 혜택을받을 수있을 것입니다.
Marius Butuc 2013

1
추측 일 뿐이지 만 String # squish는 루비 고유의 일부가 아니라 Rails의 일부입니다. 즉, active_support를 사용하지 않으면 작동하지 않습니다.
radiospiel 2013-07-19

2

기억하기 쉬운 또 다른 옵션은 unindent gem을 사용하는 것입니다.

require 'unindent'

p <<-end.unindent
    hello
      world
  end
# => "hello\n  world\n"  

2

systemsed명령을 여러 줄로 나눈 다음 들여 쓰기와 줄 바꿈을 제거 할 수 있는 무언가를 사용해야했습니다 .

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

그래서 나는 이것을 생각해 냈습니다.

class ::String
  def strip_heredoc(compress = false)
    stripped = gsub(/^#{scan(/^\s*/).min_by(&:length)}/, "")
    compress ? stripped.gsub(/\n/," ").chop : stripped
  end
end

기본 동작은 다른 모든 예제와 마찬가지로 개행 문자를 제거하지 않는 것입니다.


1

나는 답변을 수집하고 이것을 얻었습니다.

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

훌륭한 SQL을 생성하고 AR 범위를 벗어나지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.