Ruby HEREDOC'tan baştaki boşluk karakterlerini nasıl kaldırırım?


93

Yapmaya çalıştığım bir Ruby heredocu ile ilgili bir sorun yaşıyorum. Baştaki tüm boşluk karakterlerini gizlemesi beklenen - operatörünü dahil etmeme rağmen, her satırın başındaki boşlukları döndürüyor. benim yöntemim şuna benziyor:

    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

ve çıktım şöyle görünüyor:

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

Bu, elbette, bu belirli durumda doğrudur, ilk "ve \ t arasındaki tüm boşluklar dışında. Burada ne yaptığımı bilen var mı?"

Yanıtlar:


145

<<-Metin bildiriminde şekli sadece uç sınırlayıcı için lider boşluk yok sayar.

Ruby 2.3 ve sonrası ile <<~, içerik satırlarının önde gelen boşluklarını gizlemek için dalgalı heredoc ( ) kullanabilirsiniz:

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"

Ruby değişmezleri belgelerinden :

En az girintili satırın girintisi, içeriğin her satırından kaldırılacaktır. Yalnızca değişmez sekmelerden ve boşluklardan oluşan boş satırların ve satırların girintiyi belirlemek amacıyla göz ardı edileceğini, ancak çıkış karakterli sekmeler ve boşlukların girintisiz karakterler olarak kabul edildiğini unutmayın.


13
Soruyu sorduktan 5 yıl sonra bunun hala geçerli bir konu olmasını seviyorum. güncellenmiş yanıt için teşekkürler!
Chris Drappier

1
@ChrisDrappier Bunun mümkün olup olmadığından emin değilim, ancak bu sorunun kabul edilen cevabını, bugünlerde bu açıkça çözüm olduğu için buna değiştirmenizi öneririm.
TheDeadSerious

123

Rails 3.0 veya daha yenisini kullanıyorsanız deneyin #strip_heredoc. Dokümanlardan alınan bu örnek , son iki satırın iki boşluklu girintisini korurken, ilk üç satırı girintisiz yazdırır:

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

Belgeler ayrıca şunları da not eder: "Teknik olarak, tüm dizede en az girintili satırı arar ve bu miktardaki baştaki boşluğu kaldırır."

İşte active_support / core_ext / string / strip.rb'den uygulama :

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

Ve testleri test / core_ext / string_ext_test.rb içinde bulabilirsiniz .


2
Bunu Rails 3 dışında da kullanabilirsiniz!
iconoclast

3
iconoclast doğrudur; sadece require "active_support/core_ext/string"ilk
David J.

2
Ruby 1.8.7'de çalışmıyor gibi görünüyor: tryString için tanımlanmadı. Aslında, raylara özgü bir yapı gibi görünüyor
Otheus

45

Korktuğumu bildiğim yapacak fazla bir şey yok. Genellikle yaparım:

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

Bu işe yarıyor ama biraz hile.

DÜZENLEME: Aşağıda Rene Saarsoo'dan ilham alarak bunun yerine şuna benzer bir şey öneririm:

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

Bu sürüm, ilk satırın en soldaki satır olmaması durumunda da işlem yapmalıdır.


1
Sorduğum için kendimi kirli hissediyorum, peki ya EOFsırf kendisinin varsayılan davranışını kesmek yerine kendi davranışını hacklemeye ne dersiniz String?
patcon

1
Elbette, EOF'nin davranışı ayrıştırma sırasında belirlenir, bu yüzden sizin önerdiğiniz @patcon, Ruby'nin kaynak kodunu değiştirmeyi içerir ve sonra kodunuz Ruby'nin diğer sürümlerinde farklı davranır.
einarmagnus

2
Ruby'nin çizgi HEREDOC sözdiziminin bash'de daha çok çalışmasını diliyorum, o zaman bu problemi yaşamazdık! ( Bu bash örneğine bakın )
TrinitronX

Uzman ipucu: Bunlardan herhangi birini içerikte boş satırlarla deneyin ve ardından bunun \ssatırsonu satırlarını içerdiğini unutmayın .
Phrogz

Bunu Ruby 2.2'de denedim ve herhangi bir sorun fark etmedim. Sana ne oldu ( repl.it/B09p )
einarmagnus

23

İşte kullandığım girintisiz komut dosyasının çok daha basit bir sürümü:

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

Bunu şu şekilde kullanın:

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

İlk satırın girintisi diğerlerinden daha fazla ise ve (Rails gibi) en az girintili satıra göre girintiyi kaldırmak istiyorsanız, bunun yerine şunu kullanmak isteyebilirsiniz:

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

Sizin \s+yerine tararsanız, [ \t]+satır başını beyaz boşluk yerine heredoc'unuzdan yeni satırları çıkarabileceğinizi unutmayın. Arzulanan değil!


8

<<-Ruby'de bitiş sınırlayıcı için yalnızca öndeki boşluğu göz ardı ederek düzgün şekilde girintilenmesine izin verir. Çevrimiçi bazı belgelerin söyleyebileceklerine rağmen, dizenin içindeki satırlarda baştaki boşluğu kaldırmaz.

Aşağıdakileri kullanarak baştaki beyaz boşluğu kendiniz kaldırabilirsiniz 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

