Как удалить ведущие белые символы из 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". Кто-нибудь знает, что я здесь делаю неправильно?

Ответ 1

Форма <<- heredoc только игнорирует ведущие пробелы для конечного разделителя.

С Ruby 2.3 и более поздними версиями вы можете использовать squiggly 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 :

Отступ строки с наименьшим отступом будет удален из каждого строка содержимого. Обратите внимание, что пустые строки и строки, состоящие исключительно из литеральных вкладок и пробелов будут проигнорированы для целей определение отступа, но избегаемые вкладки и пробелы считаются неизображенные символы.

Ответ 2

Если вы используете 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.

Ответ 3

Не так много, что я знаю, я боюсь. Обычно я делаю:

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

Это работает, но немного взломан.

EDIT: Взяв вдохновение от Рене Саарсоо ниже, я бы предложил нечто подобное:

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

Эта версия должна обрабатываться, если первая строка не является одной из самых дальних слева.

Ответ 4

Здесь гораздо более простая версия unindent script, которую я использую:

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) сделать unindent на основе строки с наименьшим отступом, вы можете вместо этого использовать:

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 вместо того, чтобы вести пробелы. Не желательно!

Ответ 5

<<- в 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

Ответ 6

В некоторых других ответах найдите уровень отступа наименьшей отступаемой строки и удалите его со всех строк, но учитывая характер отступа в программировании (первая строка является наименьшим отступом), я подумайте, что вы должны искать уровень отступа в первой строке.

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

Ответ 7

Как и оригинальный плакат, я тоже обнаружил синтаксис <<-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

Ответ 8

еще один удобный для запоминания вариант - использовать unindent gem

require 'unindent'

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

Ответ 9

Примечание.. Как отметил @radiospiel, String#squish доступен только в контексте ActiveSupport.


Я верю ruby's 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

Ответ 10

Я собираю ответы и получаю следующее:

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.

Ответ 11

Мне нужно было что-то использовать с 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

Поведение по умолчанию - это не переносить строки новой строки, как и все другие примеры.