web-dev-qa-db-fra.com

Comment supprimer les principaux caractères d'espaces blancs de Ruby HEREDOC?

J'ai un problème avec un Ruby heredoc que j'essaie de créer. Il renvoie le premier espace blanc de chaque ligne même si j'inclus l'opérateur -, qui est censé supprimer tout caractères blancs en tête. Ma méthode ressemble à ceci:

    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

et ma sortie ressemble à ceci:

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

cela, bien sûr, est vrai dans ce cas spécifique, à l'exception de tous les espaces entre le premier "et\t. Quelqu'un sait-il ce que je fais mal ici?

82
Chris Drappier

Le <<- la forme de heredoc ignore uniquement les espaces de début pour le délimiteur de fin.

Avec Ruby 2.3 et versions ultérieures, vous pouvez utiliser un hérédoc squiggly (<<~) pour supprimer le premier espace des lignes de contenu:

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"

Depuis le Ruby documentation littérale :

L'indentation de la ligne la moins indentée sera supprimée de chaque ligne du contenu. Notez que les lignes vides et les lignes constituées uniquement de tabulations et d'espaces littéraux seront ignorées pour déterminer l'indentation, mais les tabulations et les espaces échappés sont considérés comme des caractères sans indentation.

128
Phil Ross

Si vous utilisez Rails 3.0 ou plus récent, essayez #strip_heredoc. Cet exemple de la documentation imprime les trois premières lignes sans retrait, tout en conservant le retrait à deux espaces des deux dernières lignes:

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

La documentation note également: "Techniquement, il recherche la ligne la moins indentée dans la chaîne entière et supprime cette quantité d'espace blanc de début".

Voici l'implémentation de 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

Et vous pouvez trouver les tests dans test/core_ext/string_ext_test.rb .

122
chrisk

Pas grand chose à faire que je sache, j'ai peur. Je fais habituellement:

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

Cela fonctionne, mais c'est un peu un hack.

EDIT: En s'inspirant de René Saarsoo ci-dessous, je suggère plutôt quelque chose comme ceci:

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

Cette version devrait gérer quand la première ligne n'est pas la plus à gauche aussi.

44
einarmagnus

Voici une version beaucoup plus simple du script non indenté que j'utilise:

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

Utilisez-le comme ceci:

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

Si la première ligne peut être plus indentée que les autres, et que vous souhaitez (comme Rails) désindenter en fonction de la ligne la moins indentée, vous pouvez utiliser à la place:

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

Notez que si vous recherchez \s+ au lieu de [ \t]+ vous pouvez finir par supprimer les sauts de ligne de votre hérédoc au lieu de laisser des espaces blancs. Pas souhaitable!

22
Phrogz

<<- in Ruby ignorera uniquement l'espace de début pour le délimiteur de fin, ce qui lui permettra d'être correctement mis en retrait. Il ne supprime pas l'espace de début sur les lignes à l'intérieur de la chaîne, malgré ce que la documentation en ligne pourrait dire.

Vous pouvez supprimer vous-même les espaces principaux en utilisant 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

Ou si vous souhaitez simplement supprimer les espaces, en laissant les onglets:

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

Certaines autres réponses trouvent le niveau d'indentation de la ligne la moins indentée, et supprimez cela de toutes les lignes, mais compte tenu de la nature de l'indentation dans la programmation (que la première ligne est la moins indentée), je pense que vous devriez recherchez le niveau de retrait de la première ligne.

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

Comme l'affiche originale, j'ai moi aussi découvert le <<-HEREDOC syntaxe et était sacrément déçu qu'il ne se comporte pas comme je pensais qu'il devrait se comporter.

Mais au lieu de salir mon code avec gsub-s, j'ai étendu la classe 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
2
Rene Saarsoo

une autre option facile à retenir est d'utiliser un joyau indenté

require 'unindent'

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

Remarque: Comme l'a souligné @radiospiel, String#squish n'est disponible que dans le contexte ActiveSupport.


Je crois Ruby String#squish est plus proche de ce que vous recherchez vraiment:

Voici comment je traiterais votre exemple:

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

J'avais besoin d'utiliser quelque chose avec system grâce auquel je pouvais diviser de longues commandes sed sur plusieurs lignes, puis supprimer l'indentation ET les nouvelles lignes ...

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

J'ai donc trouvé ceci:

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

Le comportement par défaut consiste à ne pas supprimer les sauts de ligne, comme tous les autres exemples.

1
markeissler

Je recueille des réponses et j'obtiens ceci:

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

Il génère un excellent SQL et ne sort pas des étendues AR.

0
Aivils Štoss