瀏覽代碼

document/usage update; refactor for better support other diff formats in future

Matthew Wang 11 年之前
父節點
當前提交
b9f5507620
共有 3 個文件被更改,包括 92 次插入58 次删除
  1. 3
    2
      README.rst
  2. 2
    2
      setup.py
  3. 87
    54
      src/cdiff.py

+ 3
- 2
README.rst 查看文件

1
 Cdiff
1
 Cdiff
2
 =====
2
 =====
3
 
3
 
4
-Term based tool to view **colored**, **incremental** diff in unified format or
5
-**side by side** with **auto pager**.  Requires python (>= 2.5.0) and ``less``.
4
+Term based tool to view **colored**, **incremental** diff in *git/svn/hg*
5
+workspace, or diff from given file or stdin, with **side by side** and **auto
6
+pager** support.  Requires python (>= 2.5.0) and ``less``.
6
 
7
 
7
 .. image:: http://ymattw.github.com/cdiff/img/default.png
8
 .. image:: http://ymattw.github.com/cdiff/img/default.png
8
    :alt: default
9
    :alt: default

+ 2
- 2
setup.py 查看文件

31
     author = 'Matthew Wang',
31
     author = 'Matthew Wang',
32
     author_email = 'mattwyl(@)gmail(.)com',
32
     author_email = 'mattwyl(@)gmail(.)com',
33
     license = 'BSD-3',
33
     license = 'BSD-3',
34
-    description = ('Term based tool to view colored, incremental diff in '
35
-                   'unified format or side by side with auto pager'),
34
+    description = ('View colored, incremental diff in workspace, or given '
35
+                   'file from stdin, with side by side and auto pager support'),
36
     long_description = long_description,
36
     long_description = long_description,
37
     keywords = 'colored incremental side-by-side diff',
37
     keywords = 'colored incremental side-by-side diff',
38
     url = 'https://github.com/ymattw/cdiff',
38
     url = 'https://github.com/ymattw/cdiff',

+ 87
- 54
src/cdiff.py 查看文件

2
 # -*- coding: utf-8 -*-
2
 # -*- coding: utf-8 -*-
3
 
3
 
4
 """
4
 """
5
-Term based tool to view colored, incremental diff in unified format or side by
6
-side with auto pager.  Requires Python (>= 2.5.0) and less.
5
+Term based tool to view **colored**, **incremental** diff in *git/svn/hg*
6
+workspace, or diff from given file or stdin, with **side by side** and **auto
7
+pager** support.  Requires python (>= 2.5.0) and ``less``.
7
 
8
 
8
 AUTHOR  : Matthew Wang <mattwyl(@)gmail(.)com>
9
 AUTHOR  : Matthew Wang <mattwyl(@)gmail(.)com>
9
 LICENSE : BSD-3
10
 LICENSE : BSD-3
127
         self._new_path = new_path
128
         self._new_path = new_path
128
         self._hunks = hunks
129
         self._hunks = hunks
129
 
130
 
131
+    # Follow detector and the parse_hunk_header() are suppose to be overwritten
132
+    # by derived class
133
+    #
134
+    def is_old_path(self, line):
135
+        return False
136
+
137
+    def is_new_path(self, line):
138
+        return False
139
+
140
+    def is_hunk_header(self, line):
141
+        return False
142
+
143
+    def parse_hunk_header(self, line):
144
+        """Returns a 2-eliment tuple, each of them is a tuple in form of (start,
145
+        offset)"""
146
+        return False
147
+
148
+    def is_old(self, line):
149
+        return False
150
+
151
+    def is_new(self, line):
152
+        return False
153
+
154
+    def is_common(self, line):
155
+        return False
156
+
157
+    def is_eof(self, line):
158
+        return False
159
+
160
+    def is_header(self, line):
161
+        return False
162
+
130
     def markup_traditional(self):
163
     def markup_traditional(self):
131
         """Returns a generator"""
164
         """Returns a generator"""
132
         for line in self._headers:
165
         for line in self._headers:
295
 
328
 
296
 class Udiff(Diff):
329
 class Udiff(Diff):
297
 
330
 
298
-    @staticmethod
299
-    def is_old_path(line):
331
+    def is_old_path(self, line):
300
         return line.startswith('--- ')
332
         return line.startswith('--- ')
301
 
333
 
302
-    @staticmethod
303
-    def is_new_path(line):
334
+    def is_new_path(self, line):
304
         return line.startswith('+++ ')
335
         return line.startswith('+++ ')
305
 
336
 
306
-    @staticmethod
307
-    def is_hunk_header(line):
337
+    def is_hunk_header(self, line):
308
         return line.startswith('@@ -')
