如何从Ruby HEREDOC中删除前导空格字符?

时间:2010-09-22 19:23:17

标签: ruby whitespace 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之间的所有空格。有没有人知道我在这里做错了什么?

11 个答案:

答案 0 :(得分:126)

<<-形式的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 literals documentation

  

最小缩进行的缩进将从每个缩进行中删除   内容的一行。请注意,空行和仅包含的行   为了达到目的,将忽略文字制表符和空格   确定缩进,但考虑转义标签和空格   非缩进字符。

答案 1 :(得分:122)

如果您使用的是Rails 3.0或更高版本,请尝试使用#strip_heredocThis example from the docs打印前三行没有缩进,同时保留最后两行&#39;双空缩进:

if options[:usage]
  puts <<-USAGE.strip_heredoc
    This command does such and such.
 
    Supported options are:
      -h         This message
      ...
  USAGE
end

文档还指出:&#34;从技术上讲,它在整个字符串中查找最少的缩进行,并删除前导空格的数量。&#34;

以下是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 :(得分:44)

没那么多,我知道我害怕。我通常会这样做:

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

此版本应该处理第一行不是最左边的行。

答案 3 :(得分:22)

这是我使用的一个更简单的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中删除换行符而不是前导空格。不可取!

答案 4 :(得分:7)

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

答案 5 :(得分:6)

其他一些答案找到最小缩进行的缩进级别,并从所有行中删除它,但考虑到编程中缩进的性质(第一行是最少缩进的),我认为你应该寻找第一行的缩进级别

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

答案 6 :(得分:2)

就像原版海报一样,我也发现了<<-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

答案 7 :(得分:1)

注意:正如@radiospiel所指出的那样,String#squish仅在ActiveSupport上下文中可用。


我相信 ruby​​&#39> 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

答案 8 :(得分:1)

另一个容易记住的选择是使用unindent gem

require 'unindent'

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

答案 9 :(得分:0)

我收集答案并得到了这个:

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范围。

答案 10 :(得分:0)

我需要使用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

默认行为是不剥离换行符,就像所有其他示例一样。