Browse Source

Support context diff via filterdiff, fixed #15

Matthew Wang 11 years ago
parent
commit
e7854dddde
5 changed files with 226 additions and 17 deletions
  1. 59
    17
      cdiff.py
  2. 56
    0
      tests/context/in.diff
  3. 39
    0
      tests/context/out.normal
  4. 36
    0
      tests/context/out.side-by-side
  5. 36
    0
      tests/context/out.w70

+ 59
- 17
cdiff.py View File

@@ -26,6 +26,8 @@ if sys.hexversion < 0x02050000:
26 26
 import re
27 27
 import subprocess
28 28
 import errno
29
+import fcntl
30
+import os
29 31
 import difflib
30 32
 
31 33
 
@@ -429,10 +431,6 @@ class UnifiedDiff(Diff):
429 431
         return re.match('^Binary files .* differ$', line.rstrip())
430 432
 
431 433
 
432
-class ContextDiff(Diff):
433
-    pass
434
-
435
-
436 434
 class PatchStream(object):
437 435
 
438 436
     def __init__(self, diff_hdl):
@@ -469,13 +467,54 @@ class PatchStream(object):
469 467
             yield line
470 468
 
471 469
 
470
+class PatchStreamForwarder(object):
471
+    """A non-block stream forwarder.  Note input stream is non-seekable, and
472
+    upstream has eaten some lines.
473
+    """
474
+    def __init__(self, istream, translator):
475
+        assert isinstance(istream, PatchStream)
476
+        self._istream = istream
477
+        self._translator = translator
478
+
479
+        self._istream_open = True
480
+        self._set_non_block(self._translator.stdin)
481
+        self._set_non_block(self._translator.stdout)
482
+
483
+        self._forward_until_block()
484
+
485
+    def _set_non_block(self, hdl):
486
+        fd = hdl.fileno()
487
+        fl = fcntl.fcntl(fd, fcntl.F_GETFL)
488
+        fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
489
+
490
+    def _forward_until_block(self):
491
+        for line in self._istream:
492
+            try:
493
+                self._translator.stdin.write(line.encode('utf-8'))
494
+            except IOError:
495
+                break       # EAGAIN
496
+        else:
497
+            self._translator.stdin.close()
498
+            self._istream_open = False
499
+
500
+    def __iter__(self):
501
+        while True:
502
+            try:
503
+                line = self._translator.stdout.readline()
504
+                if not line:
505
+                    return
506
+                yield line
507
+            except IOError:
508
+                if self._istream_open:
509
+                    self._forward_until_block()
510
+                continue    # EAGAIN
511
+
512
+
472 513
 class DiffParser(object):
473 514
 
474 515
     def __init__(self, stream):
475
-        self._stream = stream
476 516
 
477
-        header = [decode(line) for line in
478
-                  self._stream.read_stream_header(100)]
517
+        header = [decode(line) for line in stream.read_stream_header(100)]
479 518
         size = len(header)
480 519
 
481 520
         if size >= 4 and (header[0].startswith('*** ') and
@@ -483,34 +522,37 @@ class DiffParser(object):
483 522
                           header[2].rstrip() == '***************' and
484 523
                           header[3].startswith('*** ') and
485 524
                           header[3].rstrip().endswith(' ****')):
486
-            self._type = 'context'
487
-
488 525
             # For context diff, try use `filterdiff` to translate it to unified
489 526
             # format and provide a new stream
490 527
             #
491
-            # TODO
492
-
528
+            self._type = 'context'
529
+            try:
530
+                self._translator = subprocess.Popen(
531
+                    ['filterdiff', '--format=unified'], stdin=subprocess.PIPE,
532
+                    stdout=subprocess.PIPE)
533
+            except OSError:
534
+                raise SystemExit('*** Context diff support depends on '
535
+                                 'filterdiff')
536
+            self._stream = PatchStreamForwarder(stream, self._translator)
493 537
             return
494 538
 
495 539
         for n in range(size):
496 540
             if header[n].startswith('--- ') and (n < size - 1) and \
497 541
                     header[n+1].startswith('+++ '):
498 542
                 self._type = 'unified'
