webentwicklung-frage-antwort-db.com.de

Wie entferne ich führende Whitespace-Zeichen aus Ruby HEREDOC?

Ich habe ein Problem mit einem Ruby heredoc, das ich zu erstellen versuche. Es gibt das führende Leerzeichen aus jeder Zeile zurück, obwohl ich den Operator - einbeziehe, der alle unterdrücken soll führende Leerzeichen. meine Methode sieht so aus:

    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

und meine Ausgabe sieht so aus:

    => "            \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"

das ist natürlich in diesem speziellen Fall richtig, mit Ausnahme aller Leerzeichen zwischen dem ersten "und\t. Weiß jemand, was ich hier falsch mache?

82
Chris Drappier

Das <<- form of heredoc ignoriert nur führende Leerzeichen für den Endbegrenzer.

Mit Ruby 2.3 und höher können Sie einen schnörkellosen Heredoc (<<~) um das führende Leerzeichen von Inhaltszeilen zu unterdrücken:

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"

Aus der Ruby Literale Dokumentation :

Der Einzug der am wenigsten eingerückten Zeile wird aus jeder Zeile des Inhalts entfernt. Beachten Sie, dass leere Zeilen und Zeilen, die nur aus wörtlichen Tabulatoren und Leerzeichen bestehen, zum Ermitteln des Einzugs ignoriert werden. Escape-Tabulatoren und Leerzeichen gelten jedoch als Nicht-Einzugszeichen.

128
Phil Ross

Wenn Sie Rails 3.0 oder neuer verwenden, versuchen Sie #strip_heredoc. In diesem Beispiel aus den Dokumenten druckt die ersten drei Zeilen ohne Einrückung, wobei die zwei Einrückungen der letzten beiden Zeilen beibehalten werden:

if options[:usage]
  puts <<-USAGE.strip_heredoc
    This command does such and such.
 
    Supported options are:
      -h         This message
      ...
  USAGE
end

In der Dokumentation wird außerdem Folgendes angegeben: "Technisch wird in der gesamten Zeichenfolge nach der am wenigsten eingerückten Zeile gesucht und das führende Leerzeichen entfernt."

Hier ist die Implementierung von 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

Die Tests finden Sie in test/core_ext/string_ext_test.rb .

122
chrisk

Nicht viel zu tun, von dem ich weiß, dass ich Angst habe. Normalerweise mache ich:

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

Das funktioniert, ist aber ein bisschen hacken.

EDIT: Inspiriert von Rene Saarsoo, würde ich stattdessen Folgendes vorschlagen:

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

Diese Version sollte funktionieren, wenn die erste Zeile nicht auch die am weitesten links stehende ist.

44
einarmagnus

Hier ist eine viel einfachere Version des nicht einbezogenen Skripts, das ich verwende:

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

Benutze es so:

foo = {
  bar: <<-ENDBAR.unindent
    My multiline
      and indented
        content here
    Yay!
  ENDBAR
}
#=> {:bar=>"My multiline\n  and indented\n    content here\nYay!"}

Wenn die erste Zeile stärker eingerückt sein kann als andere und (wie Rails) die Einrückung auf der Grundlage der am wenigsten eingerückten Zeile aufheben soll, möchten Sie möglicherweise stattdessen Folgendes verwenden:

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

Beachten Sie, dass beim Scannen nach \s+ Anstatt von [ \t]+ Möglicherweise werden Zeilenumbrüche von Ihrem Heredoc entfernt, anstatt Leerzeichen zu setzen. Nicht wünschenswert!

22
Phrogz

<<- in Ruby ignoriert nur das führende Leerzeichen für das abschließende Trennzeichen, sodass es ordnungsgemäß eingerückt wird. Es werden keine führenden Leerzeichen in Zeilen innerhalb der Zeichenfolge entfernt, obwohl dies in einigen Online-Dokumentationen angegeben ist.

Sie können führende Leerzeichen selbst entfernen, indem Sie gsub verwenden:

<<-EOF.gsub /^\s*/, ''
    \tSELECT
    \t CAST('#{name}' AS VARCHAR(30)) as COLUMN_NAME
    \t,COUNT(DISTINCT #{name}) AS DISTINCT_COUNT
    \tFROM #{table.call}
EOF

Oder wenn Sie nur Leerzeichen entfernen möchten und die Registerkarten belassen:

<<-EOF.gsub /^ */, ''
    \tSELECT
    \t CAST('#{name}' AS VARCHAR(30)) as COLUMN_NAME
    \t,COUNT(DISTINCT #{name}) AS DISTINCT_COUNT
    \tFROM #{table.call}
EOF
7
Brian Campbell

Einige andere Antworten finden die Einrückungsstufe von die am wenigsten eingerückte Zeile und löschen diese aus allen Zeilen. Unter Berücksichtigung der Art der Einrückung bei der Programmierung (die erste Zeile ist die am wenigsten eingerückte Zeile) sollten Sie dies jedoch tun Suchen Sie nach der Einrückungsstufe von die erste Zeile.

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

Wie das Originalplakat entdeckte auch ich das <<-HEREDOC syntax und war ziemlich enttäuscht, dass es sich nicht so verhalten hat, wie ich es mir vorgestellt habe.

Aber anstatt meinen Code mit gsub-s zu verunreinigen, habe ich die String-Klasse erweitert:

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
2
Rene Saarsoo

eine andere, leicht zu merkende Option ist die Verwendung von Edelsteinen

require 'unindent'

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

Hinweis: Wie @radiospiel hervorhob, String#squish ist nur im Kontext ActiveSupport verfügbar.


Ich glaube Ruby's String#squish ist näher an dem, wonach Sie wirklich suchen:

So würde ich mit Ihrem Beispiel umgehen:

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
1
Marius Butuc

Ich musste etwas mit system verwenden, wobei ich lange sed Befehle über Zeilen aufteilen und dann Einrückungen UND Zeilenumbrüche entfernen konnte ...

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

Also habe ich mir Folgendes ausgedacht:

class ::String
  def strip_heredoc(compress = false)
    stripped = gsub(/^#{scan(/^\s*/).min_by(&:length)}/, "")
    compress ? stripped.gsub(/\n/," ").chop : stripped
  end
end

Standardmäßig werden keine Zeilenumbrüche entfernt, wie in allen anderen Beispielen.

1
markeissler

Ich sammle Antworten und bekam diese:

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

Es generiert exzellentes SQL und verlässt nicht den AR-Bereich.

0
Aivils Štoss