338
         return line.startswith('@@ -')
309
 
339
 
310
-    @staticmethod
311
-    def is_old(line):
312
-        return line.startswith('-') and not Udiff.is_old_path(line)
340
+    def parse_hunk_header(self, hunk_header):
341
+        # @@ -3,7 +3,6 @@
342
+        a = hunk_header.split()[1].split(',')   # -3 7
343
+        if len(a) > 1:
344
+            old_addr = (int(a[0][1:]), int(a[1]))
345
+        else:
346
+            # @@ -1 +1,2 @@
347
+            old_addr = (int(a[0][1:]), 0)
348
+
349
+        b = hunk_header.split()[2].split(',')   # +3 6
350
+        if len(b) > 1:
351
+            new_addr = (int(b[0][1:]), int(b[1]))
352
+        else:
353
+            # @@ -0,0 +1 @@
354
+            new_addr = (int(b[0][1:]), 0)
313
 
355
 
314
-    @staticmethod
315
-    def is_new(line):
316
-        return line.startswith('+') and not Udiff.is_new_path(line)
356
+        return (old_addr, new_addr)
317
 
357
 
318
-    @staticmethod
319
-    def is_common(line):
358
+    def is_old(self, line):
359
+        return line.startswith('-') and not self.is_old_path(line)
360
+
361
+    def is_new(self, line):
362
+        return line.startswith('+') and not self.is_new_path(line)
363
+
364
+    def is_common(self, line):
320
         return line.startswith(' ')
365
         return line.startswith(' ')
321
 
366
 
322
-    @staticmethod
323
-    def is_eof(line):
367
+    def is_eof(self, line):
324
         # \ No newline at end of file
368
         # \ No newline at end of file
325
         return line.startswith('\\')
369
         return line.startswith('\\')
326
 
370
 
327
-    @staticmethod
328
-    def is_header(line):
371
+    def is_header(self, line):
329
         return re.match(r'^[^+@\\ -]', line)
372
         return re.match(r'^[^+@\\ -]', line)
330
 
373
 
331
 
374
 
332
 class DiffParser(object):
375
 class DiffParser(object):
333
 
376
 
334
     def __init__(self, stream):
377
     def __init__(self, stream):
378
+        """Detect Udiff with 3 conditions"""
379
+        flag = 0
335
         for line in stream[:20]:
380
         for line in stream[:20]:
336
-            if line.startswith('+++ '):
337
-                self._type = 'udiff'
338
-                break
381
+            if line.startswith('--- '):
382
+                flag |= 1
383
+            elif line.startswith('+++ '):
384
+                flag |= 2
385
+            elif line.startswith('@@ '):
386
+                flag |= 4
387
+        if flag & 7:
388
+            self._type = 'udiff'
339
         else:
389
         else:
340
             raise RuntimeError('unknown diff type')
390
             raise RuntimeError('unknown diff type')
341
 
391
 
344
         except (AssertionError, IndexError):
394
         except (AssertionError, IndexError):
345
             raise RuntimeError('invalid patch format')
395
             raise RuntimeError('invalid patch format')
346
 
396
 
347
-
348
     def get_diffs(self):
397
     def get_diffs(self):
349
         return self._diffs
398
         return self._diffs
350
 
399
 
351
     def _parse(self, stream):
400
     def _parse(self, stream):
401
+        """parse all diff lines, construct a list of Diff objects"""
352
         if self._type == 'udiff':
402
         if self._type == 'udiff':
353
-            return self._parse_udiff(stream)
403
+            difflet = Udiff(None, None, None, None)
354
         else:
404
         else:
355
             raise RuntimeError('unsupported diff format')
405
             raise RuntimeError('unsupported diff format')
356
 
406
 
357
-    def _parse_udiff(self, stream):
358
-        """parse all diff lines here, construct a list of Udiff objects"""
359
         out_diffs = []
407
         out_diffs = []
360
         headers = []
408
         headers = []
361
         old_path = None
409
         old_path = None
367
             # 'common' line occurs before 'old_path' is considered as header
415
             # 'common' line occurs before 'old_path' is considered as header
368
             # too, this happens with `git log -p` and `git show <commit>`
416
             # too, this happens with `git log -p` and `git show <commit>`
369
             #
417
             #
370
-            if Udiff.is_header(stream[0]) or \
371
-                    (Udiff.is_common(stream[0]) and old_path is None):
418
+            if difflet.is_header(stream[0]) or \
419
+                    (difflet.is_common(stream[0]) and old_path is None):
372
                 if headers and old_path:
420
                 if headers and old_path:
