mini_tarball 0.2.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: bb6115ef818ae567ea4efad8646977a26cbb62f5164e656bd8d403dd1775ff38
4
- data.tar.gz: 5364a6eece1aa623c4bb900eaf50de383017dcd4b9285f5f7b3b52423638d9b2
3
+ metadata.gz: 313b7544e163106d7c251eb36ace29a96c22dfe43bc198310966155c18417260
4
+ data.tar.gz: fe252395b2daa1b7a8843ff27a0f6b87d96dff09031c485c42c217ad39b9ba52
5
5
  SHA512:
6
- metadata.gz: d8978adda8f400f47d37d037fc439d6d803538910aaa97a78cee3ec111b34616c0b3acd298a6a5b406a02a8e0d698e25743a5a4ff32a2e1b827835892621d186
7
- data.tar.gz: 543abee46d1e1bd3df2abdd0b0164f2e72363d65ec63a7ac8c120302c94a0fc247315fcb7c4541774ab08ef00178e4de1df355cb2699e0056f0d984e53f532bc
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
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniTarball
4
+ class WriteOutOfRangeError < StandardError
5
+ end
6
+
7
+ class LimitedSizeStream
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
11
+
12
+ def initialize(io, start_position:, max_file_size:)
13
+ @io = io
14
+ @start_position = start_position
15
+ @end_position = start_position + max_file_size
16
+ end
17
+
18
+ def write(data)
19
+ current_position = io.pos
20
+
21
+ if current_position < start_position || current_position + data.bytesize > end_position
22
+ raise WriteOutOfRangeError.new("Writing outside of limits not allowed")
23
+ end
24
+
25
+ io.write(data)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniTarball
4
+ class PlaceholderStream < LimitedSizeStream
5
+ def initialize(io, start_position:, file_size:)
6
+ super(io, start_position: start_position, max_file_size: file_size)
7
+ end
8
+
9
+ def write(data)
10
+ super(data)
11
+
12
+ if (current_position = io.pos) <= end_position
13
+ io.write("\0" * (end_position - current_position))
14
+ end
15
+ end
16
+ end
17
+ end
@@ -7,7 +7,7 @@ module MiniTarball
7
7
  end
8
8
 
9
9
  def write(data)
10
- @io.write (data)
10
+ @io.write(data)
11
11
  end
12
12
  end
13
13
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MiniTarball
4
- VERSION = "0.2.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,38 +32,68 @@ 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)
36
39
  @header_writer = HeaderWriter.new(@write_only_io)
37
40
  @closed = false
41
+ @placeholders = []
38
42
  end
39
43
 
40
- def add_file(name:, source_file_path:, mode: nil, uname: nil, gname: nil, uid: nil, gid: nil, mtime: nil)
41
- 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
42
60
 
43
- file = File.open(source_file_path, "rb")
44
61
  stat = File.stat(source_file_path)
45
62
 
46
- @header_writer.write(Header.new(
47
- name: name,
48
- size: stat.size,
49
- mode: mode || stat.mode,
50
- uid: uid || stat.uid,
51
- gid: gid || stat.gid,
52
- uname: uname || Etc.getpwuid(stat.uid).name,
53
- gname: gname || Etc.getgrgid(stat.gid).name,
54
- mtime: mtime || stat.mtime
55
- ))
56
-
57
- 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
+
58
78
  write_padding
59
79
  nil
60
80
  end
61
81
 
62
- def add_file_from_stream(name:, mode: 0644, uname: "nobody", gname: "nogroup", uid: nil, gid: nil, mtime: nil)
63
- 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
64
97
 
65
98
  header_start_position = @io.pos
66
99
  @header_writer.write(Header.new(name: name))
@@ -71,18 +104,63 @@ module MiniTarball
71
104
  write_padding
72
105
 
73
106
  @io.seek(header_start_position)
