File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change 11# frozen_string_literal: true
22
33# Released under the MIT License.
4- # Copyright, 2023-2025 , by Samuel Williams.
4+ # Copyright, 2023-2026 , by Samuel Williams.
55
66require_relative "stream/version"
77require_relative "stream/buffered"
8+ require_relative "stream/duplex"
89
910# @namespace
1011class IO
1112 # @namespace
1213 module Stream
14+ def self . Duplex ( input , output = input , **options )
15+ Buffered . wrap ( Duplex . new ( input , output ) , **options )
16+ end
1317 end
1418
1519 # Convert any IO-like object into a buffered stream.
Original file line number Diff line number Diff line change 11# frozen_string_literal: true
22
33# Released under the MIT License.
4- # Copyright, 2024-2025 , by Samuel Williams.
4+ # Copyright, 2024-2026 , by Samuel Williams.
55
66require_relative "generic"
77require_relative "connection_reset_error"
Original file line number Diff line number Diff line change 1+ # frozen_string_literal: true
2+
3+ # Released under the MIT License.
4+ # Copyright, 2026, by Samuel Williams.
5+
6+ module IO ::Stream
7+ # A low-level duplex IO adapter that composes distinct readable and writable endpoints.
8+ class Duplex
9+ def initialize ( input , output = input )
10+ @input = input
11+ @output = output
12+ end
13+
14+ attr :input
15+ attr :output
16+
17+ def to_io
18+ @input || @output
19+ end
20+
21+ def timeout
22+ [ @input . timeout , @output . timeout ] . compact . max
23+ end
24+
25+ def timeout = ( duration )
26+ @input . timeout = duration
27+ @output . timeout = duration
28+ end
29+
30+ def closed?
31+ @input . closed? && @output . closed?
32+ end
33+
34+ def close_read
35+ return if @input . closed?
36+
37+ if @input . respond_to? ( :close_read )
38+ @input . close_read
39+ else
40+ @input . close
41+ end
42+ end
43+
44+ def close_write
45+ return if @output . closed?
46+
47+ if @output . respond_to? ( :close_write )
48+ @output . close_write
49+ else
50+ @output . close
51+ end
52+ end
53+
54+ def readable?
55+ @input . readable?
56+ end
57+
58+ def close
59+ @output . close unless @output . closed?
60+ @input . close unless @input . closed?
61+ end
62+
63+ def write ( buffer )
64+ @output . write ( buffer )
65+ end
66+
67+ def read_nonblock ( size , buffer , exception : false )
68+ @input . read_nonblock ( size , buffer , exception : exception )
69+ end
70+
71+ def wait_readable ( duration = @timeout )
72+ @input . wait_readable ( duration )
73+ end
74+
75+ def wait_writable ( duration = @timeout )
76+ @output . wait_writable ( duration )
77+ end
78+ end
79+ end
Original file line number Diff line number Diff line change 11# frozen_string_literal: true
22
33# Released under the MIT License.
4- # Copyright, 2023-2025 , by Samuel Williams.
4+ # Copyright, 2023-2026 , by Samuel Williams.
55
66require_relative "string_buffer"
77require_relative "readable"
88require_relative "writable"
99
1010require_relative "shim/buffered"
1111require_relative "shim/readable"
12+ require_relative "shim/timeout"
1213
1314require_relative "openssl"
1415
Original file line number Diff line number Diff line change 11# frozen_string_literal: true
22
33# Released under the MIT License.
4- # Copyright, 2024-2025 , by Samuel Williams.
4+ # Copyright, 2024-2026 , by Samuel Williams.
55
66require "openssl"
77
Original file line number Diff line number Diff line change 11# frozen_string_literal: true
22
33# Released under the MIT License.
4- # Copyright, 2025, by Samuel Williams.
4+ # Copyright, 2025-2026 , by Samuel Williams.
55
66require_relative "string_buffer"
77
Original file line number Diff line number Diff line change 1+ # frozen_string_literal: true
2+
3+ # Released under the MIT License.
4+ # Copyright, 2024-2026, by Samuel Williams.
5+
6+ require "stringio"
7+
8+ class StringIO
9+ unless method_defined? ( :timeout )
10+ def timeout
11+ @timeout
12+ end
13+ end
14+
15+ unless method_defined? ( :timeout= )
16+ def timeout = ( duration )
17+ @timeout = duration
18+ end
19+ end
20+ end
Original file line number Diff line number Diff line change 11# MIT License
22
3- Copyright, 2023-2025 , by Samuel Williams.
3+ Copyright, 2023-2026 , by Samuel Williams.
44
55Permission is hereby granted, free of charge, to any person obtaining a copy
66of this software and associated documentation files (the "Software"), to deal
Original file line number Diff line number Diff line change 22
33## Unreleased
44
5+ - Introduce ` IO::Stream::Duplex ` as a low-level duplex transport for composing separate input and output endpoints.
6+ - Add ` IO::Stream::Duplex(input, output) ` as a convenient constructor that returns a buffered stream wrapping a duplex transport.
7+ - Add a timeout compatibility shim for ` StringIO ` so duplex streams composed from in-memory endpoints can participate in the timeout interface consistently.
58 - Remove old OpenSSL method shims.
69
710## v0.11.0
Original file line number Diff line number Diff line change 11# frozen_string_literal: true
22
33# Released under the MIT License.
4- # Copyright, 2024, by Samuel Williams.
4+ # Copyright, 2024-2026 , by Samuel Williams.
55
66require "io/stream"
77
2222
2323 expect ( stream2 ) . to be_equal ( stream )
2424 end
25+
26+ it "can wrap an existing duplex stream" do
27+ input = StringIO . new
28+ output = StringIO . new
29+
30+ duplex = IO ::Stream ::Duplex . new ( input , output )
31+ stream = IO ::Stream ( duplex )
32+
33+ expect ( stream ) . to be_a ( IO ::Stream ::Buffered )
34+ expect ( stream . io ) . to be_equal ( duplex )
35+ end
36+
37+ it "provides timeout shims for StringIO-backed duplex streams" do
38+ duplex = IO ::Stream ::Duplex . new ( StringIO . new , StringIO . new )
39+
40+ expect ( duplex . timeout ) . to be_nil
41+ expect ( duplex . timeout = 0.5 ) . to be == 0.5
42+ expect ( duplex . timeout ) . to be == 0.5
43+ end
2544end
You can’t perform that action at this time.
0 commit comments