Как удалить ведущие пробелы, символы с Рубином помощи 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

и мой вывод выглядит следующим образом:

    => "            tSELECTn            t CAST('SRC_ACCT_NUM' AS VARCHAR(30)) as
COLUMN_NAMEn            t,COUNT(DISTINCT SRC_ACCT_NUM) AS DISTINCT_COUNTn
        tFROM UD461.MGMT_REPORT_HNBn"

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

11 74

11 ответов:

The <<- форма 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"

от рубинового литералы документации:

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

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

class String
  def strip_heredoc
    indent = scan(/^[ \t]*(?=\S)/).min.try(:size) || 0
    gsub(/^[ \t]{#{indent}}/, '')
  end
end

и вы можете найти тесты test/core_ext / string_ext_test.РБ.

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

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

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

изменить: Вдохновляясь Рене Саарсу ниже, я бы предложил что-то вроде этого:

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!"}

если первая строка может быть отступом больше, чем другие, и хотите (например, рельсы), чтобы 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 вместо ведущих пробелов. Не желательно!

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

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

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

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

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

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

require 'unindent'

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

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

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 в результате чего я мог бы разделить долго 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

поведение по умолчанию заключается в том, чтобы не удалять новые строки, как и все другие примеры.