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