mini_tarball 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e4f402eb6b43dd9a6a5b62d59d60d4ab949e9ea9c4e03be89354ab71624f5c74
4
- data.tar.gz: 96e28996b32be3a777e7e8a1517e746536851e8a5e5cd330854a2e75b763be70
3
+ metadata.gz: 313b7544e163106d7c251eb36ace29a96c22dfe43bc198310966155c18417260
4
+ data.tar.gz: fe252395b2daa1b7a8843ff27a0f6b87d96dff09031c485c42c217ad39b9ba52
5
5
  SHA512:
6
- metadata.gz: 0b941f8cb82d7a0a73782cd59e4e6185c4e28bd4c3e41f5eaaff1056e8503747df7012ba5cbe6a515d2138183bed3502f977b62f5cf6ab1909d9d7efdc3cd956
7
- data.tar.gz: 108091d73b67d1cff944b04fc9c0123490b2234fcf1d53a53858bfa341204ab1bb7048b71cc4e596cb979c4ef92d62f0ebce6d914267f0012fe5e387477e7d40
6
+ metadata.gz: acf0381c3b1dfc12c7d3e2da19f3bf3747f3c60e4af2939718d0095fe6dbff6554bf359beaf8e2a74b2a6f44b317620eb85977360a6bce0cae88c638442b5e86
7
+ data.tar.gz: f86d7def07d9c89a5375f2428324c1b9e8f7ec2e35c57d473c35a79e42207929685410900e2e76e1e19969ac2051f3f34e9f8f2669de792c2a522a6da78e1c56
@@ -8,7 +8,7 @@ module MiniTarball
8
8
  TYPE_REGULAR = "0"
9
9
  TYPE_LONG_LINK = "L"
10
10
 
11
- # rubocop:disable Layout/HashAlignment
11
+ # stree-ignore
12
12
  FIELDS = {
13
13
  name: { length: 100, type: :chars },
14
14
  mode: { length: 8, type: :mode },
@@ -27,9 +27,33 @@ module MiniTarball
27
27
  devminor: { length: 8, type: :number },
28
28
  prefix: { length: 155, type: :chars }
29
29
  }
30
- # rubocop:enable Layout/HashAlignment
31
30
 
32
- def initialize(name:, mode: 0, uid: nil, gid: nil, size: 0, mtime: 0, typeflag: TYPE_REGULAR, linkname: "", uname: nil, gname: nil)
31
+ def self.long_link_header(name)
32
+ Header.new(
33
+ name: "././@LongLink",
34
+ mode: 0644,
35
+ uid: 0,
36
+ gid: 0,
37
+ size: name.bytesize + 1,
38
+ typeflag: TYPE_LONG_LINK,
39
+ uname: "root",
40
+ gname: "root",
41
+ )
42
+ end
43
+
44
+ # :reek:LongParameterList
45
+ def initialize(
46
+ name:,
47
+ mode: 0,
48
+ uid: nil,
49
+ gid: nil,
50
+ size: 0,
51
+ mtime: 0,
52
+ typeflag: TYPE_REGULAR,
53
+ linkname: "",
54
+ uname: nil,
55
+ gname: nil
56
+ )
33
57
  @values = {
34
58
  name: name,
35
59
  mode: mode,
@@ -46,12 +70,21 @@ module MiniTarball
46
70
  gname: gname,
47
71
  devmajor: nil,
48
72
  devminor: nil,
49
- prefix: ""
73
+ prefix: "",
50
74
  }
51
75
  end
52
76
 
53
77
  def value_of(key)
54
78
  @values[key]
55
79
  end
80
+
81
+ def to_binary
82
+ fields = HeaderFields.new(self)
83
+ fields.to_binary
84
+ end
85
+
86
+ def has_long_name?
87
+ value_of(:name).bytesize > FIELDS[:name][:length]
88
+ end
56
89
  end
