Browse Source

- Fixed incorrect yield on diff missing eof

- Fixed a bug in diff format probe
- Handle keyboard interrupt and large diffs in non-color mode
- Code clean up
Matthew Wang 11 years ago
parent
commit
7debe071d5
3 changed files with 78 additions and 47 deletions
  1. 7
    0
      CHANGES
  2. 3
    3
      README.rst
  3. 68
    44
      cdiff.py

+ 7
- 0
CHANGES View File

@@ -2,6 +2,13 @@
2 2
 Change log
3 3
 ==========
4 4
 
5
+Version 0.5.1 (2013-02-19)
6
+
7
+  - Fixed incorrect yield on diff missing eof
8
+  - Fixed a bug in diff format probe
9
+  - Handle keyboard interrupt and large diffs in non-color mode
10
+  - Code clean up
11
+
5 12
 Version 0.5 (2013-02-18)
6 13
 
7 14
   - Support read output from ``svn diff --log`` and ``hg log -p``

+ 3
- 3
README.rst View File

@@ -5,7 +5,7 @@ Cdiff
5 5
    :target: https://travis-ci.org/ymattw/cdiff
6 6
    :alt: Build status
7 7
 
8
-Term based tool to view **colored**, **incremental** diff in *git/svn/hg*
8
+Term based tool to view **colored**, **incremental** diff in *Git/Mercurial/Svn*
9 9
 workspace, given patch or two files, or from stdin, with **side by side** and
10 10
 **auto pager** support.  Requires python (>= 2.5.0) and ``less``.
11 11
 
@@ -63,7 +63,7 @@ Show usage:
63 63
 
64 64
     cdiff -h
65 65
 
66
-Read diff from local modification in a *svn*, *git*, or *hg* workspace:
66
+Read diff from local modification in a *Git/Mercurial/Svn* workspace:
67 67
 
68 68
 .. code:: sh
69 69
 
@@ -72,7 +72,7 @@ Read diff from local modification in a *svn*, *git*, or *hg* workspace:
72 72
     cdiff -s                    # view side by side
73 73
     cdiff -s -w 90              # use text width 90 other than default 80
74 74
 
75
-Read the log (e.g. ``git log -p``) in a *svn*, *git*, or *hg* workspace:
75
+Read the log (e.g. ``git log -p``) in a *Git/Mercurial/Svn* workspace:
76 76
 
77 77
 .. code:: sh
78 78
 

+ 68
- 44
cdiff.py View File

@@ -2,13 +2,13 @@
2 2
 # -*- coding: utf-8 -*-
3 3
 
4 4
 """
5
-Term based tool to view **colored**, **incremental** diff in *git/svn/hg*
5
+Term based tool to view **colored**, **incremental** diff in Git/Mercurial/Svn
6 6
 workspace, given patch or two files, or from stdin, with **side by side** and
7 7
 **auto pager** support.  Requires python (>= 2.5.0) and ``less``.
8 8
 """
9 9
 
