mini_tarball 0.3.0 → 0.3.1

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 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