57
90
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniTarball
4
+ class HeaderFields
5
+ def self.pack_format
6
+ @pack_format ||= Header::FIELDS.values.map { |field| "a#{field[:length]}" }.join("")
7
+ end
8
+
9
+ def initialize(header)
10
+ @header = header
11
+ @values_by_field = {}
12
+ end
13
+
14
+ def to_binary
15
+ Header::FIELDS.each_key do |name|
16
+ value = @header.value_of(name)
17
+ set_value(name, value)
18
+ end
19
+
20
+ update_checksum
21
+ HeaderFormatter.zero_pad(encode_fields)
22
+ end
23
+
24
+ # :reek:DuplicateMethodCall
25
+ def set_value(name, value)
26
+ field = Header::FIELDS[name]
27
+
28
+ case field[:type]
29
+ when :number
30
+ @values_by_field[name] = HeaderFormatter.format_number(value, field[:length])
31
+ when :mode
32
+ @values_by_field[name] = HeaderFormatter.format_permissions(value, field[:length])
33
+ when :checksum
34
+ @values_by_field[name] = HeaderFormatter.format_checksum(value)
35
+ else
36
+ @values_by_field[name] = value
37
+ end
38
+ end
39
+
40
+ def update_checksum
41
+ checksum = encode_fields.unpack("C*").sum
42
+ set_value(:checksum, checksum)
43
+ end
44
+
45
+ private def encode_fields
46
+ values = @values_by_field.values
47
+ values.pack(HeaderFields.pack_format)
48
+ end
49
+ end
50
+ end
@@ -3,30 +3,50 @@
3
3
  module MiniTarball
4
4
  ValueTooLargeError = Class.new(StandardError)
5
5
 
6
- class HeaderFormatter
6
+ module HeaderFormatter
7
7
  PERMISSION_BITMASK = 0007777
8
8
 
9
9
  # @param value [Integer]
10
10
  # @param length [Integer]
11
11
  def self.format_number(value, length)
12
- return nil if value.nil?
12
+ return nil if !value
13
13
  raise NotImplementedError.new("Negative numbers are not supported") if value.negative?
14
14
 
15
+ fits_into_octal?(value, length) ? to_octal(value, length) : to_base256(value, length)
16
+ end
17
+
18
+ # Removes file type bitfields and returns file permissions as formatted number
19
+ # @param value [Integer]
20
+ # @param length [Integer]
21
+ def self.format_permissions(value, length)
22
+ format_number(value & PERMISSION_BITMASK, length)
23
+ end
24
+
25
+ def self.format_checksum(checksum)
26
+ length = Header::FIELDS[:checksum][:length]
27
+
28
+ checksum ? format_number(checksum, length - 1) << "\0 " : " " * length
29
+ end
30
+
31
+ def self.zero_pad(binary)
32
+ padding_length = (Header::BLOCK_SIZE - binary.length) % Header::BLOCK_SIZE
33
+ binary << "\0" * padding_length
34
+ end
35
+
36
+ private_class_method def self.fits_into_octal?(value, length)
15
37
  octal_length = length - 1
16
38
  max_octal_value = ("0" + "7" * octal_length).to_i(8)
17
-
18
- if (value <= max_octal_value)
19
- to_octal(value, octal_length)
20
- else
21
- to_base256(value, length)
22
- end
39
+ value <= max_octal_value
23
40
  end
24
41
 
25
- def self.to_octal(value, length)
26
- "%0#{length}o" % value
42
+ private_class_method def self.to_octal(value, length)
43
+ octal_length = length - 1
44
+ "%0#{octal_length}o" % value
27
45
  end
28
46
 
29
- def self.to_base256(value, length)
47
+ # :reek:TooManyStatements { max_statements: 8}
48
+ # :reek:UncommunicativeMethodName
49
+ private_class_method def self.to_base256(value, length)
30
50
  encoded = Array.new(length, 0)
31
51
  encoded[0] = 0x80
32
52
  index = length - 1
@@ -40,12 +60,5 @@ module MiniTarball
40
60
 
41
61
  encoded.pack("C#{length}")
42
62
  end
43
-
44
- # Removes file type bitfields and returns file permissions as formatted number
45
- # @param value [Integer]
46
- # @param length [Integer]
47
- def self.format_permissions(value, length)
48
- format_number(value & PERMISSION_BITMASK, length)
49
- end
50
63
  end
51
64
  end
@@ -7,79 +7,17 @@ module MiniTarball
7
7
  end
8
8
 
9
9
  def write(header)
10
- write_long_name_header(header) if has_long_name?(header)
11
- @io.write(to_binary(header))
10
+ write_long_name_header(header) if header.has_long_name?
11
+ @io.write(header.to_binary)
12
12
  end
13
13
 