Veya yalnızca boşlukları çıkarmak istiyorsanız, sekmeleri bırakarak:

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

1
-1 Yalnızca girinti miktarı yerine baştaki tüm boşlukları çıkarmak için.
Phrogz

7
@Phrogz OP, "önde gelen tüm boşluk karakterlerini bastırmasını" beklediğinden bahsetti, bu yüzden aradığı şey buysa diye, bunu yapan yanı sıra yalnızca boşlukları kaldıran bir yanıt verdim, eğer aradığı buysa. Birkaç ay sonra, OP için işe yarayan cevapları düşürmek ve kendi rakip cevabınızı yayınlamak biraz saçma.
Brian Campbell

@BrianCampbell Böyle hissettiğin için üzgünüm; hiçbir suç niyetlenmedi. Umarım kendi cevabım için oy toplamaya çalışmadığımı söylediğimde bana inanıyorsunuz, çünkü bu soruya benzer işlevsellik için dürüst bir arayışla geldiğim ve cevapları burada yetersiz bulduğum için. OP'nin tam ihtiyacını çözdüğü konusunda haklısınız, ancak daha fazla işlevsellik sağlayan biraz daha genel bir çözüm de öyle. Ayrıca, biri kabul edildikten sonra gönderilen yanıtların, özellikle iyileştirme sunmaları halinde, sitenin tamamı için hala değerli olduğunu kabul edeceğinizi umuyorum.
Phrogz

4
Son olarak, "rakip cevap" ifadesine değinmek istedim. Ne sen ne de ben rekabette olmamalıyız, öyle olduğumuza da inanmıyorum. (Olsa da, şu andan itibaren 27.4k temsilciyle kazanıyorsunuz. :) Sorunları olan kişilere hem kişisel olarak (OP) hem de anonim olarak (Google üzerinden gelenler) yardımcı oluyoruz. Daha fazla (geçerli) yanıt yardımı. Bu bağlamda, olumsuz oyumu tekrar gözden geçiriyorum. Cevabınızın zararlı, yanıltıcı veya abartılmadığı konusunda haklısınız. Şimdi sorunuzu, sizden aldığım 2 tekrar puanını verebilmek için düzenledim.
Phrogz

1
@Phrogz Huysuz olduğum için üzgünüm; OP'yi yeterince ele alan yanıtlar için "Beğenmediğim bir şey için -1" ile ilgili bir sorun yaşama eğilimindeyim. Neredeyse, ancak tam olarak değil, istediğinizi yapan zaten olumlu oylanmış veya kabul edilmiş yanıtlar olduğunda, gelecekte bir yorumda yanıtın nasıl daha iyi olabileceğini düşündüğünüzü açıklığa kavuşturmak, olumsuz oy kullanmaktan çok daha yararlı olacaktır. aşağıda çok aşağıda görünecek ve genellikle sorunu olan başka kimse tarafından görülmeyecek ayrı bir cevap yayınlamak. Yalnızca cevap gerçekten yanlış veya yanıltıcı ise olumsuz oy veririm.
Brian Campbell

6

Diğer bazı yanıtlar, girinti düzeyini bulur azından girintili hat ve tüm çizgilerin o silmek, ancak (ilk satır az girintili olması) programlama içinde girinti doğası göz önüne ben senin, girinti düzeyi için bakmak gerektiğini düşünüyorum ilk satır .

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

1
Psst: ya ilk satır boşsa?
Phrogz

3

Orijinal poster gibi ben de keşfettim <<-HEREDOC sözdizimini olması gerektiğini düşündüğüm gibi davranmadığı için oldukça hayal kırıklığına uğradım.

Ancak kodumu gsub-s ile doldurmak yerine String sınıfını genişlettim:

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

3
Monkeypatch için +1 ve yalnızca girintili boşlukları sıyırma, ancak aşırı karmaşık bir uygulama için -1.
Phrogz

Phrogz ile aynı fikirde olun, bu gerçekten kavramsal olarak en iyi cevap, ancak uygulama çok karmaşık
einarmagnus

2

Not: @radiospiel'in belirttiği gibi String#squish, yalnızca ActiveSupportbağlamda mevcuttur .


inanıyorum yakutlar String#squish gerçekten aradığınız şeye daha yakın:

Örneğinizi şu şekilde ele alacağım:

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

Olumsuz oy için teşekkürler, ancak bu çözümden neden kaçınılması gerektiğini açıklayan bir yorumdan hepimizin daha iyi yararlanacağına inanıyorum.
Marius Butuc

1
Sadece bir tahmin, ancak String # squish muhtemelen Ruby'nin bir parçası değil, Rails'in bir parçası; yani active_support kullanılmadıkça çalışmaz.
radiospiel

2

hatırlanması kolay bir başka seçenek de girintisiz mücevher kullanmaktır

require 'unindent'

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

2

systemUzun sedkomutları satırlara bölebileceğim ve ardından girintiyi VE satırsonlarını kaldırabileceğim bir şey kullanmam gerekiyordu ...

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

Ben de şunu buldum:

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

Varsayılan davranış, diğer tüm örneklerde olduğu gibi yeni satırları çıkarmamaktır.


1

Cevapları topluyorum ve şunu aldım:

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

Mükemmel SQL üretir ve AR kapsamlarının dışına çıkmaz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.