如何从Ruby HEREDOC删除主要的空白字符?


91

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

Answers:


143

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

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


11
我爱问这个问题五年后,这仍然是一个相关主题。感谢您的最新回复!
克里斯·德拉皮尔

1
@ChrisDrappier不确定是否可行,但我建议将对此问题的可接受答案更改为该答案,因为如今这显然是解决方案。
TheDeadSerious

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中找到测试。


2
您仍然可以在Rails 3之外使用它!
iconoclast 2012年

3
iconoclast是正确的;刚require "active_support/core_ext/string"开始
David J.

2
在ruby 1.8.7中似乎不起作用:try未为String定义。实际上,这似乎是特定于Rails的构造
Otheus

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

专家提示:尝试使用内容中的空行来进行操作,然后记住其中\s包括换行符。
Phrogz

我在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]+则最终可能会从Heredoc中删除换行符,而不是前导空格。不可取!


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的答案并发布您自己的竞争性答案有点kind脚。
布莱恩·坎贝尔

@BrianCampbell对不起,您有这种感觉;无意冒犯。我希望您能相信我,当我说我并不是为赢得自己的答案而投票时,而是因为我是通过诚实地寻找类似功能来找到这个问题的,并在这里找到了次优的答案。没错,它可以解决OP的确切需求,但是提供更多功能的更为通用的解决方案也可以。我也希望您同意,接受一个答案后发布的答案对于整个站点仍然是有价值的,特别是如果它们提供了改进的话。
Phrogz

4
最后,我想谈一谈“竞争性答案”。您和我都不应该参加比赛,我也不认为我们参加了比赛。(尽管我们做到了,但截至目前,您已经赢得了27.4千名代表。:)我们可以帮助有问题的个人,无论是个人(OP)还是匿名(通过Google到达)。更多(有效)答案帮助。因此,我重新考虑了我的反对意见。您的回答没有任何危害,误导或高估,这是正确的。现在,我已经编辑了您的问题,以便向您赠送我代表的2分。
Phrogz

1
@Phrogz对不起,脾气暴躁;对于“ -1,表示我不喜欢的内容”,我倾向于有一个问题,这些问题的答案足以解决OP。当已经有投票赞成或接受的答案几乎但不是完全按照您想要的去做时,对于将来的任何人来说,只要在注释中阐明您认为答案可能会更好,往往会比不赞成或不赞成就更有用了。发布一个单独的答案,该答案将显示在下方,并且其他有问题的人通常不会看到。如果答案确实是错误的或误导性的,我只会投反对票。
Brian Campbell

6

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

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

1
附:如果第一行是空白怎么办?
Phrogz

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
+1表示猴子补丁,仅去除缩进的空格,而-1表示过于复杂的实现。
Phrogz

同意Phrogz的观点,这确实是最好的答案,但是实现起来太复杂了
einarmagnus 2012年

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可能不是红宝石的一部分,而是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范围。


这很难读。
塞巴斯蒂安·帕尔马
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.