¿Cómo elimino los caracteres principales de espacio en blanco de Ruby HEREDOC?

Estoy teniendo un problema con un Ruby heredoc que estoy tratando de hacer. Devuelve el espacio en blanco inicial de cada línea a pesar de que incluyo el operador -, que se supone que suprime todos los caracteres principales en blanco. mi método se ve así:

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 

y mi salida se ve así:

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

esto, por supuesto, es correcto en esta instancia específica, a excepción de todos los espacios entre el primero “y” ¿alguien sabe lo que estoy haciendo mal aquí?

La forma < <- de heredoc solo ignora los espacios en blanco iniciales para el delimitador final.

Con Ruby 2.3 y posterior puede usar un squiggly heredoc ( < <~ ) para suprimir el espacio en blanco inicial de las líneas de contenido:

 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" 

De la documentación de Ruby literales :

La sangría de la línea menos sangrada se eliminará de cada línea del contenido. Tenga en cuenta que las líneas y líneas vacías que consisten únicamente en tabs y espacios literales se ignorarán a los efectos de determinar la sangría, pero las tabs y espacios vacíos se consideran caracteres sin sangría.

Si está utilizando Rails 3.0 o posterior, intente con #strip_heredoc . Este ejemplo de los documentos imprime las primeras tres líneas sin sangría, conservando la sangría de dos espacios de las dos últimas líneas:

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

La documentación también señala: "Técnicamente, busca la línea menos sangrada en toda la cadena y elimina esa cantidad de espacio en blanco inicial".

Aquí está la implementación 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 

Y puede encontrar las pruebas en test / core_ext / string_ext_test.rb .

No hay mucho que hacer que sé que tengo miedo. Usualmente hago:

 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 

Eso funciona, pero es un poco complicado.

EDITAR: Tomando inspiración de Rene Saarsoo a continuación, sugeriría algo como esto en su lugar:

 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 

Esta versión debe manejar cuando la primera línea no es la que está más a la izquierda también.

Aquí hay una versión mucho más simple de la secuencia de comandos sin incluir que uso:

 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 

Úselo así:

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

Si la primera línea se puede sangrar más que otras, y desea (como Rieles) desencriptar en función de la línea con sangrado mínimo, en su lugar puede desear usar:

 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 

Tenga en cuenta que si explora por \s+ lugar de [ \t]+ , puede acabar eliminando nuevas líneas de su heredoc en lugar de marcar espacios en blanco. ¡No deseable!

< <- en Ruby solo ignorará el espacio inicial para el delimitador final, lo que permite que tenga una sangría adecuada. No quita el espacio que lleva en las líneas dentro de la cadena, a pesar de lo que pueda decir alguna documentación en línea.

Puede quitar el espacio en blanco líder utilizando 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 

O si solo quieres quitar espacios, dejando las tabs:

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

Algunas otras respuestas encuentran el nivel de sangría de la línea con la sangría mínima y la eliminan de todas las líneas, pero considerando la naturaleza de la sangría en la progtwigción (que la primera línea es la menos sangrada), creo que debería buscar el nivel de sangría del primera línea

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

Al igual que el póster original, también descubrí la syntax < <-HEREDOC y me decepcionó bastante que no se comportara como pensé que debería comportarse.

Pero en lugar de ensuciar mi código con gsub-s extendí la clase 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 

Nota: Como señaló @radiospiel, String#squish solo está disponible en el contexto de ActiveSupport .


Creo Ruby String#squish está más cerca de lo que realmente estás buscando:

Así es como manejaría su ejemplo:

 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 

Otra opción fácil de recordar es usar la gem de unindent

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

Recopilo respuestas y obtuve esto:

 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 

Genera SQL excelente y no sale de los ámbitos AR.

Necesitaba usar algo con el system por el cual pudiera dividir los comandos long sed entre las líneas y luego eliminar la sangría Y las líneas nuevas …

 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 

Así que se me ocurrió esto:

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

El comportamiento predeterminado es no quitar nuevas líneas, al igual que todos los demás ejemplos.