如何从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”之外,谁知道我在这里做错了什么?

heredoc的forms只是忽略了最后一个分隔符的主要空格。

在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 文档文档 :

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

如果您使用的是Rails 3.0或更高版本,请尝试#strip_heredoc 。 这个来自docs的例子打印了前三行,没有缩进,同时保留了最后两行的双空格缩进:

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

该文档还指出:“从技术上讲,它在整个string中查找最less的缩进行,并删除这些数量的前导空格。”

以下是来自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中findtesting。

没有太多的事情要做,我知道我害怕。 我通常做:

 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获取灵感,我会build议像这样的东西:

 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 

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

这里是我使用的非简单脚本的一个更简单的版本:

 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中删除换行符而不是前导空格。 不可取!

<<-在Ruby中只会忽略结尾分隔符的前导空格,允许它正确缩进。 它不会去掉string内部的空格,尽pipe网上有些文档可能会这样说。

您可以使用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 

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

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

就像原来的海报一样,我也发现了<<-HEREDOC语法”,并且非常失望,它没有像我认为应该performance的那样行事。

但是,而不是用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 

另一个容易记住的select是使用unindentgem

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

注意: @radiospiel指出, String#squish ActiveSupport仅在ActiveSupport上下文中可用。


我相信 ruby的 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 

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

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

我需要使用system东西,我可以跨行分割长期的命令,然后删除缩进和换行符…

 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 

默认行为是不去除换行符,就像所有其他的例子。