14
- private
15
-
16
- def to_binary(header)
17
- values_by_field = {}
18
-
19
- Header::FIELDS.each do |name, field|
20
- value = values_by_field[name] = header.value_of(name)
21
-
22
- case field[:type]
23
- when :number
24
- values_by_field[name] = HeaderFormatter.format_number(value, field[:length])
25
- when :mode
26
- values_by_field[name] = HeaderFormatter.format_permissions(value, field[:length])
27
- when :checksum
28
- values_by_field[name] = " " * field[:length]
29
- end
30
- end
31
-
32
- update_checksum(values_by_field)
33
- add_padding(encode(values_by_field.values))
34
- end
35
-
36
- def update_checksum(values_by_field)
37
- checksum = encode(values_by_field.values).unpack("C*").sum
38
- values_by_field[:checksum] = format_checksum(checksum)
39
- end
40
-
41
- def format_checksum(checksum)
42
- length = Header::FIELDS[:checksum][:length] - 1
43
- HeaderFormatter.format_number(checksum, length) << "\0 "
44
- end
45
-
46
- def encode(values)
47
- @pack_format ||= Header::FIELDS.values
48
- .map { |field| "a#{field[:length]}" }
49
- .join("")
50
-
51
- values.pack(@pack_format)
52
- end
53
-
54
- def add_padding(binary)
55
- padding_length = (Header::BLOCK_SIZE - binary.length) % Header::BLOCK_SIZE
56
- binary << "\0" * padding_length
57
- end
58
-
59
- def has_long_name?(header)
60
- header.value_of(:name).bytesize > Header::FIELDS[:name][:length]
61
- end
62
-
63
- def write_long_name_header(header)
14
+ private def write_long_name_header(header)
64
15
  name = header.value_of(:name)
65
- private_header = long_link_header(name, Header::TYPE_LONG_LINK)
66
- data = [header.value_of(:name)].pack("Z*")
67
-
68
- @io.write(to_binary(private_header))
69
- @io.write(add_padding(data))
70
- end
16
+ private_header = Header.long_link_header(name)
17
+ binary_data = [name].pack("Z*")
71
18
 
72
- def long_link_header(name, type)
73
- Header.new(
74
- name: "././@LongLink",
75
- mode: 0644,
76
- uid: 0,
77
- gid: 0,
78
- size: name.bytesize + 1,
79
- typeflag: type,
80
- uname: "root",
81
- gname: "root"
82
- )
19
+ @io.write(private_header.to_binary)
20
+ @io.write(HeaderFormatter.zero_pad(binary_data))
83
21
  end
84
22
  end
85
23
  end
@@ -1,10 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MiniTarball
4
- class WriteOutOfRangeError < StandardError; end
4
+ class WriteOutOfRangeError < StandardError
5
+ end
5
6
 
6
7
  class LimitedSizeStream
7
8
  attr_reader :start_position, :end_position
9
+ attr_reader :io
10
+ private :io # TODO change to `private attr_reader :io` after dropping support for Ruby 2.7
8
11
 
9
12
  def initialize(io, start_position:, max_file_size:)
10
13
  @io = io
@@ -13,11 +16,13 @@ module MiniTarball
13
16
  end
14
17
 
15
18
  def write(data)
16
- if @io.pos < start_position || @io.pos + data.bytesize > end_position
19
+ current_position = io.pos
20
+
21
+ if current_position < start_position || current_position + data.bytesize > end_position
17
22
  raise WriteOutOfRangeError.new("Writing outside of limits not allowed")
18
23
  end
19
24
 
20
- @io.write(data)
25
+ io.write(data)
21
26
  end
22
27
  end
23
28
  end
@@ -8,7 +8,10 @@ module MiniTarball
8
8
 
9
9
  def write(data)
10
10
  super(data)
11
- @io.write("\0" * (end_position - @io.pos)) if @io.pos <= end_position
11
+
12
+ if (current_position = io.pos) <= end_position
13
+ io.write("\0" * (end_position - current_position))
14
+ end
12
15
  end
13
16
  end
14
17
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MiniTarball
4
- VERSION = "0.3.0"
4
+ VERSION = "0.3.1"
5
5
  end
@@ -1,17 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'etc'
3
+ require "etc"
4
4
 
5
5
  module MiniTarball
6
+ class NoIOLikeObjectError < StandardError
7
+ end
8
+
6
9
  class Writer
7
10
  END_OF_TAR_BLOCK_SIZE = 1024
8
11
 
9
12
  # @param [String] filename
10
13
  # @yieldparam [Writer]
14
+ #
15
+ # :reek:NestedIterators
11
16
  def self.create(filename)
12
- File.open(filename, "wb") do |file|
13
- use(file) { |writer| yield(writer) }
14
- end
17
+ File.open(filename, "wb") { |file| use(file) { |writer| yield(writer) } }
15
18
  end
