Как мне удалить ведущие пробельные символы из Ruby HEREDOC? - PullRequest
81 голосов
/ 22 сентября 2010

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

    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"

это, конечно, правильно в данном конкретном случае, за исключением всех пробелов междупервый "и \ т. кто-нибудь знает, что я здесь не так делаю?

Ответы [ 11 ]

127 голосов
/ 20 января 2016

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

В Ruby 2.3 и более поздних версиях вы можете использовать волнистый наследственный ключ (<<~) для подавления ведущего пробела строк содержимого:

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 literal :

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

122 голосов
/ 11 марта 2012

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

44 голосов
/ 22 сентября 2010

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

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

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

22 голосов
/ 12 апреля 2011

Вот гораздо более простая версия сценария неиспользования, который я использую:

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

7 голосов
/ 22 сентября 2010

<<- в 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 голосов
/ 15 июля 2013

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

class String
  def unindent; gsub(/^#{match(/^\s+/)}/, "") end
end
2 голосов
/ 17 декабря 2010

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

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

Еще один простой для запоминания вариант - использовать неопределяемый гем

require 'unindent'

p <<-end.unindent
    hello
      world
  end
# => "hello\n  world\n"  
1 голос
/ 20 апреля 2013

Примечание: Как указал @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
...