97

我正在尝试制作的 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 之间的所有空格。有人知道我在这里做错了什么吗?

4

11 回答 11

157

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文字文档

缩进最小的行的缩进将从内容的每一行中删除。请注意,为了确定缩进,将忽略空行和仅由文字制表符和空格组成的行,但转义的制表符和空格被视为非缩进字符。

于 2016-01-19T22:33:21.593 回答
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中找到测试。

于 2012-03-11T10:45:29.840 回答
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

当第一行也不是最左边的那一行时,这个版本应该可以处理。

于 2010-09-22T19:30:15.160 回答
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 中删除换行符而不是前导空格。不可取!

于 2011-04-12T15:58:23.700 回答
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
于 2010-09-22T19:35:06.017 回答
6

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

class String
  def unindent; gsub(/^#{match(/^\s+/)}/, "") end
end
于 2013-07-15T02:36:58.730 回答
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
于 2010-12-16T21:41:40.380 回答
3

注意:正如@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
于 2013-04-19T21:58:46.490 回答
2

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

require 'unindent'

p <<-end.unindent
    hello
      world
  end
# => "hello\n  world\n"  
于 2014-07-29T03:36:26.123 回答
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

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

于 2017-05-18T19:25:27.197 回答
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 范围。

于 2017-03-02T06:38:08.373 回答