16
19
 
17
20
  # @param [IO] io
@@ -29,7 +32,7 @@ module MiniTarball
29
32
  end
30
33
 
31
34
  def initialize(io)
32
- ensure_valid_io!(io)
35
+ ensure_valid_io(io)
33
36
 
34
37
  @io = io
35
38
  @write_only_io = WriteOnlyStream.new(@io)
@@ -38,30 +41,59 @@ module MiniTarball
38
41
  @placeholders = []
39
42
  end
40
43
 
41
- def add_file(name:, source_file_path:, mode: nil, uname: nil, gname: nil, uid: nil, gid: nil, mtime: nil)
42
- ensure_not_closed!
44
+ # :reek:ControlParameter
45
+ # :reek:DuplicateMethodCall { allow_calls: ['stat.uid', 'stat.gid'] }
46
+ # :reek:FeatureEnvy
47
+ # :reek:LongParameterList
48
+ # :reek:TooManyStatements
49
+ def add_file(
50
+ name:,
51
+ source_file_path:,
52
+ mode: nil,
53
+ uname: nil,
54
+ gname: nil,
55
+ uid: nil,
56
+ gid: nil,
57
+ mtime: nil
58
+ )
59
+ ensure_not_closed
43
60
 
44
- file = File.open(source_file_path, "rb")
45
61
  stat = File.stat(source_file_path)
46
62
 
47
- @header_writer.write(Header.new(
48
- name: name,
49
- size: stat.size,
50
- mode: mode || stat.mode,
51
- uid: uid || stat.uid,
52
- gid: gid || stat.gid,
53
- uname: uname || Etc.getpwuid(stat.uid).name,
54
- gname: gname || Etc.getgrgid(stat.gid).name,
55
- mtime: mtime || stat.mtime
56
- ))
57
-
58
- IO.copy_stream(file, @write_only_io)
63
+ @header_writer.write(
64
+ Header.new(
65
+ name: name,
66
+ size: stat.size,
67
+ mode: mode || stat.mode,
68
+ uid: uid || stat.uid,
69
+ gid: gid || stat.gid,
70
+ uname: uname || Etc.getpwuid(stat.uid).name,
71
+ gname: gname || Etc.getgrgid(stat.gid).name,
72
+ mtime: mtime || stat.mtime,
73
+ ),
74
+ )
75
+
76
+ File.open(source_file_path, "rb") { |file| IO.copy_stream(file, @write_only_io) }
77
+
59
78
  write_padding
60
79
  nil
61
80
  end
62
81
 
63
- def add_file_from_stream(name:, mode: 0644, uname: "nobody", gname: "nogroup", uid: nil, gid: nil, mtime: nil)
64
- ensure_not_closed!
82
+ # :reek:ControlParameter
83
+ # :reek:DuplicateMethodCall { allow_calls: ['@io.pos'] }
84
+ # :reek:LongParameterList
85
+ # :reek:TooManyStatements
86
+ def add_file_from_stream(
87
+ name:,
88
+ mode: 0644,
89
+ uname: "nobody",
90
+ gname: "nogroup",
91
+ uid: nil,
92
+ gid: nil,
93
+ mtime: nil
94
+ )
95
+ ensure_not_closed
96
+ ensure_seekable_io
65
97
 
66
98
  header_start_position = @io.pos
67
99
  @header_writer.write(Header.new(name: name))
@@ -72,23 +104,27 @@ module MiniTarball
72
104
  write_padding
73
105
 
74
106
  @io.seek(header_start_position)
75
- @header_writer.write(Header.new(
76
- name: name,
77
- size: file_size,
78
- mode: mode,
79
- uid: uid,
80
- gid: gid,
81
- uname: uname,
82
- gname: gname,
83
- mtime: mtime || Time.now.utc
84
- ))
107
+ @header_writer.write(
108
+ Header.new(
109
+ name: name,
110
+ size: file_size,
111
+ mode: mode,
112
+ uid: uid,
113
+ gid: gid,
114
+ uname: uname,
115
+ gname: gname,
116
+ mtime: mtime || Time.now.utc,
117
+ ),
118
+ )
85
119
 
86
120
  @io.seek(0, IO::SEEK_END)
87
121
  nil
88
122
  end
89
123
 
124
+ # :reek:DuplicateMethodCall { allow_calls: ['@io.pos'] }
125
+ # :reek:TooManyStatements
90
126
  def add_file_placeholder(name:, file_size:)
