Skip to content

Commit 068dc3f

Browse files
committed
feat: send method to writer or reader by define method_missing
1 parent c59a54c commit 068dc3f

3 files changed

Lines changed: 63 additions & 49 deletions

File tree

lib/rb/io/multi_writer.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class MultiWriter
1515
# If `#sync_close?` is `true`, closing this `IO` will close all of the underlying
1616
# IOs.
1717
attr_accessor :sync_close
18+
attr_accessor :writers
1819
attr_reader :closed
1920

2021
@closed = false

lib/rb/io/stapled.rb

Lines changed: 37 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ class IO
1515
class Stapled
1616
# If `#sync_close?` is `true`, closing this `IO` will close the underlying `IO`s.
1717
attr_accessor :sync_close
18+
attr_reader :reader
19+
attr_reader :writer
1820

1921
# Returns `true` if this `IO` is closed.
2022
#
@@ -23,6 +25,15 @@ class Stapled
2325

2426
@closed = false
2527

28+
WRITER_DELEGATE = %w[
29+
<<
30+
].freeze
31+
32+
READER_DELEGATE = %w[
33+
eof
34+
eof?
35+
].freeze
36+
2637
alias_method :sync_close?, :sync_close
2738
alias_method :closed?, :closed
2839

@@ -33,53 +44,30 @@ def initialize(reader, writer, sync_close: false)
3344
@sync_close = sync_close
3445
end
3546

36-
def read(...)
37-
check_open
38-
39-
@reader.read(...)
40-
end
41-
42-
def readlines(...)
43-
check_open
44-
45-
@reader.readlines(...)
46-
end
47-
48-
def each_line(...)
49-
check_open
50-
51-
@reader.each_line(...)
47+
def method_missing(name, *args, &block)
48+
if write_methods?(name.to_s)
49+
check_open
50+
@writer.send(name, *args, &block)
51+
elsif read_methods?(name.to_s)
52+
check_open
53+
@reader.send(name, *args, &block)
54+
else
55+
super
56+
end
5257
end
5358

54-
# Gets a string from `reader`.
55-
def gets(...)
56-
check_open
57-
58-
@reader.gets(...)
59+
def respond_to_missing?(name, include_private = false)
60+
super || write_methods?(name.to_s) || read_methods?(name.to_s)
5961
end
6062

61-
def puts(...)
62-
check_open
63-
64-
@writer.puts(...)
63+
def write_methods?(name)
64+
name.include?("put") || name.include?("prin") ||
65+
name.include?("write") || WRITER_DELEGATE.include?(name)
6566
end
6667

67-
def print(...)
68-
check_open
69-
70-
@writer.print(...)
71-
end
72-
73-
def printf(...)
74-
check_open
75-
76-
@writer.print(...)
77-
end
78-
79-
def write(...)
80-
check_open
81-
82-
@writer.write(...)
68+
def read_methods?(name)
69+
name.include?("get") || name.include?("read") ||
70+
name.include?("each") || READER_DELEGATE.include?(name)
8371
end
8472

8573
# Flushes `writer`.
@@ -91,6 +79,14 @@ def flush
9179
self
9280
end
9381

82+
def close_write
83+
@writer.close
84+
end
85+
86+
def close_read
87+
@reader.close
88+
end
89+
9490
# Closes this `IO`.
9591
#
9692
# If `sync_close?` is `true`, it will also close the underlying `IO`s.
@@ -105,14 +101,6 @@ def close
105101
end
106102
end
107103

108-
def close_write
109-
@writer.close
110-
end
111-
112-
def close_read
113-
@reader.close
114-
end
115-
116104
protected def check_open
117105
raise IOError.new("Closed stream") if closed?
118106
end

spec/rb/process_spec.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,29 @@
4343
expect(tempfile.readlines).to eq ["Linux\n"]
4444
tempfile.delete
4545
end
46+
47+
# https://devdocs.io/ruby~3.4/io#class-IO-label-Reading
48+
it "responds to methods" do
49+
Process.run("bash", out: File.open(File::NULL, "r+")) do |pipe|
50+
pipe.write_nonblock("echo")
51+
pipe << " "
52+
pipe.write("hello ")
53+
pipe.print("w")
54+
pipe.printf("%s", "o")
55+
pipe.putc "r"
56+
pipe.write("l")
57+
pipe.puts "d"
58+
pipe.close_write
59+
60+
pipe.getbyte
61+
pipe.getc
62+
pipe.readbyte
63+
pipe.readchar
64+
pipe.readpartial(1)
65+
pipe.readline
66+
pipe.readlines
67+
pipe.gets
68+
pipe.read
69+
end
70+
end
4671
end

0 commit comments

Comments
 (0)