Ruby HEREDOCから先頭の空白文字を削除するにはどうすればよいですか?


91

作成しようとしているRubyのヒアドキュメントに問題があります。すべての先頭の空白文字を抑制することになっている-演算子を含めても、各行から先頭の空白が返されます。私の方法は次のようになります:

    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

<<-ヒアドキュメントの形式では、終了区切り文字の先頭の空白のみが無視されます。

Ruby 2.3以降では、波線のヒアドキュメント(<<~)を使用して、コンテンツ行の先頭の空白を抑制できます。

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 2016年

1
@ChrisDrappierこれが可能かどうかはわかりませんが、この質問に対する受け入れられた回答をこの質問に変更することをお勧めします。
TheDeadSerious 2017年

123

Rails 3.0以降を使用している場合は、を試してください#strip_heredocこのドキュメントの例では、最初の3行をインデントなしで出力し、最後の2行の2スペースインデントは保持しています。

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 2012

3
iconoclastは正しいです。ちょうどrequire "active_support/core_ext/string"最初
デビッドJ.

2
Ruby 1.8.7では動作しないようです。Stringにtryは定義されていません。実際、それはレール固有の構成要素であるようです
Otheus '21

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 2012

1
確かに、EOFの動作は解析中に決定されるので、@ patconが示唆していることは、Ruby自体のソースコードを変更する必要があり、Rubyの他のバージョンではコードの動作が異なると思います。
einarmagnus 2012

2
RubyのダッシュのHEREDOC構文がbashでよりよく機能することを願っています。そうすれば、この問題は発生しません。(このbashの例を参照)
TrinitronX 2013年

プロのヒント:コンテンツの空白行でこれらのいずれかを試してから\s、改行が含まれていることを覚えておいてください。
Phrogz、2015年

ruby 2.2で試してみましたが、問題はありませんでした。どうしたの?(repl.it/B09p
einarmagnus

23

以下に、私が使用するインデント解除スクリプトのはるかに単純なバージョンを示します。

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]+空白を先頭に置く代わりに、ヒアドキュメントから改行が削除される可能性があることに注意してください。望ましくない!


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

7
@Phrogz OPは、「先行するすべての空白文字を抑制する」ことが期待されていると述べたので、それを行う答えと、彼が探していた場合に備えて、タブではなくスペースのみを取り除く答えを示しました。数か月後のことですが、OPに有効だった回答を反対票で投票し、独自の競合する回答を投稿するのはちょっと不自由です。
ブライアンキャンベル

@BrianCampbellあなたがそのように感じてすみません。犯罪は意図されていませんでした。私が自分の答えに投票するために反対票を投じていないと言っているとき、私を信じてくれるといいのですが、同様の機能を正直に検索してこの質問に出て、ここで答えが最適ではなかったからです。それはOPの正確なニーズを解決することは正しいですが、より多くの機能を提供する、もう少し一般的なソリューションも解決します。特に、改善を提供する場合、受け入れられた後に投稿された回答は、サイト全体にとって依然として価値があることに同意していただければ幸いです。
Phrogz

4
最後に、「競合する回答」というフレーズを取り上げたかったのです。あなたも私も競争すべきではありませんし、私たちがそうであるとは信じていません。(私たちがそうであるとしても、現時点であなたは27.4k担当者で勝利しています。)私たちは個人的に(OP)と匿名で(Google経由で到着する)問題のある個人を支援します。さらに(有効な)回答が役立ちます。その意味で、私は自分の反対票を再考します。あなたの答えは有害ではなく、誤解を招くものでも、過大評価されていなかったのもあなたです。私は、あなたから取り上げた2点の担当者に授与できるように、あなたの質問を編集しました。
Phrogz

1
@Phrogz不機嫌でごめんなさい。OPに適切に対処する回答に対する「私が嫌いなものは-1」の返信で問題が発生する傾向があります。すでに賛成または承認された回答があり、ほとんどではないが、あなたが望むことを行う場合、反対投票ではなく、コメントで回答の方が優れていると考える方法を明確にすることは、将来誰にとってもより役立つ傾向があります。はるか下に表示され、通常は問題のある他の人には表示されない別の回答を投稿します。答えが実際に間違っているか誤解を招く場合にのみ、反対票を投じます。
ブライアンキャンベル、

6

他のいくつかの回答は、インデントレベルを見つけます 、最もインデントされていない行それをすべての行から削除しますが、プログラミングのインデントの性質(最初の行が最もインデントされていない)を考慮すると、最初の行

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

1
PSST:最初の行が空白の場合はどうなりますか?
Phrogz、2015年

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

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

1
推測ではありますが、String#squishはおそらくRuby固有のものではなく、Railsの一部です。つまり、active_supportを使用しない限り機能しません。
radiospiel 2013

2

別の覚えやすいオプションは、インデントを解除する宝石を使用することです

require 'unindent'

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

2

私は何かを使用してsystem長いsedコマンドを行に分割し、インデントと改行を削除する必要がありました...

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.