Browse Source

Merge branch 'block-select-line-buffered'

Matthew Wang 11 years ago
parent
commit
d44d437758
1 changed files with 46 additions and 34 deletions
  1. 46
    34
      cdiff.py

+ 46
- 34
cdiff.py View File

23
 if sys.hexversion < 0x02050000:
23
 if sys.hexversion < 0x02050000:
24
     raise SystemExit("*** Requires python >= 2.5.0")    # pragma: no cover
24
     raise SystemExit("*** Requires python >= 2.5.0")    # pragma: no cover
25
 
25
 
26
+# Python < 2.6 does not have next()
27
+try:
28
+    next
29
+except NameError:
30
+    def next(obj): return obj.next()
31
+
26
 import re
32
 import re
27
 import signal
33
 import signal
28
 import subprocess
34
 import subprocess
29
-import fcntl
35
+import select
30
 import os
36
 import os
31
 import difflib
37
 import difflib
32
 
38
 
226
 
232
 
227
 
233
 
228
 class PatchStreamForwarder(object):
234
 class PatchStreamForwarder(object):
229
-    """A non-block stream forwarder.  Note input stream is non-seekable, and
230
-    upstream has eaten some lines.
235
+    """A blocking stream forwarder use `select` and line buffered mode.  Feed
236
+    input stream to a diff format translator and read output stream from it.
237
+    Note input stream is non-seekable, and upstream has eaten some lines.
231
     """
238
     """
232
     def __init__(self, istream, translator):
239
     def __init__(self, istream, translator):
233
         assert isinstance(istream, PatchStream)
240
         assert isinstance(istream, PatchStream)
234
-        self._istream = istream
235
-        self._translator = translator
236
-
237
-        self._set_non_block(self._translator.stdin)
238
-        self._set_non_block(self._translator.stdout)
239
-
240
-        self._forward_until_block()
241
+        assert isinstance(translator, subprocess.Popen)
242
+        self._istream = iter(istream)
243
+        self._in = translator.stdin
244
+        self._out = translator.stdout
241
 
245
 
242
-    def _set_non_block(self, hdl):
243
-        fd = hdl.fileno()
244
-        fl = fcntl.fcntl(fd, fcntl.F_GETFL)
245
-        fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
246
+    def _can_read(self, timeout=0):
247
+        return select.select([self._out.fileno()], [], [], timeout)[0]
246
 
248
 
247
-    def _forward_until_block(self):
248
-        for line in self._istream:
249
-            try:
250
-                self._translator.stdin.write(line.encode('utf-8'))
251
-            except IOError:
252
-                break       # EAGAIN
253
-        else:
254
-            self._translator.stdin.close()
249
+    def _forward_line(self):
250
+        try:
251
+            line = next(self._istream)
252
+            self._in.write(line.encode('utf-8'))
253
+        except StopIteration:
254
+            self._in.close()
255
 
255
 
256
     def __iter__(self):
256
     def __iter__(self):
257
         while True:
257
         while True:
258
-            try:
259
-                line = self._translator.stdout.readline()
260
-                if not line:
258
+            if self._can_read():
259
+                line = self._out.readline()
260
+                if line:
261
+                    yield line
262
+                else:
261
                     return
263
                     return
262
-                yield line
263
-            except IOError:
264
-                if not self._translator.stdin.closed:
265
-                    self._forward_until_block()
266
-                continue    # EAGAIN
264
+            elif not self._in.closed:
265
+                self._forward_line()
267
 
266
 
268
 
267
 
269
 class DiffParser(object):
268
 class DiffParser(object):
283
             #
282
             #
284
             self._type = 'context'
283
             self._type = 'context'
285
             try:
284
             try:
285
+                # Use line buffered mode so that to readline() in block mode
286
                 self._translator = subprocess.Popen(
286
                 self._translator = subprocess.Popen(
287
                     ['filterdiff', '--format=unified'], stdin=subprocess.PIPE,
287
                     ['filterdiff', '--format=unified'], stdin=subprocess.PIPE,
288
-                    stdout=subprocess.PIPE)
288
+                    stdout=subprocess.PIPE, bufsize=1)
289
             except OSError:
289
             except OSError:
290
                 raise SystemExit('*** Context diff support depends on '
290
                 raise SystemExit('*** Context diff support depends on '
291
                                  'filterdiff')
291
                                  'filterdiff')
590
 
590
 
591
 
591
 
592
 def markup_to_pager(stream, opts):
592
 def markup_to_pager(stream, opts):
593
+    """Pipe unified diff stream to pager (less).
594
+
595
+    Note: have to create pager Popen object before the translator Popen object
596
+    in PatchStreamForwarder, otherwise the `stdin=subprocess.PIPE` would cause
597
+    trouble to the translator pipe (select() never see EOF after input stream
598
+    ended), most likely python bug 12607 (http://bugs.python.org/issue12607)
599
+    which was fixed in python 2.7.3.
600
+
601
+    See issue #30 (https://github.com/ymattw/cdiff/issues/30) for more
602
+    information.
603
+    """
604
+    # Args stolen from git source: github.com/git/git/blob/master/pager.c
605
+    pager = subprocess.Popen(
606
+        ['less', '-FRSX'], stdin=subprocess.PIPE, stdout=sys.stdout)
607
+
593
     diffs = DiffParser(stream).get_diff_generator()
608
     diffs = DiffParser(stream).get_diff_generator()
594
     marker = DiffMarker()
609
     marker = DiffMarker()
595
     color_diff = marker.markup(diffs, side_by_side=opts.side_by_side,
610
     color_diff = marker.markup(diffs, side_by_side=opts.side_by_side,
596
                                width=opts.width)
611
                                width=opts.width)
597
 
612
 
598
-    # Args stolen from git source: github.com/git/git/blob/master/pager.c
599
-    pager = subprocess.Popen(
600
-        ['less', '-FRSX'], stdin=subprocess.PIPE, stdout=sys.stdout)
601
     for line in color_diff:
613
     for line in color_diff:
602
         pager.stdin.write(line.encode('utf-8'))
614
         pager.stdin.write(line.encode('utf-8'))
603
 
615