tar 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rubocop.yml +40 -0
- data/.ruby-version +1 -0
- data/.travis.yml +8 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +68 -0
- data/Rakefile +16 -0
- data/bin/console +8 -0
- data/bin/setup +6 -0
- data/lib/char_size.rb +123 -0
- data/lib/tar.rb +4 -0
- data/lib/tar/backports.rb +51 -0
- data/lib/tar/error.rb +11 -0
- data/lib/tar/file_reader.rb +357 -0
- data/lib/tar/file_reader/line.rb +122 -0
- data/lib/tar/header.rb +58 -0
- data/lib/tar/header_reader.rb +37 -0
- data/lib/tar/polyfills.rb +14 -0
- data/lib/tar/reader.rb +30 -0
- data/lib/tar/schema.rb +87 -0
- data/lib/tar/ustar.rb +27 -0
- data/lib/tar/version.rb +5 -0
- data/tar.gemspec +28 -0
- metadata +139 -0
data/lib/tar/header.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tar/backports"
|
4
|
+
require "tar/error"
|
5
|
+
require "tar/schema"
|
6
|
+
require "tar/ustar"
|
7
|
+
|
8
|
+
module Tar
|
9
|
+
class Header
|
10
|
+
using Backports
|
11
|
+
|
12
|
+
SCHEMA = Schema.new {
|
13
|
+
string :name, 100
|
14
|
+
octal_number :mode, 8
|
15
|
+
octal_number :uid, 8
|
16
|
+
octal_number :gid, 8
|
17
|
+
octal_number :size, 12
|
18
|
+
timestamp :mtime, 12
|
19
|
+
octal_number :checksum, 8
|
20
|
+
string :typeflag, 1
|
21
|
+
string :link_name, 100
|
22
|
+
string :magic, 6
|
23
|
+
string :version, 2
|
24
|
+
string :uname, 32
|
25
|
+
string :gname, 32
|
26
|
+
octal_number :dev_major, 8
|
27
|
+
octal_number :dev_minor, 8
|
28
|
+
string :prefix, 155
|
29
|
+
}
|
30
|
+
|
31
|
+
def initialize(values, checksum: nil)
|
32
|
+
@values = values
|
33
|
+
check_checksum!(checksum) if checksum
|
34
|
+
end
|
35
|
+
|
36
|
+
SCHEMA.field_names.each do |name|
|
37
|
+
define_method name do
|
38
|
+
@values.fetch(name)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def path
|
43
|
+
return name if prefix.nil?
|
44
|
+
"#{prefix}/#{name}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.parse(record)
|
48
|
+
expected_checksum = SCHEMA.clear(record, :checksum).chars.sum(&:ord)
|
49
|
+
new(SCHEMA.parse(record), checksum: expected_checksum)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def check_checksum!(expected_checksum)
|
55
|
+
raise ChecksumMismatch, "checksum mismatch at #{path.inspect}: expected #{expected_checksum}, got #{checksum}" unless checksum == expected_checksum
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tar/backports"
|
4
|
+
require "tar/error"
|
5
|
+
require "tar/header"
|
6
|
+
require "tar/ustar"
|
7
|
+
|
8
|
+
module Tar
|
9
|
+
class HeaderReader
|
10
|
+
using Backports
|
11
|
+
|
12
|
+
def initialize(io)
|
13
|
+
@io = io
|
14
|
+
end
|
15
|
+
|
16
|
+
def read
|
17
|
+
record = read_record
|
18
|
+
|
19
|
+
if empty?(record)
|
20
|
+
return nil if empty?(read_record)
|
21
|
+
raise InvalidArchive, "empty header"
|
22
|
+
end
|
23
|
+
|
24
|
+
Header.parse(record)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def read_record
|
30
|
+
USTAR.read_record(@io)
|
31
|
+
end
|
32
|
+
|
33
|
+
def empty?(record)
|
34
|
+
/\A\0+\z/m.match?(record)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "zlib"
|
4
|
+
|
5
|
+
module Tar
|
6
|
+
module Polyfills
|
7
|
+
refine Zlib::GzipReader do
|
8
|
+
def read(length = nil, buffer = nil)
|
9
|
+
raise ArgumentError, "#{self.class} does not support read to buffer" unless buffer.nil?
|
10
|
+
super(length)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/tar/reader.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tar/file_reader"
|
4
|
+
require "tar/header"
|
5
|
+
require "tar/header_reader"
|
6
|
+
|
7
|
+
module Tar
|
8
|
+
class Reader
|
9
|
+
include Enumerable
|
10
|
+
|
11
|
+
def initialize(io, **encoding_options)
|
12
|
+
@io = io
|
13
|
+
@encoding_options = encoding_options
|
14
|
+
@header_reader = HeaderReader.new(@io)
|
15
|
+
end
|
16
|
+
|
17
|
+
def each
|
18
|
+
return to_enum unless block_given?
|
19
|
+
|
20
|
+
loop do
|
21
|
+
header = @header_reader.read
|
22
|
+
break if header.nil?
|
23
|
+
|
24
|
+
file_reader = FileReader.new(header, @io, **@encoding_options)
|
25
|
+
yield file_reader
|
26
|
+
file_reader.skip_to_next_record
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/tar/schema.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Tar
|
4
|
+
class Schema
|
5
|
+
def initialize(&block)
|
6
|
+
@fields = {}
|
7
|
+
@offset = 0
|
8
|
+
instance_eval(&block)
|
9
|
+
@fields.freeze
|
10
|
+
|
11
|
+
@unpack_format = @fields.values.map(&:unpack_format).join.freeze
|
12
|
+
end
|
13
|
+
|
14
|
+
def field_names
|
15
|
+
@fields.keys
|
16
|
+
end
|
17
|
+
|
18
|
+
def clear(record, field_name)
|
19
|
+
field = @fields.fetch(field_name)
|
20
|
+
|
21
|
+
record.dup.tap { |new_record|
|
22
|
+
new_record[field.offset, field.size] = " " * field.size
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def parse(record)
|
27
|
+
@fields.zip(record.unpack(@unpack_format))
|
28
|
+
.map { |(name, type), value| [name, type.parse(value)] }
|
29
|
+
.to_h
|
30
|
+
end
|
31
|
+
|
32
|
+
def string(name, size)
|
33
|
+
add_field name, String, size
|
34
|
+
end
|
35
|
+
|
36
|
+
def octal_number(name, size)
|
37
|
+
add_field name, OctalNumber, size
|
38
|
+
end
|
39
|
+
|
40
|
+
def timestamp(name, size)
|
41
|
+
add_field name, Timestamp, size
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def add_field(name, type, size)
|
47
|
+
@fields[name] = type.new(size, @offset)
|
48
|
+
@offset += size
|
49
|
+
end
|
50
|
+
|
51
|
+
class FieldType
|
52
|
+
attr_reader :size, :offset, :unpack_format
|
53
|
+
|
54
|
+
def initialize(size, offset, unpack_format:)
|
55
|
+
@size = size
|
56
|
+
@offset = offset
|
57
|
+
@unpack_format = unpack_format
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class String < FieldType
|
62
|
+
def initialize(size, offset)
|
63
|
+
super(size, offset, unpack_format: "Z#{size}")
|
64
|
+
end
|
65
|
+
|
66
|
+
def parse(value)
|
67
|
+
value unless value.empty?
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class OctalNumber < FieldType
|
72
|
+
def initialize(size, offset)
|
73
|
+
super(size, offset, unpack_format: "A#{size}")
|
74
|
+
end
|
75
|
+
|
76
|
+
def parse(value)
|
77
|
+
value.oct
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class Timestamp < OctalNumber
|
82
|
+
def parse(value)
|
83
|
+
Time.at(super)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/lib/tar/ustar.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tar/error"
|
4
|
+
|
5
|
+
module Tar
|
6
|
+
module USTAR
|
7
|
+
module_function
|
8
|
+
|
9
|
+
RECORD_SIZE = 512
|
10
|
+
|
11
|
+
def read_record(io)
|
12
|
+
record = io.read(RECORD_SIZE) || ""
|
13
|
+
|
14
|
+
raise UnexpectedEOF, "unexpected end-of-file: attempted to read #{RECORD_SIZE} bytes from #{io}, got #{record.size}" unless record.size == RECORD_SIZE
|
15
|
+
|
16
|
+
record
|
17
|
+
end
|
18
|
+
|
19
|
+
def records(file_size)
|
20
|
+
(file_size - 1) / RECORD_SIZE + 1
|
21
|
+
end
|
22
|
+
|
23
|
+
def records_size(file_size)
|
24
|
+
RECORD_SIZE * records(file_size)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/tar/version.rb
ADDED
data/tar.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path("../lib", __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
|
6
|
+
require "tar/version"
|
7
|
+
|
8
|
+
Gem::Specification.new do |spec|
|
9
|
+
spec.name = "tar"
|
10
|
+
spec.version = Tar::VERSION
|
11
|
+
spec.authors = ["Andrew Haines"]
|
12
|
+
spec.email = ["andrew@haines.org.nz"]
|
13
|
+
|
14
|
+
spec.summary = "Read and write TAR files"
|
15
|
+
spec.homepage = "https://github.com/haines/tar"
|
16
|
+
spec.license = "MIT"
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
19
|
+
spec.bindir = "exe"
|
20
|
+
spec.executables = spec.files.grep(%r{^#{spec.bindir}/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.14"
|
24
|
+
spec.add_development_dependency "codeclimate-test-reporter", "~> 1.0.8"
|
25
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
26
|
+
spec.add_development_dependency "pry", "~> 0.10"
|
27
|
+
spec.add_development_dependency "rake", "~> 12.0"
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tar
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andrew Haines
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-06-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.14'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.14'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: codeclimate-test-reporter
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.0.8
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.0.8
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.10'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.10'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '12.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '12.0'
|
83
|
+
description:
|
84
|
+
email:
|
85
|
+
- andrew@haines.org.nz
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- ".rubocop.yml"
|
92
|
+
- ".ruby-version"
|
93
|
+
- ".travis.yml"
|
94
|
+
- CODE_OF_CONDUCT.md
|
95
|
+
- Gemfile
|
96
|
+
- LICENSE.txt
|
97
|
+
- README.md
|
98
|
+
- Rakefile
|
99
|
+
- bin/console
|
100
|
+
- bin/setup
|
101
|
+
- lib/char_size.rb
|
102
|
+
- lib/tar.rb
|
103
|
+
- lib/tar/backports.rb
|
104
|
+
- lib/tar/error.rb
|
105
|
+
- lib/tar/file_reader.rb
|
106
|
+
- lib/tar/file_reader/line.rb
|
107
|
+
- lib/tar/header.rb
|
108
|
+
- lib/tar/header_reader.rb
|
109
|
+
- lib/tar/polyfills.rb
|
110
|
+
- lib/tar/reader.rb
|
111
|
+
- lib/tar/schema.rb
|
112
|
+
- lib/tar/ustar.rb
|
113
|
+
- lib/tar/version.rb
|
114
|
+
- tar.gemspec
|
115
|
+
homepage: https://github.com/haines/tar
|
116
|
+
licenses:
|
117
|
+
- MIT
|
118
|
+
metadata: {}
|
119
|
+
post_install_message:
|
120
|
+
rdoc_options: []
|
121
|
+
require_paths:
|
122
|
+
- lib
|
123
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - ">="
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
requirements: []
|
134
|
+
rubyforge_project:
|
135
|
+
rubygems_version: 2.6.11
|
136
|
+
signing_key:
|
137
|
+
specification_version: 4
|
138
|
+
summary: Read and write TAR files
|
139
|
+
test_files: []
|