91
- ensure_not_closed!
127
+ ensure_not_closed
92
128
 
93
129
  placeholder = {}
94
130
  placeholder[:header_start_position] = @io.pos
@@ -104,17 +140,21 @@ module MiniTarball
104
140
  @placeholders.size - 1
105
141
  end
106
142
 
143
+ # :reek:TooManyStatements
107
144
  def with_placeholder(index)
145
+ ensure_seekable_io
146
+
108
147
  placeholder = @placeholders[index]
109
- raise ArgumentError.new("Placeholder not found") if placeholder.nil?
148
+ raise ArgumentError.new("Placeholder not found") if !placeholder
110
149
 
111
150
  @io.seek(placeholder[:header_start_position])
112
151
  old_write_only_io = @write_only_io
113
- @write_only_io = PlaceholderStream.new(
114
- @io,
115
- start_position: placeholder[:file_start_position],
116
- file_size: placeholder[:file_size]
117
- )
152
+ @write_only_io =
153
+ PlaceholderStream.new(
154
+ @io,
155
+ start_position: placeholder[:file_start_position],
156
+ file_size: placeholder[:file_size],
157
+ )
118
158
 
119
159
  yield self
120
160
 
@@ -129,25 +169,30 @@ module MiniTarball
129
169
  end
130
170
 
131
171
  def close
132
- ensure_not_closed!
172
+ ensure_not_closed
133
173
 
134
174
  @io.write("\0" * END_OF_TAR_BLOCK_SIZE)
135
175
  @io.close
136
176
  @closed = true
137
177
  end
138
178
 
139
- private
179
+ # :reek:FeatureEnvy
180
+ # :reek:ManualDispatch
181
+ private def ensure_valid_io(io)
182
+ unless io.respond_to?(:pos) && io.respond_to?(:write) && io.respond_to?(:close)
183
+ raise NoIOLikeObjectError.new("No IO object given")
184
+ end
185
+ end
140
186
 
141
- def ensure_valid_io!(io)
142
- raise "No IO object given" unless io.respond_to?(:pos) &&
143
- io.respond_to?(:write) && io.respond_to?(:close)
187
+ private def ensure_seekable_io
188
+ raise NoIOLikeObjectError.new("No seekable IO object given") unless @io.respond_to?(:seek)
144
189
  end
145
190
 
146
- def ensure_not_closed!
191
+ private def ensure_not_closed
147
192
  raise IOError.new("#{self.class} is closed") if closed?
148
193
  end
149
194
 
150
- def write_padding
195
+ private def write_padding
151
196
  padding_length = (Header::BLOCK_SIZE - @io.pos) % Header::BLOCK_SIZE
152
197
  @io.write("\0" * padding_length)
153
198
  end
data/lib/mini_tarball.rb CHANGED
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'mini_tarball/header'
4
- require 'mini_tarball/header_formatter'
5
- require 'mini_tarball/header_writer'
6
- require 'mini_tarball/streams/limited_size_stream'
7
- require 'mini_tarball/streams/placeholder_stream'
8
- require 'mini_tarball/streams/write_only_stream'
9
- require 'mini_tarball/writer'
10
- require 'mini_tarball/version'
3
+ require "mini_tarball/header"
4
+ require "mini_tarball/header_fields"
5
+ require "mini_tarball/header_formatter"
6
+ require "mini_tarball/header_writer"
7
+ require "mini_tarball/streams/limited_size_stream"
8
+ require "mini_tarball/streams/placeholder_stream"
9
+ require "mini_tarball/streams/write_only_stream"
10
+ require "mini_tarball/writer"
11
+ require "mini_tarball/version"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mini_tarball
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
- - Discourse
7
+ - Discourse Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-10-07 00:00:00.000000000 Z
11
+ date: 2022-12-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -67,7 +67,7 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: super_diff
70
+ name: rubycritic
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
@@ -94,6 +94,34 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: super_diff
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: syntax_tree
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
97
125
  description:
98
126
  email:
99
127
  executables: []
@@ -103,6 +131,7 @@ files:
103
131
  - LICENSE.txt
104
132
  - lib/mini_tarball.rb
105
133
  - lib/mini_tarball/header.rb
134
+ - lib/mini_tarball/header_fields.rb
106
135
  - lib/mini_tarball/header_formatter.rb
107
136
  - lib/mini_tarball/header_writer.rb
108
137
  - lib/mini_tarball/streams/limited_size_stream.rb