2012-12-05 12 views
9

Piszę test jednostkowy, a jeden z nich zwraca plik zip i chcę sprawdzić zawartość tego pliku zip, pobrać z niego pewne wartości i przekazać wartości do następnych testów.Jak iterować przez plik zip w pamięci w Ruby

Używam testu Rack, więc wiem, że zawartość mojego pliku zip znajduje się wewnątrz last_response.body. Przejrzałem dokumentację RubyZip, ale wygląda na to, że zawsze oczekuje pliku. Ponieważ przeprowadzam test jednostkowy, wolę mieć wszystko w pamięci, aby nie zanieczyszczać żadnego folderu z plikami testowymi, jeśli to możliwe.

Odpowiedz

6

Zobacz @bronson’s answer do bardziej aktualnej wersji tej odpowiedzi przy użyciu nowszej RubyZip API.

Dokumenty Rubyzip, z którymi się łączysz, wyglądają na nieco stare. The latest release (0.9.9) can handle IO objects, dzięki czemu można użyć StringIO (z odrobiną podkręcania).

Nawet API będzie akceptować IO, to wciąż wydaje się zakłada, że ​​jest to plik i próbuje wywołać path na nim, więc pierwsza małpa łata StringIO dodać metodę path (nie trzeba właściwie nic) :

require 'stringio' 
class StringIO 
    def path 
    end 
end 

Następnie można zrobić coś takiego:

require 'zip/zip' 
Zip::ZipInputStream.open_buffer(StringIO.new(last_response.body)) do |io| 
    while (entry = io.get_next_entry) 
    # deal with your zip contents here, e.g. 
    puts "Contents of #{entry.name}: '#{io.read}'" 
    end 
end 

i wszystko będzie odbywać się w pamięci.

1

Można użyć Tempfile, aby zrzucić plik zip do pliku tymczasowego. Tempfile tworzy plik tymczasowy specyficzny dla systemu operacyjnego, który zostanie wyczyszczony przez system operacyjny po zakończeniu programu.

+2

W systemach POSIX plik tymczasowy jest już "usunięty", gdy go otrzymasz, więc nie wymaga czyszczenia. Jest to najbliższa rzecz, którą można dostać się do nagiego uchwytu pliku do przejściowego obiektu pliku. – tadman

+0

@tadman nie wiedział, dziękuję. Jak to jest magiczne! – akuhn

13

Odpowiedź Matta jest słuszna. Tutaj jest aktualizowany do nowego interfejsu API:

Zip::InputStream.open(StringIO.new(input)) do |io| 
    while entry = io.get_next_entry 
    if entry.name == 'doc.kml' 
     parse_kml(io.read) 
    else 
     raise "unknown entry in kmz file: #{entry.name}" 
    end 
    end 
end 

Nie ma już potrzeby monkeypowania StringIO. Postęp!

0

Just an update na ten ze względu na zmiany w rubyzip:

Zip::InputStream.open(StringIO.new(zip_file)) do |io| 
    while (entry = io.get_next_entry) 
    # deal with your zip contents here, e.g. 
    puts "Contents of #{entry.name}: '#{io.read}'" 
    end 
end 
3
Zip::File.open_buffer(content) do |zip| 
    zip.each do |entry| 
    decompressed_data += entry.get_input_stream.read 
    end 
end 
+2

niektóre wyjaśnienia mogą znacznie pomóc w lepszym zrozumieniu, a nie skopiować i wkleić-przenieść na – MichaelChirico

0

Zainspirowany odpowiedź Matta mam nieco zmodyfikowaną rozwiązanie dla tych, którzy muszą korzystać z 0.9.x rubyzip gem. Mój nie wymaga nowej definicji klasy.

sio = StringIO.new(response.body) 
sio.define_singleton_method(:path) {} #needed to create fake method path TO satisfy the ancient rubyzip 0.9.8 gem 
Zip::ZipInputStream::open_buffer(sio) { |io| 
    while (entry = io.get_next_entry) 
     puts "Contents of #{entry.name}" 
    end 
} 
1

Z RubyZip wersji 1.2.1 (lub może niektóre wcześniejsze wersje też), po prostu trzeba użyć open_buffer metodę Zip::File klasie.

Z dokumentacji RubyZip:

Jak #open, ale odczytuje zawartość archiwum zip z ciągiem lub otwartego strumienia danych IO i wyjść do bufora. (To może być użyte do wyodrębnienia danych z pobranego archiwum zip bez uprzedniego zapisania go na dysku.)

Przykład:

Zip::File.open_buffer(last_response.body) do |zip| 
    zip.each do |entry| 
    puts entry.name 
    # Do whatever you want with the content files. 
    end 
end 
+0

Czy to działa dla Ciebie? Kiedy to zrobię, otrzymuję błąd szczegółowy [tutaj] (https://github.com/rubyzip/rubyzip/issues/177) – metahamza

0

Ten pracował dla mnie. W moim przypadku mam tylko jeden plik, więc użyłem stałej ścieżki, ale możesz użyć entry.name, aby zbudować ścieżkę.