rubytar

Error Sorting Tar Entries by full_name in Ruby


I want to enumerate through the contents of a tar file in alphabetical order of the filenames contained in the tar file. The following dies with the error Error: closed Gem::Package::TarReader::Entry.

Gem::Package::TarReader.new io do |tar|
  tar.sort_by{ |e| e.full_name }.each.map do |tar_entry|
    file_contents = tar_entry.read
    # more code here, not shown for clarity
    file_contents
  end
end

Here is the stack trace:

Liquid Exception: closed Gem::Package::TarReader::Entry in /var/sitesUbuntu/www.mslinn.com/_drafts/2020-09-28-using-aws-cloud9-with-jump-server.html
jekyll 3.8.7 | Error:  closed Gem::Package::TarReader::Entry
/usr/lib/ruby/2.7.0/rubygems/package/tar_reader/entry.rb:30:in `check_closed': closed Gem::Package::TarReader::Entry (IOError)
        from /usr/lib/ruby/2.7.0/rubygems/package/tar_reader/entry.rb:133:in `read'
        from /var/sitesUbuntu/www.mslinn.com/_plugins/archive_display.rb:14:in `block (2 levels) in untar_contents'
        from /var/sitesUbuntu/www.mslinn.com/_plugins/archive_display.rb:13:in `each'
        from /var/sitesUbuntu/www.mslinn.com/_plugins/archive_display.rb:13:in `each'
        from /var/sitesUbuntu/www.mslinn.com/_plugins/archive_display.rb:13:in `map'
        from /var/sitesUbuntu/www.mslinn.com/_plugins/archive_display.rb:13:in `block in untar_contents'
        from /usr/lib/ruby/2.7.0/rubygems/package/tar_reader.rb:29:in `new'
        from /var/sitesUbuntu/www.mslinn.com/_plugins/archive_display.rb:12:in `untar_contents'
        from /var/sitesUbuntu/www.mslinn.com/_plugins/archive_display.rb:25:in `block in render'
        from /var/sitesUbuntu/www.mslinn.com/_plugins/archive_display.rb:24:in `open'
        from /var/sitesUbuntu/www.mslinn.com/_plugins/archive_display.rb:24:in `render'
        from /var/lib/gems/2.7.0/gems/liquid-4.0.3/lib/liquid/block_body.rb:103:in `render_node_to_output'
        from /var/lib/gems/2.7.0/gems/liquid-4.0.3/lib/liquid/block_body.rb:91:in `render'
        from /var/lib/gems/2.7.0/gems/liquid-4.0.3/lib/liquid/template.rb:208:in `block in render'
        from /var/lib/gems/2.7.0/gems/liquid-4.0.3/lib/liquid/template.rb:242:in `with_profiling'
        from /var/lib/gems/2.7.0/gems/liquid-4.0.3/lib/liquid/template.rb:207:in `render'
        from /var/lib/gems/2.7.0/gems/liquid-4.0.3/lib/liquid/template.rb:220:in `render!'
        from /var/lib/gems/2.7.0/gems/jekyll-3.8.7/lib/jekyll/liquid_renderer/file.rb:30:in `block (2 levels) in render!'
        from /var/lib/gems/2.7.0/gems/jekyll-3.8.7/lib/jekyll/liquid_renderer/file.rb:42:in `measure_bytes'
        from /var/lib/gems/2.7.0/gems/jekyll-3.8.7/lib/jekyll/liquid_renderer/file.rb:29:in `block in render!'
        from /var/lib/gems/2.7.0/gems/jekyll-3.8.7/lib/jekyll/liquid_renderer/file.rb:49:in `measure_time'
        from /var/lib/gems/2.7.0/gems/jekyll-3.8.7/lib/jekyll/liquid_renderer/file.rb:28:in `render!'
        from /var/lib/gems/2.7.0/gems/jekyll-3.8.7/lib/jekyll/renderer.rb:126:in `render_liquid'
        from /var/lib/gems/2.7.0/gems/jekyll-3.8.7/lib/jekyll/renderer.rb:79:in `render_document'
        from /var/lib/gems/2.7.0/gems/jekyll-3.8.7/lib/jekyll/renderer.rb:62:in `run'
        from /var/lib/gems/2.7.0/gems/jekyll-3.8.7/lib/jekyll/site.rb:479:in `render_regenerated'
        from /var/lib/gems/2.7.0/gems/jekyll-3.8.7/lib/jekyll/site.rb:464:in `block (2 levels) in render_docs'
        from /var/lib/gems/2.7.0/gems/jekyll-3.8.7/lib/jekyll/site.rb:463:in `each'
        from /var/lib/gems/2.7.0/gems/jekyll-3.8.7/lib/jekyll/site.rb:463:in `block in render_docs'
        from /var/lib/gems/2.7.0/gems/jekyll-3.8.7/lib/jekyll/site.rb:462:in `each_value'
        from /var/lib/gems/2.7.0/gems/jekyll-3.8.7/lib/jekyll/site.rb:462:in `render_docs'
        from /var/lib/gems/2.7.0/gems/jekyll-3.8.7/lib/jekyll/site.rb:191:in `render'
        from /var/lib/gems/2.7.0/gems/jekyll-3.8.7/lib/jekyll/site.rb:71:in `process'
        from /var/lib/gems/2.7.0/gems/jekyll-3.8.7/lib/jekyll/command.rb:28:in `process_site'
        from /var/lib/gems/2.7.0/gems/jekyll-3.8.7/lib/jekyll/commands/build.rb:65:in `build'
        from /var/lib/gems/2.7.0/gems/jekyll-3.8.7/lib/jekyll/commands/build.rb:36:in `process'
        from /var/lib/gems/2.7.0/gems/jekyll-3.8.7/lib/jekyll/commands/serve.rb:93:in `block in start'
        from /var/lib/gems/2.7.0/gems/jekyll-3.8.7/lib/jekyll/commands/serve.rb:93:in `each'
        from /var/lib/gems/2.7.0/gems/jekyll-3.8.7/lib/jekyll/commands/serve.rb:93:in `start'
        from /var/lib/gems/2.7.0/gems/jekyll-3.8.7/lib/jekyll/commands/serve.rb:75:in `block (2 levels) in init_with_program'
        from /var/lib/gems/2.7.0/gems/mercenary-0.3.6/lib/mercenary/command.rb:220:in `block in execute'
        from /var/lib/gems/2.7.0/gems/mercenary-0.3.6/lib/mercenary/command.rb:220:in `each'
        from /var/lib/gems/2.7.0/gems/mercenary-0.3.6/lib/mercenary/command.rb:220:in `execute'
        from /var/lib/gems/2.7.0/gems/mercenary-0.3.6/lib/mercenary/program.rb:42:in `go'
        from /var/lib/gems/2.7.0/gems/mercenary-0.3.6/lib/mercenary.rb:19:in `program'
        from /var/lib/gems/2.7.0/gems/jekyll-3.8.7/exe/jekyll:15:in `<top (required)>'
        from /home/mslinn/gems/bin/jekyll:23:in `load'
        from /home/mslinn/gems/bin/jekyll:23:in `<main>'

Solution

  • I just had to sort the result:

    require 'rubygems/package'
    File.open(tar_name, "rb") do |file|
      Gem::Package::TarReader.new(file) do |tar|
        return tar.each.map { |tar_entry|
          if tar_entry.file?
            file_contents = tar_entry.read.strip
            "<div class='codeLabel'>#{tar_entry.full_name}</div><pre data-lt-active='false'>#{file_contents}</pre>"
          else
            ""
          end
        }.sort_by{ |e| e.full_name }
      end
    end
    

    Here is how I actually wrote the code in production:

    def traverse_tar(tar_name)
      require 'rubygems/package'
      File.open(tar_name, "rb") do |file|
        Gem::Package::TarReader.new(file) do |tar|
          return tar.each.map { |entry|
            if entry.file? then { :name => entry.full_name, :content => entry.read.strip } else nil end
          }.compact.sort_by { |entry| entry[:name] }.map { |entry|
            "<div class='codeLabel'>#{entry[:name]}</div><pre data-lt-active='false'>#{entry[:content]}</pre>"
          }
        end
      end
    end
    

    This code is used in my jekyll_archive_display plugin.