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?
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.
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 .
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.
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!
<<-
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
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
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
une autre option facile à retenir est d'utiliser un joyau indenté
require 'unindent'
p <<-end.unindent
hello
world
end
# => "hello\n world\n"
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
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.
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.