543
+                self._stream = stream
499 544
                 break
500 545
         else:
501
-            # `filterdiff` translate unknown diff to nothing, fall through to
546
+            # `filterdiff` translates unknown diff to nothing, fall through to
502 547
             # unified diff give cdiff a chance to show everything as headers
503 548
             #
504 549
             sys.stderr.write("*** unknown format, fall through to 'unified'\n")
505 550
             self._type = 'unified'
551
+            self._stream = stream
506 552
 
507 553
     def get_diff_generator(self):
508 554
         """parse all diff lines, construct a list of Diff objects"""
509
-        if self._type == 'unified':
510
-            difflet = UnifiedDiff(None, None, None, None)
511
-        else:
512
-            raise RuntimeError('unsupported diff format')
513
-
555
+        difflet = UnifiedDiff(None, None, None, None)
514 556
         diff = Diff([], None, None, [])
515 557
         headers = []
516 558
 

+ 56
- 0
tests/context/in.diff View File

@@ -0,0 +1,56 @@
1
+*** a/Lib/test/test_minidom.py	Fri Sep 30 08:46:25 2011 +0300
2
+--- b/Lib/test/test_minidom.py	Sat Oct 01 21:02:49 2011 +0300
3
+***************
4
+*** 467,472 ****
5
+--- 467,479 ----
6
+          dom.unlink()
7
+          self.confirm(domstr == str.replace("\n", "\r\n"))
8
+  
9
++     def testPrettyTextNode(self):
10
++         str = '<A>B</A>'
11
++         dom = parseString(str)
12
++         dom2 = parseString(dom.toprettyxml())
13
++         self.confirm(dom.childNodes[0].childNodes[0].toxml()==
14
++                      dom2.childNodes[0].childNodes[0].toxml())
15
++ 
16
+      def testProcessingInstruction(self):
17
+          dom = parseString('<e><?mypi \t\n data \t\n ?></e>')
18
+          pi = dom.documentElement.firstChild
19
+*** a/Lib/xml/dom/minidom.py	Fri Sep 30 08:46:25 2011 +0300
20
+--- b/Lib/xml/dom/minidom.py	Sat Oct 01 21:02:49 2011 +0300
21
+***************
22
+*** 836,842 ****
23
+              _write_data(writer, attrs[a_name].value)
24
+              writer.write("\"")
25
+          if self.childNodes:
26
+!             writer.write(">%s"%(newl))
27
+              for node in self.childNodes:
28
+                  node.writexml(writer,indent+addindent,addindent,newl)
29
+              writer.write("%s</%s>%s" % (indent,self.tagName,newl))
30
+--- 836,844 ----
31
+              _write_data(writer, attrs[a_name].value)
32
+              writer.write("\"")
33
+          if self.childNodes:
34
+!             writer.write(">")
35
+!             if self.childNodes[0].nodeType != Node.TEXT_NODE:     # Strict check
36
+!                 writer.write(newl)
37
+              for node in self.childNodes:
38
+                  node.writexml(writer,indent+addindent,addindent,newl)
39
+              writer.write("%s</%s>%s" % (indent,self.tagName,newl))
40
+***************
41
+*** 1061,1067 ****
42
+          return newText
43
+  
44
+      def writexml(self, writer, indent="", addindent="", newl=""):
45
+!         _write_data(writer, "%s%s%s"%(indent, self.data, newl))
46
+  
47
+      # DOM Level 3 (WD 9 April 2002)
48
+  
49
+--- 1063,1069 ----
50
+          return newText
51
+  
52
+      def writexml(self, writer, indent="", addindent="", newl=""):
53
+!         _write_data(writer, self.data)
54
+  
55
+      # DOM Level 3 (WD 9 April 2002)
56
+  

+ 39
- 0
tests/context/out.normal View File

@@ -0,0 +1,39 @@
1
+--- a/Lib/test/test_minidom.py	Fri Sep 30 08:46:25 2011 +0300
2
++++ b/Lib/test/test_minidom.py	Sat Oct 01 21:02:49 2011 +0300
3
+@@ -467,6 +467,13 @@
4
+         dom.unlink()
5
+         self.confirm(domstr == str.replace("\n", "\r\n"))
6
++
7
++    def testPrettyTextNode(self):
8
++        str = '<A>B</A>'
9
++        dom = parseString(str)
10
++        dom2 = parseString(dom.toprettyxml())
11
++        self.confirm(dom.childNodes[0].childNodes[0].toxml()==
12
++                     dom2.childNodes[0].childNodes[0].toxml())
13
+ 
14
+     def testProcessingInstruction(self):
15
+         dom = parseString('<e><?mypi \t\n data \t\n ?></e>')
16
+         pi = dom.documentElement.firstChild
17
+--- a/Lib/xml/dom/minidom.py	Fri Sep 30 08:46:25 2011 +0300
18
++++ b/Lib/xml/dom/minidom.py	Sat Oct 01 21:02:49 2011 +0300
19
+@@ -836,7 +836,9 @@
20
+             _write_data(writer, attrs[a_name].value)
21
+             writer.write("\"")
22
+         if self.childNodes:
23
+-            writer.write(">%s"%(newl))
24
++            writer.write(">")
25
++            if self.childNodes[0].nodeType != Node.TEXT_NODE:     # Strict check
26
++                writer.write(newl)
27
+             for node in self.childNodes:
28
+                 node.writexml(writer,indent+addindent,addindent,newl)
29
+             writer.write("%s</%s>%s" % (indent,self.tagName,newl))
30
+@@ -1061,7 +1063,7 @@
31
+         return newText
32
+ 
33
+     def writexml(self, writer, indent="", addindent="", newl=""):
34
+-        _write_data(writer, "%s%s%s"%(indent, self.data, newl))
35
++        _write_data(writer, self.data)
36
+ 
37
+     # DOM Level 3 (WD 9 April 2002)
38
+ 
39
+

+ 36
- 0
tests/context/out.side-by-side View File

@@ -0,0 +1,36 @@
1
+--- a/Lib/test/test_minidom.py	Fri Sep 30 08:46:25 2011 +0300
2
++++ b/Lib/test/test_minidom.py	Sat Oct 01 21:02:49 2011 +0300
3
+@@ -467,6 +467,13 @@
4
+467         dom.unlink()                                                             467         dom.unlink()
5
+468         self.confirm(domstr == str.replace("\n", "\r\n"))                        468         self.confirm(domstr == str.replace("\n", "\r\n"))
6
+                                                                                     469 
7
+                                                                                     470     def testPrettyTextNode(self):
8
+                                                                                     471         str = '<A>B</A>'
9
+                                                                                     472         dom = parseString(str)
10
+                                                                                     473         dom2 = parseString(dom.toprettyxml())
11
+                                                                                     474         self.confirm(dom.childNodes[0].childNodes[0].toxml()==
12
+                                                                                     475                      dom2.childNodes[0].childNodes[0].toxml())
13
+469                                                                                  476 
14
+470     def testProcessingInstruction(self):                                         477     def testProcessingInstruction(self):
15
+471         dom = parseString('<e><?mypi \t\n data \t\n ?></e>')                     478         dom = parseString('<e><?mypi \t\n data \t\n ?></e>')
16
+472         pi = dom.documentElement.firstChild                                      479         pi = dom.documentElement.firstChild
17
+--- a/Lib/xml/dom/minidom.py	Fri Sep 30 08:46:25 2011 +0300
18
++++ b/Lib/xml/dom/minidom.py	Sat Oct 01 21:02:49 2011 +0300
19
+@@ -836,7 +836,9 @@
20
+ 836             _write_data(writer, attrs[a_name].value)                              836             _write_data(writer, attrs[a_name].value)
21
+ 837             writer.write("\"")                                                    837             writer.write("\"")
22
+ 838         if self.childNodes:                                                       838         if self.childNodes:
23
+ 839             writer.write(">%s"%(newl))                                            839             writer.write(">")
24
+                                                                                       840             if self.childNodes[0].nodeType != Node.TEXT_NODE:     # Strict check
25
+                                                                                       841                 writer.write(newl)
26
+ 840             for node in self.childNodes:                                          842             for node in self.childNodes:
27
+ 841                 node.writexml(writer,indent+addindent,addindent,newl)             843                 node.writexml(writer,indent+addindent,addindent,newl)
28
+ 842             writer.write("%s</%s>%s" % (indent,self.tagName,newl))                844             writer.write("%s</%s>%s" % (indent,self.tagName,newl))
29
+@@ -1061,7 +1063,7 @@
30
+1061         return newText                                                           1063         return newText
31
+1062                                                                                  1064 
32
+1063     def writexml(self, writer, indent="", addindent="", newl=""):                1065     def writexml(self, writer, indent="", addindent="", newl=""):
33
+1064         _write_data(writer, "%s%s%s"%(indent, self.data, newl))                  1066         _write_data(writer, self.data)
34
+1065                                                                                  1067 
35
+1066     # DOM Level 3 (WD 9 April 2002)                                              1068     # DOM Level 3 (WD 9 April 2002)
36
+1067                                                                                  1069 

