mini_tarball 0.2.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: 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.