66from . import STDERR , STDOUT
77
88
9- class Director (io .TextIOBase ):
9+ class _DirectorBuffer (io .RawIOBase ):
10+ """Binary buffer that forwards text writes to the manager.
11+
12+ This makes the stream more compatible including if enabled when running tox.
13+
14+ Args:
15+ manager (Manager): The manager that writes are stored in.
16+ state: The state passed to the manager. This is often ``preditor.stream.STDOUT``
17+ or ``preditor.stream.STDERR``.
18+ old_stream: A second stream that will be written to every time this stream
19+ is written to. This allows this object to replace sys.stdout and still
20+ send that output to the original stdout, which is useful for not breaking
21+ DCC's script editors. Pass False to disable this feature. If you pass None
22+ and state is set to ``preditor.stream.STDOUT`` or ``preditor.stream.STDERR``
23+ this will automatically be set to the current sys.stdout or sys.stderr.
24+ name (str, optional): Stored on self.name.
25+ """
26+
27+ def __init__ (self , manager , state , old_stream = None , name = 'nul' ):
28+ super ().__init__ ()
29+ self .manager = manager
30+ self .state = state
31+ self .old_stream = old_stream
32+ self .name = name
33+
34+ def flush (self ):
35+ if self .old_stream :
36+ self .old_stream .flush ()
37+ super ().flush ()
38+
39+ def writable (self ):
40+ return True
41+
42+ def write (self , b ):
43+ if isinstance (b , memoryview ):
44+ b = b .tobytes ()
45+
46+ # Decode incoming bytes (TextIOWrapper encodes before sending here)
47+ msg = b .decode ("utf-8" , errors = "replace" )
48+ self .manager .write (msg , self .state )
49+
50+ if self .old_stream :
51+ self .old_stream .write (msg )
52+
53+ return len (b )
54+
55+
56+ class Director (io .TextIOWrapper ):
1057 """A file like object that stores the text written to it in a manager.
1158 This manager can be shared between multiple Directors to build a single
1259 continuous history of all writes.
1360
61+ While this uses a buffer under the hood, buffering is disabled and any calls
62+ to write will automatically flush the buffer.
63+
1464 Args:
1565 manager (Manager): The manager that writes are stored in.
1666 state: The state passed to the manager. This is often ``preditor.stream.STDOUT``
@@ -24,30 +74,40 @@ class Director(io.TextIOBase):
2474 """
2575
2676 def __init__ (self , manager , state , old_stream = None , * args , ** kwargs ):
27- super (Director , self ).__init__ (* args , ** kwargs )
28- self .manager = manager
29- self .state = state
30-
3177 # Keep track of whether we wrapped a std stream
3278 # that way we don't .close() any streams that we don't control
3379 self .std_stream_wrapped = False
3480
81+ name = 'nul'
3582 if old_stream is False :
3683 old_stream = None
3784 elif old_stream is None :
3885 if state == STDOUT :
3986 # On Windows if we're in pythonw.exe, then sys.stdout is named "nul"
4087 # And it uses cp1252 encoding (which breaks with unicode)
4188 # So if we find this nul TextIOWrapper, it's safe to just skip it
42- if getattr (sys .stdout , 'name' , '' ) != 'nul' :
89+ name = getattr (sys .stdout , 'name' , '' )
90+ if name != 'nul' :
4391 self .std_stream_wrapped = True
4492 old_stream = sys .stdout
4593 elif state == STDERR :
46- if getattr (sys .stderr , 'name' , '' ) != 'nul' :
94+ name = getattr (sys .stderr , 'name' , '' )
95+ if name != 'nul' :
4796 self .std_stream_wrapped = True
4897 old_stream = sys .stderr
4998
5099 self .old_stream = old_stream
100+ self .manager = manager
101+ self .state = state
102+
103+ # Build the buffer. This provides the expected interface for tox, etc.
104+ raw = _DirectorBuffer (manager , state , old_stream , name )
105+ buffer = io .BufferedWriter (raw )
106+
107+ super ().__init__ (buffer , encoding = "utf-8" , write_through = True , * args , ** kwargs )
108+
109+ def __repr__ (self ):
110+ return f"<Director state={ self .state } old_stream={ self .old_stream !r} >"
51111
52112 def close (self ):
53113 if (
@@ -58,16 +118,27 @@ def close(self):
58118 ):
59119 self .old_stream .close ()
60120
61- super (Director , self ).close ()
121+ super ().close ()
62122
63- def flush (self ):
64- if self .old_stream :
65- self .old_stream .flush ()
123+ def write (self , msg ):
124+ super ().write (msg )
125+ # Force a write of any buffered data
126+ self .flush ()
66127
67- super (Director , self ).flush ()
128+ # These methods enable terminal features like color coding etc.
129+ def isatty (self ):
130+ if self .old_stream is not None :
131+ return self .old_stream .isatty ()
132+ return False
68133
69- def write (self , msg ):
70- self .manager .write (msg , self .state )
134+ @property
135+ def encoding (self ):
136+ if self .old_stream is not None :
137+ return self .old_stream .encoding
138+ return super ().encoding
71139
72- if self .old_stream :
73- self .old_stream .write (msg )
140+ @property
141+ def errors (self ):
142+ if self .old_stream is not None :
143+ return self .old_stream .errors
144+ return super ().errors
0 commit comments