373
                     # Encounter a new header
421
                     # Encounter a new header
374
                     assert new_path is not None
422
                     assert new_path is not None
383
                 else:
431
                 else:
384
                     headers.append(stream.pop(0))
432
                     headers.append(stream.pop(0))
385
 
433
 
386
-            elif Udiff.is_old_path(stream[0]):
434
+            elif difflet.is_old_path(stream[0]):
387
                 if old_path:
435
                 if old_path:
388
                     # Encounter a new patch set
436
                     # Encounter a new patch set
389
                     assert new_path is not None
437
                     assert new_path is not None
398
                 else:
446
                 else:
399
                     old_path = stream.pop(0)
447
                     old_path = stream.pop(0)
400
 
448
 
401
-            elif Udiff.is_new_path(stream[0]):
449
+            elif difflet.is_new_path(stream[0]):
402
                 assert old_path is not None
450
                 assert old_path is not None
403
                 assert new_path is None
451
                 assert new_path is None
404
                 new_path = stream.pop(0)
452
                 new_path = stream.pop(0)
405
 
453
 
406
-            elif Udiff.is_hunk_header(stream[0]):
454
+            elif difflet.is_hunk_header(stream[0]):
407
                 assert old_path is not None
455
                 assert old_path is not None
408
                 assert new_path is not None
456
                 assert new_path is not None
409
                 if hunk:
457
                 if hunk:
411
                     hunks.append(hunk)
459
                     hunks.append(hunk)
412
                     hunk = None
460
                     hunk = None
413
                 else:
461
                 else:
414
-                    # @@ -3,7 +3,6 @@
415
                     hunk_header = stream.pop(0)
462
                     hunk_header = stream.pop(0)
416
-                    a = hunk_header.split()[1].split(',')   # -3 7
417
-                    if len(a) > 1:
418
-                        old_addr = (int(a[0][1:]), int(a[1]))
419
-                    else:
420
-                        # @@ -1 +1,2 @@
421
-                        old_addr = (int(a[0][1:]), 0)
422
-
423
-                    b = hunk_header.split()[2].split(',')   # +3 6
424
-                    if len(b) > 1:
425
-                        new_addr = (int(b[0][1:]), int(b[1]))
426
-                    else:
427
-                        # @@ -0,0 +1 @@
428
-                        new_addr = (int(b[0][1:]), 0)
463
+                    old_addr, new_addr = difflet.parse_hunk_header(hunk_header)
429
                     hunk = Hunk(hunk_header, old_addr, new_addr)
464
                     hunk = Hunk(hunk_header, old_addr, new_addr)
430
 
465
 
431
-            elif Udiff.is_old(stream[0]) or Udiff.is_new(stream[0]) or \
432
-                    Udiff.is_common(stream[0]):
466
+            elif difflet.is_old(stream[0]) or difflet.is_new(stream[0]) or \
467
+                    difflet.is_common(stream[0]):
433
                 assert old_path is not None
468
                 assert old_path is not None
434
                 assert new_path is not None
469
                 assert new_path is not None
435
                 assert hunk is not None
470
                 assert hunk is not None
436
                 hunk_line = stream.pop(0)
471
                 hunk_line = stream.pop(0)
437
                 hunk.append(hunk_line[0], hunk_line[1:])
472
                 hunk.append(hunk_line[0], hunk_line[1:])
438
 
473
 
439
-            elif Udiff.is_eof(stream[0]):
474
+            elif difflet.is_eof(stream[0]):
440
                 # ignore
475
                 # ignore
441
                 stream.pop(0)
476
                 stream.pop(0)
442
 
477
 
526
     supported_vcs = [check[0] for check, _ in REVISION_CONTROL]
561
     supported_vcs = [check[0] for check, _ in REVISION_CONTROL]
527
 
562
 
528
     usage = '%prog [options] [diff]'
563
     usage = '%prog [options] [diff]'
529
-    description= ('View colored, incremental diff in unified format or '
530
-                  'side by side with auto pager.  Read diff from diff '
531
-                  '(patch) file if given, or stdin if redirected, or '
532
-                  'diff produced by revision tool if in a %s workspace') \
533
-            % '/'.join(supported_vcs)
564
+    description= ('View colored, incremental diff in %s workspace, or diff '
565
+                  'from given file or stdin, with side by side and auto '
566
+                  'pager support') % '/'.join(supported_vcs)
534
 
567
 
535
     parser = optparse.OptionParser(usage=usage, description=description,
568
     parser = optparse.OptionParser(usage=usage, description=description,
536
             version='%%prog %s' % __version__)
569
             version='%%prog %s' % __version__)