+ 36
- 0
tests/context/out.w70 View File

@@ -0,0 +1,36 @@
1
+--- a/Lib/test/test_minidom.py	Fri Sep 30 08:46:25 2011 +0300
2
++++ b/Lib/test/test_minidom.py	Sat Oct 01 21:02:49 2011 +0300
3
+@@ -467,6 +467,13 @@
4
+467         dom.unlink()                                                   467         dom.unlink()
5
+468         self.confirm(domstr == str.replace("\n", "\r\n"))              468         self.confirm(domstr == str.replace("\n", "\r\n"))
6
+                                                                           469 
7
+                                                                           470     def testPrettyTextNode(self):
8
+                                                                           471         str = '<A>B</A>'
9
+                                                                           472         dom = parseString(str)
10
+                                                                           473         dom2 = parseString(dom.toprettyxml())
11
+                                                                           474         self.confirm(dom.childNodes[0].childNodes[0].toxml()==
12
+                                                                           475                      dom2.childNodes[0].childNodes[0].toxml())
13
+469                                                                        476 
14
+470     def testProcessingInstruction(self):                               477     def testProcessingInstruction(self):
15
+471         dom = parseString('<e><?mypi \t\n data \t\n ?></e>')           478         dom = parseString('<e><?mypi \t\n data \t\n ?></e>')
16
+472         pi = dom.documentElement.firstChild                            479         pi = dom.documentElement.firstChild
17
+--- a/Lib/xml/dom/minidom.py	Fri Sep 30 08:46:25 2011 +0300
18
++++ b/Lib/xml/dom/minidom.py	Sat Oct 01 21:02:49 2011 +0300
19
+@@ -836,7 +836,9 @@
20
+ 836             _write_data(writer, attrs[a_name].value)                    836             _write_data(writer, attrs[a_name].value)
21
+ 837             writer.write("\"")                                          837             writer.write("\"")
22
+ 838         if self.childNodes:                                             838         if self.childNodes:
23
+ 839             writer.write(">%s"%(newl))                                  839             writer.write(">")
24
+                                                                             840             if self.childNodes[0].nodeType != Node.TEXT_NODE:     # S>
25
+                                                                             841                 writer.write(newl)
26
+ 840             for node in self.childNodes:                                842             for node in self.childNodes:
27
+ 841                 node.writexml(writer,indent+addindent,addindent,newl)   843                 node.writexml(writer,indent+addindent,addindent,newl)
28
+ 842             writer.write("%s</%s>%s" % (indent,self.tagName,newl))      844             writer.write("%s</%s>%s" % (indent,self.tagName,newl))
29
+@@ -1061,7 +1063,7 @@
30
+1061         return newText                                                 1063         return newText
31
+1062                                                                        1064 
32
+1063     def writexml(self, writer, indent="", addindent="", newl=""):      1065     def writexml(self, writer, indent="", addindent="", newl=""):
33
+1064         _write_data(writer, "%s%s%s"%(indent, self.data, newl))        1066         _write_data(writer, self.data)
34
+1065                                                                        1067 
35
+1066     # DOM Level 3 (WD 9 April 2002)                                    1068     # DOM Level 3 (WD 9 April 2002)
36
+1067                                                                        1069