Bläddra i källkod

- 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 år sedan
förälder
incheckning
7debe071d5
3 ändrade filer med 78 tillägg och 47 borttagningar
  1. 7
    0
      CHANGES
  2. 3
    3
      README.rst
  3. 68
    44
      cdiff.py

+ 7
- 0
CHANGES Visa fil

2
 Change log
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
 Version 0.5 (2013-02-18)
12
 Version 0.5 (2013-02-18)
6
 
13
 
7
   - Support read output from ``svn diff --log`` and ``hg log -p``
14
   - Support read output from ``svn diff --log`` and ``hg log -p``

+ 3
- 3
README.rst Visa fil

5
    :target: https://travis-ci.org/ymattw/cdiff
5
    :target: https://travis-ci.org/ymattw/cdiff
6
    :alt: Build status
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
 workspace, given patch or two files, or from stdin, with **side by side** and
9
 workspace, given patch or two files, or from stdin, with **side by side** and
10
 **auto pager** support.  Requires python (>= 2.5.0) and ``less``.
10
 **auto pager** support.  Requires python (>= 2.5.0) and ``less``.
11
 
11
 
63
 
63
 
64
     cdiff -h
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
 .. code:: sh
68
 .. code:: sh
69
 
69
 
72
     cdiff -s                    # view side by side
72
     cdiff -s                    # view side by side
73
     cdiff -s -w 90              # use text width 90 other than default 80
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
 .. code:: sh
77
 .. code:: sh
78
 
78
 

+ 68
- 44
cdiff.py Visa fil

2
 # -*- coding: utf-8 -*-
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
 workspace, given patch or two files, or from stdin, with **side by side** and
6
 workspace, given patch or two files, or from stdin, with **side by side** and
7
 **auto pager** support.  Requires python (>= 2.5.0) and ``less``.
7
 **auto pager** support.  Requires python (>= 2.5.0) and ``less``.
8
 """
8
 """
9
 
9
 
