Ascii85 1.1.1 → 2.0.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 +4 -4
- data/Ascii85.gemspec +15 -14
- data/CHANGELOG.md +70 -0
- data/Gemfile +3 -1
- data/README.md +28 -12
- data/Rakefile +5 -3
- data/bin/ascii85 +79 -56
- data/lib/Ascii85/version.rb +1 -1
- data/lib/ascii85.rb +409 -163
- data/spec/bin/cli_spec.rb +211 -0
- data/spec/lib/ascii85_spec.rb +171 -103
- metadata +19 -25
- data/History.txt +0 -37
data/lib/ascii85.rb
CHANGED
@@ -1,220 +1,466 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
3
|
+
require 'stringio'
|
4
4
|
|
5
5
|
#
|
6
6
|
# Ascii85 is an implementation of Adobe's binary-to-text encoding of the
|
7
7
|
# same name in pure Ruby.
|
8
8
|
#
|
9
|
-
# See http://
|
10
|
-
#
|
11
|
-
# the format.
|
9
|
+
# See http://en.wikipedia.org/wiki/Ascii85 for more information about the
|
10
|
+
# format.
|
12
11
|
#
|
13
12
|
# Author:: Johannes Holzfuß (johannes@holzfuss.name)
|
14
13
|
# License:: Distributed under the MIT License (see LICENSE file)
|
15
14
|
#
|
15
|
+
module Ascii85
|
16
|
+
class << self
|
17
|
+
EMPTY_STRING = ''.dup.force_encoding(Encoding::ASCII_8BIT)
|
18
|
+
START_MARKER = '<~'.dup.force_encoding(Encoding::ASCII_8BIT)
|
19
|
+
ENDING_MARKER = '~>'.dup.force_encoding(Encoding::ASCII_8BIT)
|
20
|
+
LINE_BREAK = "\n".dup.force_encoding(Encoding::ASCII_8BIT)
|
16
21
|
|
22
|
+
#
|
23
|
+
# Encodes the bytes of the given String or IO-like object as Ascii85.
|
24
|
+
#
|
25
|
+
# @param str_or_io [String, IO] The input to encode
|
26
|
+
# @param wrap_lines [Integer, false] The line length for wrapping, or +false+ for no wrapping
|
27
|
+
# @param out [IO, nil] An optional IO-like object to write the output to
|
28
|
+
#
|
29
|
+
# @return [String, IO] The encoded String or the output IO object that was passed in
|
30
|
+
#
|
31
|
+
# @example Encoding a simple String
|
32
|
+
# Ascii85.encode("Ruby")
|
33
|
+
# # => <~;KZGo~>
|
34
|
+
#
|
35
|
+
# @example Encoding with line wrapping
|
36
|
+
# Ascii85.encode("Supercalifragilisticexpialidocious", 15)
|
37
|
+
# # => <~;g!%jEarNoBkD
|
38
|
+
# # BoB5)0rF*),+AU&
|
39
|
+
# # 0.@;KXgDe!L"F`R
|
40
|
+
# # ~>
|
41
|
+
#
|
42
|
+
# @example Encoding without line wrapping
|
43
|
+
# Ascii85.encode("Supercalifragilisticexpialidocious", false)
|
44
|
+
# # => <~;g!%jEarNoBkDBoB5)0rF*),+AU&0.@;KXgDe!L"F`R~>
|
45
|
+
#
|
46
|
+
# @example Encoding from an IO-like object
|
47
|
+
# input = StringIO.new("Ruby")
|
48
|
+
# Ascii85.encode(input)
|
49
|
+
# # => "<~;KZGo~>"
|
50
|
+
#
|
51
|
+
# @example Encoding to an IO object
|
52
|
+
# output = StringIO.new
|
53
|
+
# Ascii85.encode("Ruby", out: output)
|
54
|
+
# # => output (with "<~;KZGo~>" written to it)
|
55
|
+
#
|
56
|
+
def encode(str_or_io, wrap_lines = 80, out: nil)
|
57
|
+
reader = if io_like?(str_or_io)
|
58
|
+
str_or_io
|
59
|
+
else
|
60
|
+
StringIO.new(str_or_io.to_s, 'rb')
|
61
|
+
end
|
62
|
+
|
63
|
+
return EMPTY_STRING.dup if reader.eof?
|
64
|
+
|
65
|
+
# Setup buffered Reader and Writers
|
66
|
+
bufreader = BufferedReader.new(reader, unencoded_chunk_size)
|
67
|
+
bufwriter = BufferedWriter.new(out || StringIO.new(String.new, 'wb'), encoded_chunk_size)
|
68
|
+
writer = wrap_lines ? Wrapper.new(bufwriter, wrap_lines) : DummyWrapper.new(bufwriter)
|
69
|
+
|
70
|
+
padding = unfrozen_binary_copy("\0\0\0\0")
|
71
|
+
tuplebuf = unfrozen_binary_copy('!!!!!')
|
72
|
+
exclamations = unfrozen_binary_copy('!!!!!')
|
73
|
+
z = unfrozen_binary_copy('z')
|
74
|
+
|
75
|
+
bufreader.each_chunk do |chunk|
|
76
|
+
chunk.unpack('N*').each do |word|
|
77
|
+
# Encode each big-endian 32-bit word into a 5-character tuple (except
|
78
|
+
# for 0, which encodes to 'z')
|
79
|
+
if word.zero?
|
80
|
+
writer.write(z)
|
81
|
+
else
|
82
|
+
word, b0 = word.divmod(85)
|
83
|
+
word, b1 = word.divmod(85)
|
84
|
+
word, b2 = word.divmod(85)
|
85
|
+
word, b3 = word.divmod(85)
|
86
|
+
b4 = word
|
87
|
+
|
88
|
+
tuplebuf.setbyte(0, b4 + 33)
|
89
|
+
tuplebuf.setbyte(1, b3 + 33)
|
90
|
+
tuplebuf.setbyte(2, b2 + 33)
|
91
|
+
tuplebuf.setbyte(3, b1 + 33)
|
92
|
+
tuplebuf.setbyte(4, b0 + 33)
|
93
|
+
|
94
|
+
writer.write(tuplebuf)
|
95
|
+
end
|
96
|
+
end
|
17
97
|
|
18
|
-
|
19
|
-
#
|
20
|
-
# Encodes the bytes of the given String as Ascii85.
|
21
|
-
#
|
22
|
-
# If +wrap_lines+ evaluates to +false+, the output will be returned as
|
23
|
-
# a single long line. Otherwise #encode formats the output into lines
|
24
|
-
# of length +wrap_lines+ (minimum is 2).
|
25
|
-
#
|
26
|
-
# Ascii85.encode("Ruby")
|
27
|
-
# => <~;KZGo~>
|
28
|
-
#
|
29
|
-
# Ascii85.encode("Supercalifragilisticexpialidocious", 15)
|
30
|
-
# => <~;g!%jEarNoBkD
|
31
|
-
# BoB5)0rF*),+AU&
|
32
|
-
# 0.@;KXgDe!L"F`R
|
33
|
-
# ~>
|
34
|
-
#
|
35
|
-
# Ascii85.encode("Supercalifragilisticexpialidocious", false)
|
36
|
-
# => <~;g!%jEarNoBkDBoB5)0rF*),+AU&0.@;KXgDe!L"F`R~>
|
37
|
-
#
|
38
|
-
#
|
39
|
-
def self.encode(str, wrap_lines = 80)
|
40
|
-
to_encode = str.to_s
|
41
|
-
return '' if to_encode.empty?
|
42
|
-
|
43
|
-
# Deal with multi-byte encodings
|
44
|
-
if to_encode.respond_to?(:bytesize)
|
45
|
-
input_size = to_encode.bytesize
|
46
|
-
else
|
47
|
-
input_size = to_encode.size
|
48
|
-
end
|
98
|
+
next if (chunk.bytesize & 0b11).zero?
|
49
99
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
100
|
+
# If we have leftover bytes, we need to zero-pad to a multiple of four
|
101
|
+
# before converting to a 32-bit word.
|
102
|
+
padding_length = (-chunk.bytesize) % 4
|
103
|
+
trailing = chunk[-(4 - padding_length)..]
|
104
|
+
word = (trailing + padding[0...padding_length]).unpack1('N')
|
105
|
+
|
106
|
+
# Encode the last word and cut off any padding
|
107
|
+
if word.zero?
|
108
|
+
writer.write(exclamations[0..(4 - padding_length)])
|
109
|
+
else
|
110
|
+
word, b0 = word.divmod(85)
|
111
|
+
word, b1 = word.divmod(85)
|
112
|
+
word, b2 = word.divmod(85)
|
113
|
+
word, b3 = word.divmod(85)
|
114
|
+
b4 = word
|
115
|
+
|
116
|
+
tuplebuf.setbyte(0, b4 + 33)
|
117
|
+
tuplebuf.setbyte(1, b3 + 33)
|
118
|
+
tuplebuf.setbyte(2, b2 + 33)
|
119
|
+
tuplebuf.setbyte(3, b1 + 33)
|
120
|
+
tuplebuf.setbyte(4, b0 + 33)
|
121
|
+
|
122
|
+
writer.write(tuplebuf[0..(4 - padding_length)])
|
65
123
|
end
|
66
|
-
tmp.reverse
|
67
124
|
end
|
68
|
-
end
|
69
125
|
|
70
|
-
|
71
|
-
|
72
|
-
|
126
|
+
# If no output IO-object was provided, extract the encoded String from the
|
127
|
+
# default StringIO writer. We force the encoding to 'ASCII-8BIT' to work
|
128
|
+
# around a TruffleRuby bug.
|
129
|
+
return writer.finish.io.string.force_encoding(Encoding::ASCII_8BIT) if out.nil?
|
130
|
+
|
131
|
+
# Otherwise we make sure to flush the output writer, and then return it.
|
132
|
+
writer.finish.io
|
73
133
|
end
|
74
134
|
|
75
|
-
#
|
76
|
-
|
135
|
+
# Searches through a String and extracts the first substring enclosed by '<~' and '~>'.
|
136
|
+
#
|
137
|
+
# @param str [String] The String to search through
|
138
|
+
#
|
139
|
+
# @return [String] The extracted substring, or an empty String if no valid delimiters are found
|
140
|
+
#
|
141
|
+
# @example Extracting Ascii85 content
|
142
|
+
# Ascii85.extract("Foo<~;KZGo~>Bar<~z~>Baz")
|
143
|
+
# # => ";KZGo"
|
144
|
+
#
|
145
|
+
# @example When no delimiters are found
|
146
|
+
# Ascii85.extract("No delimiters")
|
147
|
+
# # => ""
|
148
|
+
#
|
149
|
+
# @note This method only accepts a String, not an IO-like object, as the entire input
|
150
|
+
# needs to be available to ensure validity.
|
151
|
+
#
|
152
|
+
def extract(str)
|
153
|
+
input = str.to_s
|
154
|
+
|
155
|
+
# Make sure the delimiter Strings have the correct encoding.
|
156
|
+
opening_delim = '<~'.encode(input.encoding)
|
157
|
+
closing_delim = '~>'.encode(input.encoding)
|
77
158
|
|
78
|
-
|
79
|
-
|
80
|
-
|
159
|
+
# Get the positions of the opening/closing delimiters. If there is no pair
|
160
|
+
# of opening/closing delimiters, return an unfrozen empty String.
|
161
|
+
(start_pos = input.index(opening_delim)) or return EMPTY_STRING.dup
|
162
|
+
(end_pos = input.index(closing_delim, start_pos + 2)) or return EMPTY_STRING.dup
|
163
|
+
|
164
|
+
# Get the String inside the delimiter-pair
|
165
|
+
input[(start_pos + 2)...end_pos]
|
81
166
|
end
|
82
167
|
|
83
|
-
#
|
84
|
-
|
168
|
+
#
|
169
|
+
# Searches through a String and decodes the first substring enclosed by '<~' and '~>'.
|
170
|
+
#
|
171
|
+
# @param str [String] The String containing Ascii85-encoded content
|
172
|
+
# @param out [IO, nil] An optional IO-like object to write the output to
|
173
|
+
#
|
174
|
+
# @return [String, IO] The decoded String (in ASCII-8BIT encoding) or the output IO object (if it was provided)
|
175
|
+
#
|
176
|
+
# @raise [Ascii85::DecodingError] When malformed input is encountered
|
177
|
+
#
|
178
|
+
# @example Decoding Ascii85 content
|
179
|
+
# Ascii85.decode("<~;KZGo~>")
|
180
|
+
# # => "Ruby"
|
181
|
+
#
|
182
|
+
# @example Decoding with multiple Ascii85 blocks present (ignores all but the first)
|
183
|
+
# Ascii85.decode("Foo<~;KZGo~>Bar<~87cURDZ~>Baz")
|
184
|
+
# # => "Ruby"
|
185
|
+
#
|
186
|
+
# @example When no delimiters are found
|
187
|
+
# Ascii85.decode("No delimiters")
|
188
|
+
# # => ""
|
189
|
+
#
|
190
|
+
# @example Decoding to an IO object
|
191
|
+
# output = StringIO.new
|
192
|
+
# Ascii85.decode("<~;KZGo~>", out: output)
|
193
|
+
# # => output (with "Ruby" written to it)
|
194
|
+
#
|
195
|
+
# @note This method only accepts a String, not an IO-like object, as the entire input
|
196
|
+
# needs to be available to ensure validity.
|
197
|
+
#
|
198
|
+
def decode(str, out: nil)
|
199
|
+
decode_raw(extract(str), out: out)
|
200
|
+
end
|
85
201
|
|
86
|
-
|
87
|
-
|
202
|
+
#
|
203
|
+
# Decodes the given raw Ascii85-encoded String or IO-like object.
|
204
|
+
#
|
205
|
+
# @param str_or_io [String, IO] The Ascii85-encoded input to decode
|
206
|
+
# @param out [IO, nil] An optional IO-like object to write the output to
|
207
|
+
#
|
208
|
+
# @return [String, IO] The decoded String (in ASCII-8BIT encoding) or the output IO object (if it was provided)
|
209
|
+
#
|
210
|
+
# @raise [Ascii85::DecodingError] When malformed input is encountered
|
211
|
+
#
|
212
|
+
# @example Decoding a raw Ascii85 String
|
213
|
+
# Ascii85.decode_raw(";KZGo")
|
214
|
+
# # => "Ruby"
|
215
|
+
#
|
216
|
+
# @example Decoding from an IO-like object
|
217
|
+
# input = StringIO.new(";KZGo")
|
218
|
+
# Ascii85.decode_raw(input)
|
219
|
+
# # => "Ruby"
|
220
|
+
#
|
221
|
+
# @example Decoding to an IO object
|
222
|
+
# output = StringIO.new
|
223
|
+
# Ascii85.decode_raw(";KZGo", out: output)
|
224
|
+
# # => output (with "Ruby" written to it)
|
225
|
+
#
|
226
|
+
# @note The input must not be enclosed in '<~' and '~>' delimiters.
|
227
|
+
#
|
228
|
+
def decode_raw(str_or_io, out: nil)
|
229
|
+
reader = if io_like?(str_or_io)
|
230
|
+
str_or_io
|
231
|
+
else
|
232
|
+
StringIO.new(str_or_io.to_s, 'rb')
|
233
|
+
end
|
234
|
+
|
235
|
+
# Return an unfrozen String on empty input
|
236
|
+
return EMPTY_STRING.dup if reader.eof?
|
237
|
+
|
238
|
+
# Setup buffered Reader and Writers
|
239
|
+
bufreader = BufferedReader.new(reader, encoded_chunk_size)
|
240
|
+
bufwriter = BufferedWriter.new(out || StringIO.new(String.new, 'wb'), unencoded_chunk_size)
|
241
|
+
|
242
|
+
# Populate the lookup table (caches the exponentiation)
|
243
|
+
lut = (0..4).map { |count| 85**(4 - count) }
|
244
|
+
|
245
|
+
# Decode
|
246
|
+
word = 0
|
247
|
+
count = 0
|
248
|
+
zeroes = unfrozen_binary_copy("\0\0\0\0")
|
249
|
+
wordbuf = zeroes.dup
|
250
|
+
|
251
|
+
bufreader.each_chunk do |chunk|
|
252
|
+
chunk.each_byte do |c|
|
253
|
+
case c.chr
|
254
|
+
when ' ', "\t", "\r", "\n", "\f", "\0"
|
255
|
+
# Ignore whitespace
|
256
|
+
next
|
257
|
+
|
258
|
+
when 'z'
|
259
|
+
raise(Ascii85::DecodingError, "Found 'z' inside Ascii85 5-tuple") unless count.zero?
|
260
|
+
|
261
|
+
# Expand z to 0-word
|
262
|
+
bufwriter.write(zeroes)
|
263
|
+
|
264
|
+
when '!'..'u'
|
265
|
+
# Decode 5 characters into a 4-byte word
|
266
|
+
word += (c - 33) * lut[count]
|
267
|
+
count += 1
|
268
|
+
|
269
|
+
if count == 5 && word > 0xffffffff
|
270
|
+
raise(Ascii85::DecodingError, "Invalid Ascii85 5-tuple (#{word} >= 2**32)")
|
271
|
+
elsif count == 5
|
272
|
+
b3 = word & 0xff; word >>= 8
|
273
|
+
b2 = word & 0xff; word >>= 8
|
274
|
+
b1 = word & 0xff; word >>= 8
|
275
|
+
b0 = word
|
276
|
+
|
277
|
+
wordbuf.setbyte(0, b0)
|
278
|
+
wordbuf.setbyte(1, b1)
|
279
|
+
wordbuf.setbyte(2, b2)
|
280
|
+
wordbuf.setbyte(3, b3)
|
281
|
+
|
282
|
+
bufwriter.write(wordbuf)
|
283
|
+
|
284
|
+
word = 0
|
285
|
+
count = 0
|
286
|
+
end
|
287
|
+
|
288
|
+
else
|
289
|
+
raise(Ascii85::DecodingError, "Illegal character inside Ascii85: #{c.chr.dump}")
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# We're done if all 5-tuples have been consumed
|
295
|
+
if count.zero?
|
296
|
+
bufwriter.flush
|
297
|
+
return out || bufwriter.io.string.force_encoding(Encoding::ASCII_8BIT)
|
298
|
+
end
|
299
|
+
|
300
|
+
raise(Ascii85::DecodingError, 'Last 5-tuple consists of single character') if count == 1
|
301
|
+
|
302
|
+
# Finish last, partially decoded 32-bit word
|
303
|
+
count -= 1
|
304
|
+
word += lut[count]
|
305
|
+
|
306
|
+
bufwriter.write((word >> 24).chr) if count >= 1
|
307
|
+
bufwriter.write(((word >> 16) & 0xff).chr) if count >= 2
|
308
|
+
bufwriter.write(((word >> 8) & 0xff).chr) if count == 3
|
309
|
+
bufwriter.flush
|
88
310
|
|
89
|
-
|
90
|
-
wrapped << to_wrap.slice(index, line_length)
|
311
|
+
out || bufwriter.io.string.force_encoding(Encoding::ASCII_8BIT)
|
91
312
|
end
|
92
313
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
314
|
+
private
|
315
|
+
|
316
|
+
# Copies the given String and forces the encoding of the returned copy to
|
317
|
+
# be Encoding::ASCII_8BIT.
|
318
|
+
def unfrozen_binary_copy(str)
|
319
|
+
str.dup.force_encoding(Encoding::ASCII_8BIT)
|
98
320
|
end
|
99
321
|
|
100
|
-
|
101
|
-
|
322
|
+
# Buffers an underlying IO object to increase efficiency. You do not need
|
323
|
+
# to use this directly.
|
324
|
+
#
|
325
|
+
# @private
|
326
|
+
#
|
327
|
+
class BufferedReader
|
328
|
+
def initialize(io, buffer_size)
|
329
|
+
@io = io
|
330
|
+
@buffer_size = buffer_size
|
331
|
+
end
|
102
332
|
|
103
|
-
|
104
|
-
|
105
|
-
#
|
106
|
-
# #decode expects an Ascii85-encoded String enclosed in <~ and ~> — it will
|
107
|
-
# ignore all characters outside these markers. The returned strings are always
|
108
|
-
# encoded as ASCII-8BIT.
|
109
|
-
#
|
110
|
-
# Ascii85.decode("<~;KZGo~>")
|
111
|
-
# => "Ruby"
|
112
|
-
#
|
113
|
-
# Ascii85.decode("Foo<~;KZGo~>Bar<~;KZGo~>Baz")
|
114
|
-
# => "Ruby"
|
115
|
-
#
|
116
|
-
# Ascii85.decode("No markers")
|
117
|
-
# => ""
|
118
|
-
#
|
119
|
-
# #decode will raise Ascii85::DecodingError when malformed input is
|
120
|
-
# encountered.
|
121
|
-
#
|
122
|
-
def self.decode(str)
|
123
|
-
input = str.to_s
|
333
|
+
def each_chunk
|
334
|
+
return enum_for(:each_chunk) unless block_given?
|
124
335
|
|
125
|
-
|
126
|
-
|
336
|
+
until @io.eof?
|
337
|
+
chunk = @io.read(@buffer_size)
|
338
|
+
yield chunk if chunk
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
127
342
|
|
128
|
-
#
|
343
|
+
# Buffers an underlying IO object to increase efficiency. You do not need
|
344
|
+
# to use this directly.
|
129
345
|
#
|
130
|
-
#
|
131
|
-
# errors if an especially exotic input encoding is introduced.
|
132
|
-
# As of Ruby 1.9.2 all non-dummy encodings work fine though.
|
346
|
+
# @private
|
133
347
|
#
|
134
|
-
|
135
|
-
|
136
|
-
closing_delim = closing_delim.encode(input.encoding)
|
137
|
-
end
|
348
|
+
class BufferedWriter
|
349
|
+
attr_accessor :io
|
138
350
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
# Get the string inside the delimiter-pair
|
145
|
-
input = input[(start_pos + 2)...end_pos]
|
146
|
-
|
147
|
-
# Decode
|
148
|
-
word = 0
|
149
|
-
count = 0
|
150
|
-
result = []
|
151
|
-
|
152
|
-
input.each_byte do |c|
|
153
|
-
case c.chr
|
154
|
-
when " ", "\t", "\r", "\n", "\f", "\0"
|
155
|
-
# Ignore whitespace
|
156
|
-
next
|
157
|
-
|
158
|
-
when 'z'
|
159
|
-
if count == 0
|
160
|
-
# Expand z to 0-word
|
161
|
-
result << 0
|
162
|
-
else
|
163
|
-
raise(Ascii85::DecodingError, "Found 'z' inside Ascii85 5-tuple")
|
164
|
-
end
|
351
|
+
def initialize(io, buffer_size)
|
352
|
+
@io = io
|
353
|
+
@buffer_size = buffer_size
|
354
|
+
@buffer = String.new(capacity: buffer_size, encoding: Encoding::ASCII_8BIT)
|
355
|
+
end
|
165
356
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
357
|
+
def write(tuple)
|
358
|
+
flush if @buffer.bytesize + tuple.bytesize > @buffer_size
|
359
|
+
@buffer << tuple
|
360
|
+
end
|
170
361
|
|
171
|
-
|
362
|
+
def flush
|
363
|
+
@io.write(@buffer)
|
364
|
+
@buffer.clear
|
365
|
+
end
|
366
|
+
end
|
172
367
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
368
|
+
# Wraps the input in '<~' and '~>' delimiters and passes it through
|
369
|
+
# unmodified to the underlying IO object otherwise. You do not need to
|
370
|
+
# use this directly.
|
371
|
+
#
|
372
|
+
# @private
|
373
|
+
#
|
374
|
+
class DummyWrapper
|
375
|
+
def initialize(out)
|
376
|
+
@out = out
|
377
|
+
@out.write(START_MARKER)
|
378
|
+
end
|
177
379
|
|
178
|
-
|
380
|
+
def write(buffer)
|
381
|
+
@out.write(buffer)
|
382
|
+
end
|
179
383
|
|
180
|
-
|
181
|
-
|
182
|
-
|
384
|
+
def finish
|
385
|
+
@out.write(ENDING_MARKER)
|
386
|
+
@out.flush
|
183
387
|
|
184
|
-
|
185
|
-
raise(Ascii85::DecodingError,
|
186
|
-
"Illegal character inside Ascii85: #{c.chr.dump}")
|
388
|
+
@out
|
187
389
|
end
|
188
390
|
end
|
189
391
|
|
190
|
-
#
|
191
|
-
|
392
|
+
# Wraps the input in '<~' and '~>' delimiters and ensures that no line is
|
393
|
+
# longer than the specified length. You do not need to use this directly.
|
394
|
+
#
|
395
|
+
# @private
|
396
|
+
#
|
397
|
+
class Wrapper
|
398
|
+
def initialize(out, wrap_lines)
|
399
|
+
@line_length = [2, wrap_lines.to_i].max
|
192
400
|
|
193
|
-
|
194
|
-
|
401
|
+
@out = out
|
402
|
+
@out.write(START_MARKER)
|
195
403
|
|
196
|
-
|
197
|
-
raise(Ascii85::DecodingError,
|
198
|
-
"Last 5-tuple consists of single character")
|
404
|
+
@cur_len = 2
|
199
405
|
end
|
200
406
|
|
201
|
-
|
202
|
-
|
407
|
+
def write(buffer)
|
408
|
+
loop do
|
409
|
+
s = buffer.bytesize
|
203
410
|
|
204
|
-
|
205
|
-
|
206
|
-
|
411
|
+
if @cur_len + s < @line_length
|
412
|
+
@out.write(buffer)
|
413
|
+
@cur_len += s
|
414
|
+
return
|
415
|
+
end
|
416
|
+
|
417
|
+
remaining = @line_length - @cur_len
|
418
|
+
@out.write(buffer[0...remaining])
|
419
|
+
@out.write(LINE_BREAK)
|
420
|
+
@cur_len = 0
|
421
|
+
buffer = buffer[remaining..]
|
422
|
+
return if buffer.empty?
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
def finish
|
427
|
+
# Add the closing delimiter (may need to be pushed to the next line)
|
428
|
+
@out.write(LINE_BREAK) if @cur_len + 2 > @line_length
|
429
|
+
@out.write(ENDING_MARKER)
|
430
|
+
|
431
|
+
@out.flush
|
432
|
+
@out
|
433
|
+
end
|
207
434
|
end
|
208
435
|
|
209
|
-
|
436
|
+
# Check if an object is IO-like
|
437
|
+
#
|
438
|
+
# @private
|
439
|
+
#
|
440
|
+
def io_like?(obj)
|
441
|
+
obj.respond_to?(:read) &&
|
442
|
+
obj.respond_to?(:eof?)
|
443
|
+
end
|
444
|
+
|
445
|
+
# @return [Integer] Buffer size for to-be-encoded input
|
446
|
+
#
|
447
|
+
def unencoded_chunk_size
|
448
|
+
4 * 2048
|
449
|
+
end
|
450
|
+
|
451
|
+
# @return [Integer] Buffer size for encoded output
|
452
|
+
#
|
453
|
+
def encoded_chunk_size
|
454
|
+
5 * 2048
|
455
|
+
end
|
210
456
|
end
|
211
457
|
|
212
458
|
#
|
213
|
-
#
|
214
|
-
# problems in the input:
|
459
|
+
# Error raised when Ascii85 encounters problems while decoding the input.
|
215
460
|
#
|
216
|
-
#
|
217
|
-
# *
|
461
|
+
# This error is raised for the following issues:
|
462
|
+
# * An invalid character (valid characters are '!'..'u' and 'z')
|
463
|
+
# * A 'z' character inside a 5-tuple ('z' is only valid on its own)
|
218
464
|
# * An invalid 5-tuple that decodes to >= 2**32
|
219
465
|
# * The last tuple consisting of a single character. Valid tuples always have
|
220
466
|
# at least two characters.
|