74
- @header_writer.write(Header.new(
75
- name: name,
76
- size: file_size,
77
- mode: mode,
78
- uid: uid,
79
- gid: gid,
80
- uname: uname,
81
- gname: gname,
82
- mtime: mtime || Time.now.utc
83
- ))
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
+ )
119
+
120
+ @io.seek(0, IO::SEEK_END)
121
+ nil
122
+ end
123
+
124
+ # :reek:DuplicateMethodCall { allow_calls: ['@io.pos'] }
125
+ # :reek:TooManyStatements
126
+ def add_file_placeholder(name:, file_size:)
127
+ ensure_not_closed
128
+
129
+ placeholder = {}
130
+ placeholder[:header_start_position] = @io.pos
131
+ @header_writer.write(Header.new(name: name, size: file_size))
132
+
133
+ placeholder[:file_start_position] = @io.pos
134
+ @io.write("\0" * file_size)
135
+ placeholder[:file_size] = file_size
84
136
 
137
+ write_padding
138
+
139
+ @placeholders << placeholder
140
+ @placeholders.size - 1
141
+ end
142
+
143
+ # :reek:TooManyStatements
144
+ def with_placeholder(index)
145
+ ensure_seekable_io
146
+
147
+ placeholder = @placeholders[index]
148
+ raise ArgumentError.new("Placeholder not found") if !placeholder
149
+
150
+ @io.seek(placeholder[:header_start_position])
151
+ old_write_only_io = @write_only_io
152
+ @write_only_io =
153
+ PlaceholderStream.new(
154
+ @io,
155
+ start_position: placeholder[:file_start_position],
156
+ file_size: placeholder[:file_size],
157
+ )
158
+
159
+ yield self
160
+
161
+ @write_only_io = old_write_only_io
85
162
  @io.seek(0, IO::SEEK_END)
163
+
86
164
  nil
87
165
  end
88
166
 
@@ -91,25 +169,30 @@ module MiniTarball
91
169
  end
92
170
 
93
171
  def close
94
- ensure_not_closed!
172
+ ensure_not_closed
95
173
 
96
174
  @io.write("\0" * END_OF_TAR_BLOCK_SIZE)
97
175
  @io.close
98
176
  @closed = true
99
177
  end
100
178
 
101
- 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
102
186
 
103
- def ensure_valid_io!(io)
104
- raise "No IO object given" unless io.respond_to?(:pos) &&
105
- 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)
106
189
  end
107
190
 
108
- def ensure_not_closed!
191
+ private def ensure_not_closed
109
192
  raise IOError.new("#{self.class} is closed") if closed?
110
193
  end
111
194
 
112
- def write_padding
195
+ private def write_padding
113
196
  padding_length = (Header::BLOCK_SIZE - @io.pos) % Header::BLOCK_SIZE
114
197
  @io.write("\0" * padding_length)
115
198
  end
data/lib/mini_tarball.rb CHANGED
@@ -1,8 +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/write_only_stream'
7
- require 'mini_tarball/writer'
8
- 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.2.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: 2021-03-09 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,10 +131,13 @@ 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
137
+ - lib/mini_tarball/streams/limited_size_stream.rb
138
+ - lib/mini_tarball/streams/placeholder_stream.rb
139
+ - lib/mini_tarball/streams/write_only_stream.rb
108
140
  - lib/mini_tarball/version.rb
109
- - lib/mini_tarball/write_only_stream.rb
110
141
  - lib/mini_tarball/writer.rb
111
142
  homepage: https://github.com/discourse/mini_tarball
112
143
  licenses:
@@ -122,14 +153,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
122
153
  requirements:
123
154
  - - ">="
124
155
  - !ruby/object:Gem::Version
125
- version: 2.6.0
156
+ version: 2.7.0
126
157
  required_rubygems_version: !ruby/object:Gem::Requirement
127
158
  requirements:
128
159
  - - ">="
129
160
  - !ruby/object:Gem::Version
130
161
  version: '0'
131
162
  requirements: []
132
- rubygems_version: 3.1.4
163
+ rubygems_version: 3.1.6
133
164
  signing_key:
134
165
  specification_version: 4
135
166
  summary: A minimal implementation of the GNU Tar format.