10
 META_INFO = {
10
 META_INFO = {
11
-    'version'     : '0.5',
11
+    'version'     : '0.5.1',
12
     'license'     : 'BSD-3',
12
     'license'     : 'BSD-3',
13
     'author'      : 'Matthew Wang',
13
     'author'      : 'Matthew Wang',
14
     'email'       : 'mattwyl(@)gmail(.)com',
14
     'email'       : 'mattwyl(@)gmail(.)com',
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
 def ansi_code(color):
73
 def ansi_code(color):
86
     def get_new_addr(self):
98
     def get_new_addr(self):
87
         return self._new_addr
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
     def mdiff(self):
106
     def mdiff(self):
94
         r"""The difflib._mdiff() function returns an interator which returns a
107
         r"""The difflib._mdiff() function returns an interator which returns a
134
         self._new_path = new_path
147
         self._new_path = new_path
135
         self._hunks = hunks
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
     def is_old_path(self, line):
154
     def is_old_path(self, line):
142
         return False
155
         return False
148
         return False
161
         return False
149
 
162
 
150
     def parse_hunk_meta(self, line):
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
         offset)"""
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
     def is_old(self, line):
173
     def is_old(self, line):
156
         return False
174
         return False
203
             return line.replace('\t', ' '*8).replace('\n', '').replace('\r', '')
221
             return line.replace('\t', ' '*8).replace('\n', '').replace('\r', '')
204
 
222
 
205
         def _fit_width(markup, width, pad=False):
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
             out = []
228
             out = []
210
             count = 0
229
             count = 0
214
 
233
 
215
             while markup and count < width:
234
             while markup and count < width:
216
                 if patt.match(markup):
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
                     out.append(patt.sub(r'\1', markup))
239
                     out.append(patt.sub(r'\1', markup))
218
                     markup = patt.sub(r'\3', markup)
240
                     markup = patt.sub(r'\3', markup)
219
                 else:
241
                 else:
227
                     markup = markup[1:]
249
                     markup = markup[1:]
228
 
250
 
229
             if count == width and repl.sub('', markup):
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
                 out[-1] = ansi_code('reset') + colorize('>', 'lightmagenta')
253
                 out[-1] = ansi_code('reset') + colorize('>', 'lightmagenta')
232
             elif count < width and pad:
254
             elif count < width and pad:
233
                 pad_len = width - count
255
                 pad_len = width - count
366
 
388
 
367
         return (old_addr, new_addr)
389
         return (old_addr, new_addr)
368
 
390
 
391
+    def parse_hunk_line(self, line):
392
+        return (line[0], line[1:])
393
+
369
     def is_old(self, line):
394
     def is_old(self, line):
370
         """Exclude header line from svn log --diff output"""
395
         """Exclude header line from svn log --diff output"""
371
         return line.startswith('-') and not self.is_old_path(line) and \
396
         return line.startswith('-') and not self.is_old_path(line) and \
387
 
412
 
388
     def __init__(self, diff_hdl):
413
     def __init__(self, diff_hdl):
389
         self._diff_hdl = diff_hdl
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
         # Test whether stream is empty by read 1 line
418
         # Test whether stream is empty by read 1 line
394
         line = self._diff_hdl.readline()
419
         line = self._diff_hdl.readline()
395
-        if line is None:
420
+        if not line:
396
             self._is_empty = True
421
             self._is_empty = True
397
         else:
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
             self._is_empty = False
425
             self._is_empty = False
401
 
426
 
402
     def is_empty(self):
427
     def is_empty(self):
403
         return self._is_empty
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
         """Returns a small chunk for patch type detect, suppose to call once"""
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
             line = self._diff_hdl.readline()
433
             line = self._diff_hdl.readline()
409
-            if line is None:
434
+            if not line:
410
                 break
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
     def __iter__(self):
440
     def __iter__(self):
416
-        for line in self._header_chunk:
441
+        for line in self._stream_header:
417
             yield line
442
             yield line
418
         for line in self._diff_hdl:
443
         for line in self._diff_hdl:
419
             yield line
444
             yield line
428
         self._stream = stream
453
         self._stream = stream
429
 
454
 
430
         flag = 0
455
         flag = 0
431
-        for line in self._stream.read_header_chunks(100):
456
+        for line in self._stream.read_stream_header(100):
432
             line = decode(line)
457
             line = decode(line)
433
             if line.startswith('--- '):
458
             if line.startswith('--- '):
434
                 flag |= 1
459
                 flag |= 1
436
                 flag |= 2
461
                 flag |= 2
437
             elif line.startswith('@@ ') or line.startswith('## '):
462
             elif line.startswith('@@ ') or line.startswith('## '):
438
                 flag |= 4
463
                 flag |= 4
439
-            if flag & 7:
464
+            if (flag & 7) == 7:
440
                 self._type = 'udiff'
465
                 self._type = 'udiff'
441
                 break
466
                 break
442
         else:
467
         else:
481
 
506
 
482
             elif len(diff._hunks) > 0 and (difflet.is_old(line) or \
507
             elif len(diff._hunks) > 0 and (difflet.is_old(line) or \
483
                     difflet.is_new(line) or difflet.is_common(line)):
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
             elif difflet.is_eof(line):
511
             elif difflet.is_eof(line):
488
                 # ignore
512
                 # ignore
557
 
581
 
558
 def revision_control_diff():
582
 def revision_control_diff():
559
     """Return diff from revision control system."""
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
 def revision_control_log():
589
 def revision_control_log():
566
     """Return log from revision control system."""
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
 def decode(line):
596
 def decode(line):
580
 def main():
604
 def main():
581
     import optparse
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
     usage = """
609
     usage = """
586
   %prog [options]
610
   %prog [options]
589
     parser = optparse.OptionParser(usage=usage,
613
     parser = optparse.OptionParser(usage=usage,
590
             description=META_INFO['description'],
614
             description=META_INFO['description'],
591
             version='%%prog %s' % META_INFO['version'])
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
     parser.add_option('-s', '--side-by-side', action='store_true',
616
     parser.add_option('-s', '--side-by-side', action='store_true',
595
             help='show in side-by-side mode')
617
             help='show in side-by-side mode')
596
     parser.add_option('-w', '--width', type='int', default=80, metavar='N',
618
     parser.add_option('-w', '--width', type='int', default=80, metavar='N',
597
             help='set text width (side-by-side mode only), default is 80')
619
             help='set text width (side-by-side mode only), default is 80')
598
     parser.add_option('-l', '--log', action='store_true',
620
     parser.add_option('-l', '--log', action='store_true',
599
             help='show diff log from revision control')
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
     opts, args = parser.parse_args()
624
     opts, args = parser.parse_args()
601
 
625
 
602
     if opts.log:
626
     if opts.log: