瀏覽代碼

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,8 +1,9 @@
1 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 8
 .. image:: http://ymattw.github.com/cdiff/img/default.png
8 9
    :alt: default

+ 2
- 2
setup.py 查看文件

@@ -31,8 +31,8 @@ setup(
31 31
     author = 'Matthew Wang',
32 32
     author_email = 'mattwyl(@)gmail(.)com',
33 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 36
     long_description = long_description,
37 37
     keywords = 'colored incremental side-by-side diff',
38 38
     url = 'https://github.com/ymattw/cdiff',

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

@@ -2,8 +2,9 @@
2 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 9
 AUTHOR  : Matthew Wang <mattwyl(@)gmail(.)com>
9 10
 LICENSE : BSD-3
@@ -127,6 +128,38 @@ class Diff(object):
127 128
         self._new_path = new_path
128 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 163
     def markup_traditional(self):
131 164
         """Returns a generator"""
132 165
         for line in self._headers:
@@ -295,47 +328,64 @@ class Diff(object):
295 328
 
296 329
 class Udiff(Diff):
297 330
 
298
-    @staticmethod
299
-    def is_old_path(line):
331
+    def is_old_path(self, line):
300 332
         return line.startswith('--- ')
301 333
 
302
-    @staticmethod
303
-    def is_new_path(line):
334
+    def is_new_path(self, line):
304 335
         return line.startswith('+++ ')
305 336
 
306
-    @staticmethod
307
-    def is_hunk_header(line):
337
+    def is_hunk_header(self, line):
308 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 365
         return line.startswith(' ')
321 366
 
322
-    @staticmethod
323
-    def is_eof(line):
367
+    def is_eof(self, line):
324 368
         # \ No newline at end of file
325 369
         return line.startswith('\\')
326 370
 
327
-    @staticmethod
328
-    def is_header(line):
371
+    def is_header(self, line):
329 372
         return re.match(r'^[^+@\\ -]', line)
330 373
 
331 374
 
332 375
 class DiffParser(object):
333 376
 
334 377
     def __init__(self, stream):
378
+        """Detect Udiff with 3 conditions"""
379
+        flag = 0
335 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 389
         else:
340 390
             raise RuntimeError('unknown diff type')
341 391
 
@@ -344,18 +394,16 @@ class DiffParser(object):
344 394
         except (AssertionError, IndexError):
345 395
             raise RuntimeError('invalid patch format')
346 396
 
347
-
348 397
     def get_diffs(self):
349 398
         return self._diffs
350 399
 
351 400
     def _parse(self, stream):
401
+        """parse all diff lines, construct a list of Diff objects"""
352 402
         if self._type == 'udiff':
353
-            return self._parse_udiff(stream)
403
+            difflet = Udiff(None, None, None, None)
354 404
         else:
355 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 407
         out_diffs = []
360 408
         headers = []
361 409
         old_path = None
@@ -367,8 +415,8 @@ class DiffParser(object):
367 415
             # 'common' line occurs before 'old_path' is considered as header
368 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 420
                 if headers and old_path:
373 421
                     # Encounter a new header
374 422
                     assert new_path is not None
@@ -383,7 +431,7 @@ class DiffParser(object):
383 431
                 else:
384 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 435
                 if old_path:
388 436
                     # Encounter a new patch set
389 437
                     assert new_path is not None
@@ -398,12 +446,12 @@ class DiffParser(object):
398 446
                 else:
399 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 450
                 assert old_path is not None
403 451
                 assert new_path is None
404 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 455
                 assert old_path is not None
408 456
                 assert new_path is not None
409 457
                 if hunk:
@@ -411,32 +459,19 @@ class DiffParser(object):
411 459
                     hunks.append(hunk)
412 460
                     hunk = None
413 461
                 else:
414
-                    # @@ -3,7 +3,6 @@
415 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 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 468
                 assert old_path is not None
434 469
                 assert new_path is not None
435 470
                 assert hunk is not None
436 471
                 hunk_line = stream.pop(0)
437 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 475
                 # ignore
441 476
                 stream.pop(0)
442 477
 
@@ -526,11 +561,9 @@ def main():
526 561
     supported_vcs = [check[0] for check, _ in REVISION_CONTROL]
527 562
 
528 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 568
     parser = optparse.OptionParser(usage=usage, description=description,
536 569
             version='%%prog %s' % __version__)