10 10
 META_INFO = {
11
-    'version'     : '0.5',
11
+    'version'     : '0.5.1',
12 12
     'license'     : 'BSD-3',
13 13
     'author'      : 'Matthew Wang',
14 14
     'email'       : 'mattwyl(@)gmail(.)com',
@@ -50,12 +50,24 @@ COLORS = {
50 50
 }
51 51
 
52 52
 
53
-# Keys for checking and values for diffing.
54
-REVISION_CONTROL = (
55
-    (['git', 'rev-parse'], ['git', 'diff'], ['git', 'log', '--patch']),
56
-    (['svn', 'info'],      ['svn', 'diff'], ['svn', 'log', '--diff']),
57
-    (['hg',  'summary'],   ['hg',  'diff'], ['hg',  'log', '--patch'])
58
-)
53
+# Keys for revision control probe, diff and log with diff
54
+VCS_INFO = {
55
+    'Git': {
56
+        'probe' : ['git', 'rev-parse'],
57
+        'diff'  : ['git', 'diff'],
58
+        'log'   : ['git', 'log', '--patch'],
59
+    },
60
+    'Mercurial': {
61
+        'probe' : ['hg', 'summary'],
62
+        'diff'  : ['hg', 'diff'],
63
+        'log'   : ['hg', 'log', '--patch'],
64
+    },
65
+    'Svn': {
66
+        'probe' : ['svn', 'info'],
67
+        'diff'  : ['svn', 'diff'],
68
+        'log'   : ['svn', 'log', '--diff'],
69
+    },
70
+}
59 71
 
60 72
 
61 73
 def ansi_code(color):
@@ -86,9 +98,10 @@ class Hunk(object):
86 98
     def get_new_addr(self):
87 99
         return self._new_addr
88 100
 
89
-    def append(self, attr, line):
90
-        """attr: '-': old, '+': new, ' ': common"""
91
-        self._hunk_list.append((attr, line))
101
+    def append(self, hunk_line):
102
+        """hunk_line is a 2-element tuple: (attr, text), where attris : '-':
103
+        old, '+': new, ' ': common"""
104
+        self._hunk_list.append(hunk_line)
92 105
 
93 106
     def mdiff(self):
94 107
         r"""The difflib._mdiff() function returns an interator which returns a
@@ -134,9 +147,9 @@ class Diff(object):
134 147
         self._new_path = new_path
135 148
         self._hunks = hunks
136 149
 
137
-    # Follow detector and the parse_hunk_meta() are suppose to be overwritten
138
-    # by derived class.  No is_header() anymore, all non-recognized lines are
139
-    # considered as headers
150
+    # Following detectors, parse_hunk_meta() and parse_hunk_line() are suppose
151
+    # to be overwritten by derived class.  No is_header() anymore, all
152
+    # non-recognized lines are considered as headers
140 153
     #
141 154
     def is_old_path(self, line):
142 155
         return False
@@ -148,9 +161,14 @@ class Diff(object):
148 161
         return False
149 162
 
150 163
     def parse_hunk_meta(self, line):
151
-        """Returns a 2-eliment tuple, each of them is a tuple in form of (start,
164
+        """Returns a 2-element tuple, each of them is a tuple in form of (start,
152 165
         offset)"""
153
-        return False
166
+        return None
167
+
168
+    def parse_hunk_line(self, line):
169
+        """Returns a 2-element tuple: (attr, text), where attr is: '-': old,
170
+        '+': new, ' ': common"""
171
+        return None
154 172
 
155 173
     def is_old(self, line):
156 174
         return False
@@ -203,8 +221,9 @@ class Diff(object):
203 221
             return line.replace('\t', ' '*8).replace('\n', '').replace('\r', '')
204 222
 
205 223
         def _fit_width(markup, width, pad=False):
206
-            """str len does not count correctly if left column contains ansi
207
-            color code.  Only left side need to set `pad`
224
+            """Fit input markup to given width, pad or wrap accordingly, str len
225
+            does not count correctly if line contains ansi color code.  Only
226
+            left side need to set `pad`
208 227
             """
209 228
             out = []
210 229
             count = 0
@@ -214,6 +233,9 @@ class Diff(object):
214 233
 
215 234
             while markup and count < width:
216 235
                 if patt.match(markup):
236
+                    # Extract the ansi color code seq to target output and
237
+                    # remove the seq from input markup, no update on counter 
238
+                    #
217 239
                     out.append(patt.sub(r'\1', markup))
218 240
                     markup = patt.sub(r'\3', markup)
219 241
                 else:
@@ -227,7 +249,7 @@ class Diff(object):
227 249
                     markup = markup[1:]
228 250
 
229 251
             if count == width and repl.sub('', markup):
230
-                # stripped: output fulfil and still have ascii in markup
252
+                # Was stripped: output fulfil and still has ascii in markup
231 253
                 out[-1] = ansi_code('reset') + colorize('>', 'lightmagenta')
232 254
             elif count < width and pad:
233 255
                 pad_len = width - count
@@ -366,6 +388,9 @@ class Udiff(Diff):
366 388
 
367 389
         return (old_addr, new_addr)
368 390
 
391
+    def parse_hunk_line(self, line):
392
+        return (line[0], line[1:])
393
+
369 394
     def is_old(self, line):
370 395
         """Exclude header line from svn log --diff output"""
371 396
         return line.startswith('-') and not self.is_old_path(line) and \
@@ -387,33 +412,33 @@ class PatchStream(object):
387 412
 
388 413
     def __init__(self, diff_hdl):
389 414
         self._diff_hdl = diff_hdl
390
-        self._header_chunk_size = 0
391
-        self._header_chunk = []
415
+        self._stream_header_size = 0
416
+        self._stream_header = []
392 417
 
393 418
         # Test whether stream is empty by read 1 line
394 419
         line = self._diff_hdl.readline()
395
-        if line is None:
420
+        if not line:
396 421
             self._is_empty = True
397 422
         else:
398
-            self._header_chunk.append(line)
399
-            self._header_chunk_size += 1
423
+            self._stream_header.append(line)
424
+            self._stream_header_size += 1
400 425
             self._is_empty = False
401 426
 
402 427
     def is_empty(self):
403 428
         return self._is_empty
404 429
 
405
-    def read_header_chunks(self, header_chunk_size):
430
+    def read_stream_header(self, stream_header_size):
406 431
         """Returns a small chunk for patch type detect, suppose to call once"""
407
-        for i in range(1, header_chunk_size):
432
+        for i in range(1, stream_header_size):
408 433
             line = self._diff_hdl.readline()
409
-            if line is None:
434
+            if not line:
410 435
                 break
411
-            self._header_chunk.append(line)
412
-            self._header_chunk_size += 1
413
-            yield line
436
+            self._stream_header.append(line)
437
+            self._stream_header_size += 1
438
+        return self._stream_header
414 439
 
415 440
     def __iter__(self):
416
-        for line in self._header_chunk:
441
+        for line in self._stream_header:
417 442
             yield line
418 443
         for line in self._diff_hdl:
419 444
             yield line
@@ -428,7 +453,7 @@ class DiffParser(object):
428 453
         self._stream = stream
429 454
 
430 455
         flag = 0
431
-        for line in self._stream.read_header_chunks(100):
456
+        for line in self._stream.read_stream_header(100):
432 457
             line = decode(line)
433 458
             if line.startswith('--- '):
434 459
                 flag |= 1
@@ -436,7 +461,7 @@ class DiffParser(object):
436 461
                 flag |= 2
437 462
             elif line.startswith('@@ ') or line.startswith('## '):
438 463
                 flag |= 4
439
-            if flag & 7:
464
+            if (flag & 7) == 7:
440 465
                 self._type = 'udiff'
441 466
                 break
442 467
         else:
@@ -481,8 +506,7 @@ class DiffParser(object):
481 506
 
482 507
             elif len(diff._hunks) > 0 and (difflet.is_old(line) or \
483 508
                     difflet.is_new(line) or difflet.is_common(line)):
484
-                hunk_line = line
485
-                diff._hunks[-1].append(hunk_line[0], hunk_line[1:])
509
+                diff._hunks[-1].append(difflet.parse_hunk_line(line))
486 510
 
487 511
             elif difflet.is_eof(line):
488 512
                 # ignore
@@ -557,16 +581,16 @@ def check_command_status(arguments):
557 581
 
558 582
 def revision_control_diff():
559 583
     """Return diff from revision control system."""
560
-    for check, diff, _ in REVISION_CONTROL:
561
-        if check_command_status(check):
562
-            return subprocess.Popen(diff, stdout=subprocess.PIPE).stdout
584
+    for _, ops in VCS_INFO.items():
585
+        if check_command_status(ops['probe']):
586
+            return subprocess.Popen(ops['diff'], stdout=subprocess.PIPE).stdout
563 587
 
564 588
 
565 589
 def revision_control_log():
566 590
     """Return log from revision control system."""
567
-    for check, _, log in REVISION_CONTROL:
568
-        if check_command_status(check):
569
-            return subprocess.Popen(log, stdout=subprocess.PIPE).stdout
591
+    for _, ops in VCS_INFO.items():
592
+        if check_command_status(ops['probe']):
593
+            return subprocess.Popen(ops['log'], stdout=subprocess.PIPE).stdout
570 594
 
571 595
 
572 596
 def decode(line):
@@ -580,7 +604,7 @@ def decode(line):
580 604
 def main():
581 605
     import optparse
582 606
 
583
-    supported_vcs = [check[0][0] for check in REVISION_CONTROL]
607
+    supported_vcs = sorted(VCS_INFO.keys())
584 608
 
585 609
     usage = """
586 610
   %prog [options]
@@ -589,14 +613,14 @@ def main():
589 613
     parser = optparse.OptionParser(usage=usage,
590 614
             description=META_INFO['description'],
591 615
             version='%%prog %s' % META_INFO['version'])
592
-    parser.add_option('-c', '--color', default='auto', metavar='WHEN',
593
-            help='colorize mode "auto" (default), "always", or "never"')
594 616
     parser.add_option('-s', '--side-by-side', action='store_true',
595 617
             help='show in side-by-side mode')
596 618
     parser.add_option('-w', '--width', type='int', default=80, metavar='N',
597 619
             help='set text width (side-by-side mode only), default is 80')
598 620
     parser.add_option('-l', '--log', action='store_true',
599 621
             help='show diff log from revision control')
622
+    parser.add_option('-c', '--color', default='auto', metavar='X',
623
+            help='colorize mode "auto" (default), "always", or "never"')
600 624
     opts, args = parser.parse_args()
601 625
 
602 626
     if opts.log: