Skip to content

Commit c453134

Browse files
authored
add Noop codec (#4)
1 parent 231b8c4 commit c453134

File tree

4 files changed

+155
-7
lines changed

4 files changed

+155
-7
lines changed

docs/src/index.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -187,24 +187,22 @@ compressed = take!(buf)
187187
close(stream)
188188
```
189189

190-
### Use an identity (no-op) codec
190+
### Use a noop codec
191191

192-
Sometimes, the `Identity` codec, which does nothing, may be useful. The
193-
following example creates a decompression stream based on the extension of a
194-
filepath:
192+
Sometimes, the `Noop` codec, which does nothing, may be useful. The following
193+
example creates a decompression stream based on the extension of a filepath:
195194
```julia
196195
using CodecZlib
197196
using CodecBzip2
198197
using TranscodingStreams
199-
using TranscodingStreams.CodecIdentity
200198

201199
function makestream(filepath)
202200
if endswith(filepath, ".gz")
203201
codec = GzipDecompression()
204202
elseif endswith(filepath, ".bz2")
205203
codec = Bzip2Decompression()
206204
else
207-
codec = Identity()
205+
codec = Noop()
208206
end
209207
return TranscodingStream(codec, open(filepath))
210208
end
@@ -239,6 +237,11 @@ transcode(codec::Codec, data::Vector{UInt8})
239237
TranscodingStreams.TOKEN_END
240238
```
241239

240+
```@docs
241+
TranscodingStreams.Noop
242+
TranscodingStreams.NoopStream
243+
```
244+
242245
```@docs
243246
TranscodingStreams.CodecIdentity.Identity
244247
TranscodingStreams.CodecIdentity.IdentityStream

src/TranscodingStreams.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ __precompile__()
22

33
module TranscodingStreams
44

5-
export TranscodingStream
5+
export
6+
TranscodingStream,
7+
Noop,
8+
NoopStream
69

710
include("memory.jl")
811
include("buffer.jl")
@@ -11,6 +14,7 @@ include("state.jl")
1114
include("stream.jl")
1215
include("io.jl")
1316
include("identity.jl")
17+
include("noop.jl")
1418
include("testtools.jl")
1519

1620
end # module

src/noop.jl

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# Noop Codec
2+
# ==========
3+
4+
"""
5+
Noop()
6+
7+
Create a noop codec.
8+
9+
Noop (no operation) is a codec that does nothing. The data read from or written
10+
to the stream are kept as-is without any modification. This is often useful as a
11+
buffered stream or an identity element of a composition of streams.
12+
13+
The implementations are specialized for this codec. For example, a `Noop` stream
14+
uses only one buffer rather than a pair of buffers, which avoids copying data
15+
between two buffers and the throughput will be larger than a naive
16+
implementation.
17+
"""
18+
struct Noop <: Codec end
19+
20+
const NoopStream{S} = TranscodingStream{Noop,S} where S<:IO
21+
22+
"""
23+
NoopStream(stream::IO)
24+
25+
Create a noop stream.
26+
"""
27+
function NoopStream(stream::IO)
28+
return TranscodingStream(Noop(), stream)
29+
end
30+
31+
function TranscodingStream(codec::Noop, stream::IO; bufsize::Integer=DEFAULT_BUFFER_SIZE)
32+
if bufsize 0
33+
throw(ArgumentError("non-positive buffer size"))
34+
end
35+
# Use only one buffer.
36+
buffer = Buffer(bufsize)
37+
state = TranscodingStreams.State(buffer, buffer)
38+
return TranscodingStream(codec, stream, state)
39+
end
40+
41+
function Base.unsafe_read(stream::NoopStream, output::Ptr{UInt8}, nbytes::UInt)
42+
changestate!(stream, :read)
43+
buffer = stream.state.buffer1
44+
p = output
45+
p_end = output + nbytes
46+
while p < p_end && !eof(stream)
47+
if buffersize(buffer) > 0
48+
m = min(buffersize(buffer), p_end - p)
49+
unsafe_copy!(p, bufferptr(buffer), m)
50+
buffer.bufferpos += m
51+
else
52+
# directly read data from the underlying stream
53+
m = p_end - p
54+
unsafe_read(stream.stream, p, m)
55+
end
56+
p += m
57+
end
58+
if p < p_end && eof(stream)
59+
throw(EOFError())
60+
end
61+
return
62+
end
63+
64+
function Base.unsafe_write(stream::NoopStream, input::Ptr{UInt8}, nbytes::UInt)
65+
changestate!(stream, :write)
66+
buffer = stream.state.buffer1
67+
if marginsize(buffer) nbytes
68+
unsafe_copy!(marginptr(buffer), input, nbytes)
69+
buffer.marginpos += nbytes
70+
return Int(nbytes)
71+
else
72+
flushbuffer(stream)
73+
# directly write data to the underlying stream
74+
return unsafe_write(stream.stream, input, nbytes)
75+
end
76+
end
77+
78+
function Base.transcode(::Noop, data::Vector{UInt8})
79+
# Copy data because the caller may expect the return object is not the same
80+
# as from the input.
81+
return copy(data)
82+
end
83+
84+
85+
# Buffering
86+
# ---------
87+
#
88+
# These methods are overloaded for the `Noop` codec because it has only one
89+
# buffer for efficiency.
90+
91+
function fillbuffer(stream::NoopStream)
92+
changestate!(stream, :read)
93+
buffer = stream.state.buffer1
94+
@assert buffer === stream.state.buffer2
95+
nfilled::Int = 0
96+
while buffersize(buffer) == 0 && !eof(stream.stream)
97+
makemargin!(buffer, 1)
98+
n = unsafe_read(stream.stream, marginptr(buffer), marginsize(buffer))
99+
buffer.marginpos += n
100+
nfilled += n
101+
end
102+
return nfilled
103+
end
104+
105+
function flushbuffer(stream::NoopStream)
106+
changestate!(stream, :write)
107+
buffer = stream.state.buffer1
108+
@assert buffer === stream.state.buffer2
109+
nflushed::Int = 0
110+
while buffersize(buffer) > 0
111+
n = unsafe_write(stream.stream, bufferptr(buffer), buffersize(buffer))
112+
buffer.bufferpos += n
113+
nflushed += n
114+
end
115+
makemargin!(buffer, 0)
116+
return nflushed
117+
end
118+
119+
function flushbufferall(stream::NoopStream)
120+
@assert stream.state.state == :write
121+
buffer = stream.state.buffer1
122+
bufsize = buffersize(buffer)
123+
while buffersize(buffer) > 0
124+
writebuffer!(stream.stream, buffer)
125+
end
126+
return bufsize
127+
end
128+
129+
function processall(stream::NoopStream)
130+
flushbufferall(stream)
131+
@assert buffersize(stream.state.buffer1) == 0
132+
end

test/runtests.jl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,15 @@ end
171171
@test_throws ArgumentError TranscodingStream(Identity(), IOBuffer(), bufsize=0)
172172
end
173173

174+
@testset "Noop Codec" begin
175+
data = b"foo"
176+
@test transcode(Noop(), data) !== data
177+
TranscodingStreams.test_roundtrip_transcode(Noop, Noop)
178+
TranscodingStreams.test_roundtrip_read(NoopStream, NoopStream)
179+
TranscodingStreams.test_roundtrip_write(NoopStream, NoopStream)
180+
TranscodingStreams.test_roundtrip_lines(NoopStream, NoopStream)
181+
end
182+
174183
# This does not implement necessary interface methods.
175184
struct InvalidCodec <: TranscodingStreams.Codec end
176185

0 commit comments

Comments
 (0)