archive-tar-external 1.3.2 → 1.5.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 +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/{CHANGES → CHANGES.md} +39 -13
- data/Gemfile +3 -0
- data/LICENSE +177 -0
- data/MANIFEST.md +13 -0
- data/README.md +59 -0
- data/Rakefile +10 -15
- data/archive-tar-external.gemspec +22 -19
- data/certs/djberg96_pub.pem +26 -0
- data/doc/archive_tar_external.md +134 -0
- data/lib/archive-tar-external.rb +3 -0
- data/lib/archive/tar/external.rb +65 -75
- data/spec/archive_tar_external_spec.rb +234 -0
- data/spec/spec_helper.rb +6 -0
- metadata +71 -37
- metadata.gz.sig +0 -0
- data/MANIFEST +0 -8
- data/README +0 -43
- data/doc/tar_external.txt +0 -110
- data/test/test_archive_tar_external.rb +0 -208
@@ -0,0 +1,26 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIEcDCCAtigAwIBAgIBATANBgkqhkiG9w0BAQsFADA/MREwDwYDVQQDDAhkamJl
|
3
|
+
cmc5NjEVMBMGCgmSJomT8ixkARkWBWdtYWlsMRMwEQYKCZImiZPyLGQBGRYDY29t
|
4
|
+
MB4XDTE4MDMxODE1MjIwN1oXDTI4MDMxNTE1MjIwN1owPzERMA8GA1UEAwwIZGpi
|
5
|
+
ZXJnOTYxFTATBgoJkiaJk/IsZAEZFgVnbWFpbDETMBEGCgmSJomT8ixkARkWA2Nv
|
6
|
+
bTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBALgfaroVM6CI06cxr0/h
|
7
|
+
A+j+pc8fgpRgBVmHFaFunq28GPC3IvW7Nvc3Y8SnAW7pP1EQIbhlwRIaQzJ93/yj
|
8
|
+
u95KpkP7tA9erypnV7dpzBkzNlX14ACaFD/6pHoXoe2ltBxk3CCyyzx70mTqJpph
|
9
|
+
75IB03ni9a8yqn8pmse+s83bFJOAqddSj009sGPcQO+QOWiNxqYv1n5EHcvj2ebO
|
10
|
+
6hN7YTmhx7aSia4qL/quc4DlIaGMWoAhvML7u1fmo53CYxkKskfN8MOecq2vfEmL
|
11
|
+
iLu+SsVVEAufMDDFMXMJlvDsviolUSGMSNRTujkyCcJoXKYYxZSNtIiyd9etI0X3
|
12
|
+
ctu0uhrFyrMZXCedutvXNjUolD5r9KGBFSWH1R9u2I3n3SAyFF2yzv/7idQHLJJq
|
13
|
+
74BMnx0FIq6fCpu5slAipvxZ3ZkZpEXZFr3cIBtO1gFvQWW7E/Y3ijliWJS1GQFq
|
14
|
+
058qERadHGu1yu1dojmFRo6W2KZvY9al2yIlbkpDrD5MYQIDAQABo3cwdTAJBgNV
|
15
|
+
HRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQUFZsMapgzJimzsbaBG2Tm8j5e
|
16
|
+
AzgwHQYDVR0RBBYwFIESZGpiZXJnOTZAZ21haWwuY29tMB0GA1UdEgQWMBSBEmRq
|
17
|
+
YmVyZzk2QGdtYWlsLmNvbTANBgkqhkiG9w0BAQsFAAOCAYEAW2tnYixXQtKxgGXq
|
18
|
+
/3iSWG2bLwvxS4go3srO+aRXZHrFUMlJ5W0mCxl03aazxxKTsVVpZD8QZxvK91OQ
|
19
|
+
h9zr9JBYqCLcCVbr8SkmYCi/laxIZxsNE5YI8cC8vvlLI7AMgSfPSnn/Epq1GjGY
|
20
|
+
6L1iRcEDtanGCIvjqlCXO9+BmsnCfEVehqZkQHeYczA03tpOWb6pon2wzvMKSsKH
|
21
|
+
ks0ApVdstSLz1kzzAqem/uHdG9FyXdbTAwH1G4ZPv69sQAFAOCgAqYmdnzedsQtE
|
22
|
+
1LQfaQrx0twO+CZJPcRLEESjq8ScQxWRRkfuh2VeR7cEU7L7KqT10mtUwrvw7APf
|
23
|
+
DYoeCY9KyjIBjQXfbj2ke5u1hZj94Fsq9FfbEQg8ygCgwThnmkTrrKEiMSs3alYR
|
24
|
+
ORVCZpRuCPpmC8qmqxUnARDArzucjaclkxjLWvCVHeFa9UP7K3Nl9oTjJNv+7/jM
|
25
|
+
WZs4eecIcUc4tKdHxcAJ0MO/Dkqq7hGaiHpwKY76wQ1+8xAh
|
26
|
+
-----END CERTIFICATE-----
|
@@ -0,0 +1,134 @@
|
|
1
|
+
## Description
|
2
|
+
A simple tar interface using external system calls.
|
3
|
+
|
4
|
+
## Synopsis
|
5
|
+
```ruby
|
6
|
+
# Assuming we have three .txt files, t1.txt, t2.txt, t3.txt ...
|
7
|
+
require 'archive/tar/external'
|
8
|
+
include Archive
|
9
|
+
|
10
|
+
t = Tar::External.new("myfile.tar")
|
11
|
+
|
12
|
+
t.create_archive("*.txt")
|
13
|
+
t.compress_archive("bzip2") # 'myfile.tar.bz2' now exists
|
14
|
+
|
15
|
+
t.uncompress_archive("bunzip2")
|
16
|
+
|
17
|
+
t.archive_name # "myfile.tar"
|
18
|
+
t.archive_info # ["t1.txt","t2.txt","t3.txt"]
|
19
|
+
|
20
|
+
t.add_to_archive("t4.txt","t5.txt")
|
21
|
+
t.expand_archive
|
22
|
+
```
|
23
|
+
|
24
|
+
## Constants
|
25
|
+
`VERSION`- The current version number of this library. This is a string.
|
26
|
+
|
27
|
+
## Singleton Methods
|
28
|
+
`.new(archive_name, pattern=nil, program=nil)`
|
29
|
+
|
30
|
+
Creates an instance of an Archive::Tar::External object. The `archive_name` is
|
31
|
+
the name of the tarball. While a '.tar' extension is recommended based on
|
32
|
+
years of convention, it is not enforced.
|
33
|
+
|
34
|
+
If `pattern` is provided, then the `create_archive` method is called internally.
|
35
|
+
|
36
|
+
If `program` is provided, then the `compress_archive` method is called internally.
|
37
|
+
|
38
|
+
Note that `archive_name` name must be a string, or a `TypeError` is raised.
|
39
|
+
|
40
|
+
`.expand_archive(archive_name, file1 [, file2, ...])`
|
41
|
+
|
42
|
+
Identical to the instance method of the same name, except that you must
|
43
|
+
specify the `archive_name`, and the tar program is hard coded to `tar xf`.
|
44
|
+
|
45
|
+
`.uncompress_archive(archive_name, program='gunzip')`
|
46
|
+
|
47
|
+
Identical to the instance method of the same name, except that you must
|
48
|
+
specify the +archive_name+ as the first argument.
|
49
|
+
|
50
|
+
## Instance Methods
|
51
|
+
```
|
52
|
+
#add(file1 [, file2, ...])
|
53
|
+
#add_to_archive(file1 [, file2, ...])
|
54
|
+
```
|
55
|
+
|
56
|
+
Adds a list of files to the current archive. At least one file must be
|
57
|
+
provided or an `Archive::Tar::Error` is raised.
|
58
|
+
|
59
|
+
```
|
60
|
+
#archive_info
|
61
|
+
#info
|
62
|
+
```
|
63
|
+
|
64
|
+
Returns an array of file names that are included within the tarball.
|
65
|
+
|
66
|
+
`#archive_name`
|
67
|
+
|
68
|
+
Returns the current archive name.
|
69
|
+
|
70
|
+
`#archive_name=`
|
71
|
+
|
72
|
+
Sets the current archive name.
|
73
|
+
|
74
|
+
```
|
75
|
+
#compress(program="gzip")
|
76
|
+
#compress_archive(program="gzip")
|
77
|
+
```
|
78
|
+
|
79
|
+
Compresses the tarball using the program you pass to this method. The default is "gzip".
|
80
|
+
|
81
|
+
Note that any arguments you want to be passed along with the program can simply
|
82
|
+
be included as part of the program, e.g. "gzip -f".
|
83
|
+
|
84
|
+
```
|
85
|
+
#create(file_pattern)
|
86
|
+
#create_archive(file_pattern, options = 'cf')
|
87
|
+
```
|
88
|
+
|
89
|
+
Creates a new tarball, including those files which match `file_pattern`
|
90
|
+
using `options`, which are set to 'cf' (create file) by default.
|
91
|
+
|
92
|
+
```
|
93
|
+
#expand_archive(files=nil)
|
94
|
+
#extract_archive(files=nil)
|
95
|
+
```
|
96
|
+
|
97
|
+
Expands the contents of the tarball. Note that this method does NOT delete the tarball.
|
98
|
+
|
99
|
+
If file names are provided, then only those files are extracted.
|
100
|
+
|
101
|
+
`#tar_program`
|
102
|
+
|
103
|
+
Returns the name of the tar program used. The default is "tar".
|
104
|
+
|
105
|
+
`#tar_program=(program_name)`
|
106
|
+
|
107
|
+
Sets the name of the tar program to be used.
|
108
|
+
|
109
|
+
```
|
110
|
+
#uncompress(program="gunzip")
|
111
|
+
#uncompress_archive(program="gunzip")
|
112
|
+
```
|
113
|
+
|
114
|
+
Uncompresses the tarball using the program you pass to this method. The default is "gunzip".
|
115
|
+
|
116
|
+
Like the `compress_archive` method, you can pass arguments along as part of the method call.
|
117
|
+
|
118
|
+
```
|
119
|
+
#update(files)
|
120
|
+
#update_archive(files)
|
121
|
+
```
|
122
|
+
|
123
|
+
Updates the given `files` in the archive, i.e they are added if they
|
124
|
+
are not already in the archive or have been modified.
|
125
|
+
|
126
|
+
## Exceptions
|
127
|
+
`Archive::Tar::Error`
|
128
|
+
|
129
|
+
Raised if something goes wrong during the execution of any methods that
|
130
|
+
use the tar command internally.
|
131
|
+
|
132
|
+
`Archive::Tar::CompressError`
|
133
|
+
|
134
|
+
Raised if something goes wrong during the `compress_archive` or `uncompress_archive` methods.
|
data/lib/archive/tar/external.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'open3'
|
2
4
|
|
3
5
|
# The Archive module serves as a namespace only.
|
4
6
|
module Archive
|
5
|
-
|
6
7
|
# The Tar class serves as a toplevel class namespace only.
|
7
8
|
class Tar
|
8
|
-
|
9
9
|
# Raised if something goes wrong during the execution of any methods
|
10
10
|
# which use the tar command internally.
|
11
11
|
class Error < StandardError; end
|
@@ -17,40 +17,43 @@ module Archive
|
|
17
17
|
# This class encapsulates tar & zip operations.
|
18
18
|
class Tar::External
|
19
19
|
# The version of the archive-tar-external library.
|
20
|
-
VERSION = '1.
|
20
|
+
VERSION = '1.5.0'
|
21
21
|
|
22
22
|
# The name of the archive file to be used, e.g. "test.tar"
|
23
23
|
attr_accessor :archive_name
|
24
24
|
|
25
|
-
# The name of the tar program you wish to use.
|
25
|
+
# The name of the tar program you wish to use. The default is "tar".
|
26
26
|
attr_accessor :tar_program
|
27
27
|
|
28
28
|
# The name of the archive file after compression, e.g. "test.tar.gz"
|
29
29
|
attr_reader :compressed_archive_name
|
30
30
|
|
31
|
-
#
|
32
|
-
|
31
|
+
# The format of the archive file. The default is "pax".
|
32
|
+
attr_reader :format
|
33
|
+
|
34
|
+
# Returns an Archive::Tar::External object. The +archive_name+ is the
|
35
|
+
# name of the tarball. While a .tar extension is recommended based on
|
33
36
|
# years of convention, it is not enforced.
|
34
37
|
#
|
35
38
|
# Note that this does not actually create the archive unless you
|
36
|
-
# pass a value to +file_pattern+.
|
39
|
+
# pass a value to +file_pattern+. This then becomes a shortcut for
|
37
40
|
# Archive::Tar::External.new + Archive::Tar::External#create_archive.
|
38
41
|
#
|
39
42
|
# If +program+ is provided, then it compresses the archive as well by
|
40
43
|
# calling Archive::Tar::External#compress_archive internally.
|
41
44
|
#
|
42
|
-
|
45
|
+
# You may also specify an archive format. As of version 1.5, the
|
46
|
+
# default is 'pax'. Previous versions used whatever your tar program
|
47
|
+
# used by default.
|
48
|
+
#
|
49
|
+
def initialize(archive_name, file_pattern = nil, program = nil, format = 'pax')
|
43
50
|
@archive_name = archive_name.to_s
|
44
51
|
@compressed_archive_name = nil
|
45
52
|
@tar_program = 'tar'
|
53
|
+
@format = 'pax'
|
46
54
|
|
47
|
-
if file_pattern
|
48
|
-
|
49
|
-
end
|
50
|
-
|
51
|
-
if program
|
52
|
-
compress_archive(program)
|
53
|
-
end
|
55
|
+
create_archive(file_pattern) if file_pattern
|
56
|
+
compress_archive(program) if program
|
54
57
|
end
|
55
58
|
|
56
59
|
# Assign a compressed archive name. This autogenerates the archive_name
|
@@ -62,9 +65,9 @@ module Archive
|
|
62
65
|
# that you want to uncompress, and want to have a Tar::External object
|
63
66
|
# around. Otherwise, use the class method Tar::External.uncompress.
|
64
67
|
#
|
65
|
-
def compressed_archive_name=(name, ext=File.extname(name))
|
68
|
+
def compressed_archive_name=(name, ext = File.extname(name))
|
66
69
|
if ext.downcase == '.tgz'
|
67
|
-
@archive_name = File.basename(name, ext.downcase)
|
70
|
+
@archive_name = File.basename(name, ext.downcase) << '.tar'
|
68
71
|
else
|
69
72
|
@archive_name = File.basename(name, ext)
|
70
73
|
end
|
@@ -72,24 +75,23 @@ module Archive
|
|
72
75
|
end
|
73
76
|
|
74
77
|
# Creates the archive using +file_pattern+ using +options+ or 'cf'
|
75
|
-
# (create file) by default.
|
78
|
+
# (create file) by default. The 'f' option should always be present
|
79
|
+
# and always be last.
|
76
80
|
#
|
77
81
|
# Raises an Archive::Tar::Error if a failure occurs.
|
78
82
|
#
|
79
83
|
def create_archive(file_pattern, options = 'cf')
|
80
|
-
cmd = "#{@tar_program} #{options} #{@archive_name} #{file_pattern}"
|
84
|
+
cmd = "#{@tar_program} --format #{@format} -#{options} #{@archive_name} #{file_pattern}"
|
81
85
|
|
82
|
-
Open3.popen3(cmd)
|
86
|
+
Open3.popen3(cmd) do |_tar_in, _tar_out, tar_err|
|
83
87
|
err = tar_err.gets
|
84
|
-
if err
|
85
|
-
|
86
|
-
end
|
87
|
-
}
|
88
|
+
raise Error, err.chomp if err
|
89
|
+
end
|
88
90
|
|
89
91
|
self
|
90
92
|
end
|
91
93
|
|
92
|
-
alias
|
94
|
+
alias create create_archive
|
93
95
|
|
94
96
|
# Compresses the archive with +program+, or gzip if no program is
|
95
97
|
# provided. If you want to pass arguments to +program+, merely include
|
@@ -97,10 +99,10 @@ module Archive
|
|
97
99
|
#
|
98
100
|
# Any errors that occur here will raise a Tar::CompressError.
|
99
101
|
#
|
100
|
-
def compress_archive(program='gzip')
|
102
|
+
def compress_archive(program = 'gzip')
|
101
103
|
cmd = "#{program} #{@archive_name}"
|
102
104
|
|
103
|
-
Open3.popen3(cmd)
|
105
|
+
Open3.popen3(cmd) do |_prog_in, _prog_out, prog_err|
|
104
106
|
err = prog_err.gets
|
105
107
|
raise CompressError, err.chomp if err
|
106
108
|
|
@@ -108,12 +110,12 @@ module Archive
|
|
108
110
|
# reliable way to do this, but this should work 99% of the time.
|
109
111
|
name = Dir["#{@archive_name}.{gz,bz2,cpio,zip}"].first
|
110
112
|
@compressed_archive_name = name
|
111
|
-
|
113
|
+
end
|
112
114
|
|
113
115
|
self
|
114
116
|
end
|
115
117
|
|
116
|
-
alias
|
118
|
+
alias compress compress_archive
|
117
119
|
|
118
120
|
# Uncompresses the tarball using the program you pass to this method. The
|
119
121
|
# default is "gunzip". Just as for +compress_archive+, you can pass
|
@@ -125,33 +127,31 @@ module Archive
|
|
125
127
|
#
|
126
128
|
# Any errors that occur here will raise a Tar::CompressError.
|
127
129
|
#
|
128
|
-
def uncompress_archive(program=
|
129
|
-
unless @compressed_archive_name
|
130
|
-
raise CompressError, "no compressed file found"
|
131
|
-
end
|
130
|
+
def uncompress_archive(program = 'gunzip')
|
131
|
+
raise CompressError, 'no compressed file found' unless @compressed_archive_name
|
132
132
|
|
133
133
|
cmd = "#{program} #{@compressed_archive_name}"
|
134
134
|
|
135
|
-
Open3.popen3(cmd)
|
135
|
+
Open3.popen3(cmd) do |_prog_in, _prog_out, prog_err|
|
136
136
|
err = prog_err.gets
|
137
137
|
raise CompressError, err.chomp if err
|
138
138
|
@compressed_archive_name = nil
|
139
|
-
|
139
|
+
end
|
140
140
|
self
|
141
141
|
end
|
142
142
|
|
143
|
-
alias
|
143
|
+
alias uncompress uncompress_archive
|
144
144
|
|
145
145
|
# Uncompress an existing archive, using +program+ to uncompress it.
|
146
146
|
# The default decompression program is gunzip.
|
147
147
|
#
|
148
|
-
def self.uncompress_archive(archive, program='gunzip')
|
148
|
+
def self.uncompress_archive(archive, program = 'gunzip')
|
149
149
|
cmd = "#{program} #{archive}"
|
150
150
|
|
151
|
-
Open3.popen3(cmd)
|
151
|
+
Open3.popen3(cmd) do |_prog_in, _prog_out, prog_err|
|
152
152
|
err = prog_err.gets
|
153
153
|
raise CompressError, err.chomp if err
|
154
|
-
|
154
|
+
end
|
155
155
|
end
|
156
156
|
|
157
157
|
class << self
|
@@ -164,58 +164,54 @@ module Archive
|
|
164
164
|
def archive_info
|
165
165
|
result = []
|
166
166
|
cmd = "#{@tar_program} tf #{@archive_name}"
|
167
|
-
|
167
|
+
|
168
|
+
Open3.popen3(cmd) do |_ain, aout, aerr|
|
168
169
|
err = aerr.gets
|
169
|
-
if err
|
170
|
-
raise Error, err.chomp
|
171
|
-
end
|
170
|
+
raise Error, err.chomp if err
|
172
171
|
|
173
|
-
while output = aout.gets
|
172
|
+
while (output = aout.gets)
|
174
173
|
result << output.chomp
|
175
174
|
end
|
176
|
-
|
175
|
+
end
|
176
|
+
|
177
177
|
result
|
178
178
|
end
|
179
179
|
|
180
|
-
alias
|
180
|
+
alias info archive_info
|
181
181
|
|
182
182
|
# Adds +files+ to an already existing archive.
|
183
183
|
#
|
184
184
|
def add_to_archive(*files)
|
185
|
-
if files.empty?
|
186
|
-
raise Error, "there must be at least one file specified"
|
187
|
-
end
|
185
|
+
raise Error, 'there must be at least one file specified' if files.empty?
|
188
186
|
|
189
|
-
cmd = "#{@tar_program} rf #{@archive_name} #{files.join(
|
187
|
+
cmd = "#{@tar_program} rf #{@archive_name} #{files.join(' ')}"
|
190
188
|
|
191
|
-
Open3.popen3(cmd)
|
189
|
+
Open3.popen3(cmd) do |_ain, _aout, aerr|
|
192
190
|
err = aerr.gets
|
193
191
|
raise Error, err.chomp if err
|
194
|
-
|
192
|
+
end
|
195
193
|
self
|
196
194
|
end
|
197
195
|
|
198
|
-
alias
|
196
|
+
alias add add_to_archive
|
199
197
|
|
200
198
|
# Updates the given +files+ in the archive, i.e. they are added if they
|
201
199
|
# are not already in the archive or have been modified.
|
202
200
|
#
|
203
201
|
def update_archive(*files)
|
204
|
-
if files.empty?
|
205
|
-
raise Error, "there must be at least one file specified"
|
206
|
-
end
|
202
|
+
raise Error, 'there must be at least one file specified' if files.empty?
|
207
203
|
|
208
|
-
cmd = "#{@tar_program} uf #{@archive_name} #{files.join(
|
204
|
+
cmd = "#{@tar_program} uf #{@archive_name} #{files.join(' ')}"
|
209
205
|
|
210
|
-
Open3.popen3(cmd)
|
206
|
+
Open3.popen3(cmd) do |_ain, _aout, aerr|
|
211
207
|
err = aerr.gets
|
212
208
|
raise Error, err.chomp if err
|
213
|
-
|
209
|
+
end
|
214
210
|
|
215
211
|
self
|
216
212
|
end
|
217
213
|
|
218
|
-
alias
|
214
|
+
alias update update_archive
|
219
215
|
|
220
216
|
# Expands the contents of the tarball. It does NOT delete the tarball.
|
221
217
|
# If +files+ are provided, then only those files are extracted.
|
@@ -227,22 +223,19 @@ module Archive
|
|
227
223
|
#
|
228
224
|
def extract_archive(*files)
|
229
225
|
cmd = "#{@tar_program} xf #{@archive_name}"
|
226
|
+
cmd = "#{cmd} #{files.join(' ')}" unless files.empty?
|
230
227
|
|
231
|
-
|
232
|
-
cmd << " " << files.join(" ")
|
233
|
-
end
|
234
|
-
|
235
|
-
Open3.popen3(cmd){ |ain, aout, aerr|
|
228
|
+
Open3.popen3(cmd) do |_ain, _aout, aerr|
|
236
229
|
err = aerr.gets
|
237
230
|
raise Error, err.chomp if err
|
238
|
-
|
231
|
+
end
|
239
232
|
|
240
233
|
self
|
241
234
|
end
|
242
235
|
|
243
|
-
alias
|
244
|
-
alias
|
245
|
-
alias
|
236
|
+
alias expand_archive extract_archive
|
237
|
+
alias extract extract_archive
|
238
|
+
alias expand extract_archive
|
246
239
|
|
247
240
|
# A class method that behaves identically to the equivalent instance
|
248
241
|
# method, except that you must specifiy that tarball as the first
|
@@ -250,15 +243,12 @@ module Archive
|
|
250
243
|
#
|
251
244
|
def self.extract_archive(archive, *files)
|
252
245
|
cmd = "tar xf #{archive}"
|
246
|
+
cmd = "#{cmd} #{files.join(' ')}" unless files.empty?
|
253
247
|
|
254
|
-
|
255
|
-
cmd << " " << files.join(" ")
|
256
|
-
end
|
257
|
-
|
258
|
-
Open3.popen3(cmd){ |ain, aout, aerr|
|
248
|
+
Open3.popen3(cmd) do |_ain, _aout, aerr|
|
259
249
|
err = aerr.gets
|
260
250
|
raise Error, err.chomp if err
|
261
|
-
|
251
|
+
end
|
262
252
|
|
263
253
|
self
|
264
254
|
end
|