web-dev-qa-db-ja.com

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の間のすべてのスペースを除いて、この特定のインスタンスで正しいです。誰かが私がここで間違っていることを知っていますか?

82
Chris Drappier

<<-形式のheredocは、終了区切り文字の先頭の空白のみを無視します。

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 literals documentation から:

最もインデントされていない行のインデントは、コンテンツの各行から削除されます。空行とリテラルのタブとスペースのみで構成される行は、インデントを決定するために無視されますが、エスケープされたタブとスペースはインデントされていない文字と見なされます。

128
Phil Ross

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

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 にあります。

122
chrisk

私が恐れていることを知っていることはあまりありません。私は通常:

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

それは機能しますが、ちょっとしたハックです。

編集:以下のルネ・ザールスからインスピレーションを得て、代わりに次のようなものを提案します。

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

このバージョンは、最初の行も左端にない場合に処理する必要があります。

44
einarmagnus

ここに、私が使用する字下げしないスクリプトのはるかに単純なバージョンを示します。

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

22
Phrogz

<<- in 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
7
Brian Campbell

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

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

元のポスターと同様に、私も<<-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
2
Rene Saarsoo

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

require 'unindent'

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

注:@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
1
Marius Butuc

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
markeissler

私は答えを集めてこれを得ました:

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スコープから外に出ません。

0
Aivils Štoss