On this page
difflib —计算增量的助手
源代码: Lib/difflib.py
此模块提供用于比较序列的类和Function。例如,它可以用于比较文件,并可以产生各种格式的差异信息,包括 HTML 和上下文以及统一的 diff。要比较目录和文件,另请参见filecmp模块。
- 类别
difflib.
SequenceMatcher
- 这是用于比较任何类型的序列对的灵活类,只要序列元素为hashable即可。基本算法早于 1980 年代末由 Ratcliff 和 Obershelp 在双曲线名称“格式塔模式匹配”下发布的算法,并且比在以前的算法更新颖。这个想法是找到最长的不包含“垃圾”元素的连续匹配子序列。这些“垃圾”元素在某种意义上是无趣的,例如空白行或空白。 (处理垃圾是对 Ratcliff 和 Obershelp 算法的扩展.)然后,将相同的思想递归应用于匹配子序列左侧和右侧的片段。这不会产生最小的编辑序列,但是会产生对人们“看起来正确”的匹配。
时间: 基本的 Ratcliff-Obershelp 算法在最坏的情况下是三次时间,在预期的情况下是二次时间。 SequenceMatcher是最坏情况的二次时间,其预期情况的行为以复杂的方式取决于序列共有多少个元素;最好的情况是线性的。
自动垃圾启发式: SequenceMatcher支持一种将某些序列项自动视为垃圾的启发式。试探法计算每个单个项目出现在序列中的次数。如果某项的重复项(在第一个项之后)占序列的 1%以上,并且该序列的长度至少为 200,则该项目被标记为“受欢迎”,并且出于序列匹配的目的被视为垃圾。可以pass在创建SequenceMatcher时将autojunk
参数设置为False
来关闭此启发式。
版本 3.2 中的新Function:* autojunk *参数。
- 类别
difflib.
Differ
- 这是一类用于比较文本行序列并产生人类可读的差异或增量的类。 Differ 使用SequenceMatcher来比较行序列,并比较相似(近匹配)行中的字符序列。
Differ delta 的每一行都以两个字母的代码开头:
Code | Meaning |
---|---|
'- ' |
序列 1 特有的行 |
'+ ' |
序列 2 特有的行 |
' ' |
这两个序列共有的线 |
'? ' |
这两个 Importing 序列中都不存在行 |
以“ ?
”开头的行试图将眼睛引导至行内差异,但在任一 Importing 序列中均不存在。如果序列包含制表符,这些行可能会造成混淆。
- 类别
difflib.
HtmlDiff
- 此类可用于创建 HTML 表(或包含该表的完整 HTML 文件),以显示并排,逐行比较带有行间和行内更改突出显示的文本。该表可以完全或上下文差异模式生成。
此类的构造函数为:
__init__
(* tabsize = 8 , wrapcolumn = None , linejunk = None , charjunk = IS_CHARACTER_JUNK *)- 初始化HtmlDiff的实例。
tabsize *是一个可选的关键字参数,用于指定制表位的间距,默认为
8
。wrapcolumn *是一个可选关键字,用于指定断行和换行的列号,默认为
None
(不换行)。linejunk 和 charjunk *是传递给ndiff()的可选关键字参数(由HtmlDiff使用以生成并排的 HTML 差异)。有关参数默认值和说明,请参见ndiff()文档。
以下是公开的方法:
make_file
(* fromlines , tolines , fromdesc ='', todesc ='', context = False , numlines = 5 ,**,* charset ='utf-8'*)- 比较* fromlines 和 tolines *(字符串列表)并返回一个字符串,它是一个完整的 HTML 文件,其中包含一个表格,其中显示了逐行差异,并突出显示了行间和行内更改。
fromdesc 和 todesc *是可选的关键字参数,用于指定从/到文件列标题字符串(均默认为空字符串)。
context 和 numlines 都是可选的关键字参数。当要显示上下文差异时,将 context *设置为
True
,否则默认为False
以显示完整文件。 * numlines 默认为5
。当 context 为True
时, numlines 控制围绕差异高亮显示的上下文行的数量。当 context 是False
时, numlines *控制使用“下一个”超链接时在差异突出显示之前显示的行数(设置为零会导致“下一个”超链接将下一个差异突出显示放置在浏览器,没有任何领先的上下文)。
Note
- fromdesc 和 todesc *被解释为未转义的 HTML,在接收来自不受信任来源的 Importing 时应正确转义。
在版本 3.5 中更改:添加了* charset *仅关键字参数。 HTML 文档的默认字符集从'ISO-8859-1'
更改为'utf-8'
。
make_table
((* fromlines , tolines , fromdesc ='', todesc ='', context = False , numlines = 5 *)- 比较* fromlines 和 tolines *(字符串列表)并返回一个字符串,该字符串是一个完整的 HTML 表,显示逐行差异,并突出显示行间和行内更改。
此方法的参数与make_file()方法的参数相同。
Tools/scripts/diff.py
是此类的命令行前端,并且包含一个很好的用法示例。
difflib.
context_diff
(* a , b , fromfile ='', tofile ='', fromfiledate ='', tofiledate ='', n = 3 , lineterm =' n'*)- 比较* a 和 b *(字符串列表);以上下文差异格式返回增量(生成增量行的generator)。
上下文差异是一种仅显示已更改的行以及上下文的几行的紧凑方式。更改以之前/之后的样式显示。上下文行的数量由* n *设置,默认为三。
默认情况下,差异控制线(带有***
或---
的控制线)使用尾随换行符创建。这很有用,因为从io.IOBase.readlines()创建的 Importing 将导致差异适合与io.IOBase.writelines()一起使用,因为 Importing 和输出均具有尾随换行符。
对于没有尾随换行符的 Importing,请将* lineterm *参数设置为""
,以使输出一致地没有换行符。
上下文差异格式通常具有文件名和修改时间的 Headers。可以使用* fromfile , tofile , fromfiledate 和 tofiledate *的字符串指定其中的任何一个或全部。修改时间通常以 ISO 8601 格式表示。如果未指定,则字符串默认为空白。
>>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n']
>>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n']
>>> sys.stdout.writelines(context_diff(s1, s2, fromfile='before.py', tofile='after.py'))
*** before.py
--- after.py
***************
*** 1,4 ****
! bacon
! eggs
! ham
guido
--- 1,4 ----
! python
! eggy
! hamster
guido
有关更详细的示例,请参见difflib 的命令行界面。
difflib.
get_close_matches
(* word , possbilities , n = 3 , cutoff = 0.6 *)- 返回最佳“足够好”的匹配项列表。 * word 是需要紧密匹配的序列(通常是字符串), possibilities 是要与之匹配 word *的序列列表(通常是字符串列表)。
可选参数* n *(默认值3
)是要返回的最大匹配数; * n *必须大于0
。
可选参数* cutoff (默认为0.6
)是[0,1]范围内的浮点数。得分不低于 word *的可能性将被忽略。
可能性中的最佳匹配(不超过* n *个)以列表形式返回,并按相似性得分排序,最相似的为第一。
>>> get_close_matches('appel', ['ape', 'apple', 'peach', 'puppy'])
['apple', 'ape']
>>> import keyword
>>> get_close_matches('wheel', keyword.kwlist)
['while']
>>> get_close_matches('pineapple', keyword.kwlist)
[]
>>> get_close_matches('accept', keyword.kwlist)
['except']
difflib.
ndiff
(* a , b , linejunk = None , charjunk = IS_CHARACTER_JUNK *)
可选关键字参数* linejunk 和 charjunk *是过滤Function(或None
):
linejunk *:一个函数,它接受单个字符串参数,如果字符串为垃圾,则返回 true,否则返回 false。默认值为
None
。还有一个模块级FunctionIS_LINE_JUNK(),过滤掉不带可见字符的行,但最多不超过一磅字符('#'
)–但是底层的SequenceMatcher类对哪些行是如此频繁以至于构成噪声进行了动态分析,并且这通常比使用此Function更好。charjunk *:一个函数,该函数接受一个字符(长度为 1 的字符串),并在字符为垃圾字符时返回,否则返回 false。默认值为模块级别的函数IS_CHARACTER_JUNK(),该函数过滤出空格字符(空格或制表符;在其中包括换行符是个坏主意!)。
Tools/scripts/ndiff.py
是此Function的命令行前端。
>>> diff = ndiff('one\ntwo\nthree\n'.splitlines(keepends=True),
... 'ore\ntree\nemu\n'.splitlines(keepends=True))
>>> print(''.join(diff), end="")
- one
? ^
+ ore
? ^
- two
- three
? -
+ tree
+ emu
difflib.
restore
(序列,其中)- 返回生成增量的两个序列之一。
给定Differ.compare()或ndiff()产生的* sequence ,提取源自文件 1 或 2 的行(参数 which *),去除行前缀。
Example:
>>> diff = ndiff('one\ntwo\nthree\n'.splitlines(keepends=True),
... 'ore\ntree\nemu\n'.splitlines(keepends=True))
>>> diff = list(diff) # materialize the generated delta into a list
>>> print(''.join(restore(diff, 1)), end="")
one
two
three
>>> print(''.join(restore(diff, 2)), end="")
ore
tree
emu
difflib.
unified_diff
(* a , b , fromfile ='', tofile ='', fromfiledate ='', tofiledate ='', n = 3 , lineterm =' n'*)- 比较* a 和 b *(字符串列表);以统一的 diff 格式返回增量(生成增量行的generator)。
统一差异是仅显示已更改的行以及上下文的几行的紧凑方式。更改以内联样式显示(而不是单独的 before/after 块)。上下文行的数量由* n *设置,默认为三。
默认情况下,差异控制线(带有---
,+++
或@@
的控制线)以尾随换行符创建。这很有用,因为从io.IOBase.readlines()创建的 Importing 将导致差异适合与io.IOBase.writelines()一起使用,因为 Importing 和输出均具有尾随换行符。
对于没有尾随换行符的 Importing,请将* lineterm *参数设置为""
,以使输出一致地没有换行符。
上下文差异格式通常具有文件名和修改时间的 Headers。可以使用* fromfile , tofile , fromfiledate 和 tofiledate *的字符串指定其中的任何一个或全部。修改时间通常以 ISO 8601 格式表示。如果未指定,则字符串默认为空白。
>>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n']
>>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n']
>>> sys.stdout.writelines(unified_diff(s1, s2, fromfile='before.py', tofile='after.py'))
--- before.py
+++ after.py
@@ -1,4 +1,4 @@
-bacon
-eggs
-ham
+python
+eggy
+hamster
guido
有关更详细的示例,请参见difflib 的命令行界面。
difflib.
diff_bytes
(* dfunc , a , b , fromfile = b'', tofile = b'', fromfiledate = b'', tofiledate = b'', n = 3 , lineterm = b' n'*)- 使用* dfunc 比较 a 和 b (字节对象列表);以 dfunc *返回的格式产生一系列增量线(也为字节)。 * dfunc *必须是可调用的,通常是unified_diff()或context_diff()。
使您可以比较编码未知或不一致的数据。除* n 外的所有 Importing 都必须是字节对象,而不是 str。pass将所有 Importing( n 除外)无损转换为 str 并调用dfunc(a, b, fromfile, tofile, fromfiledate, tofiledate, n, lineterm)
来工作。然后, dfunc 的输出将转换回字节,因此您收到的增量行与 a 和 b *具有相同的未知/不一致编码。
3.5 版中的新Function。
difflib.
IS_LINE_JUNK
(* line *)- 返回
True
表示可忽略的行。如果* line 为空白或包含单个'#'
,则 line 行是可忽略的,否则不可忽略。用作旧版本ndiff()中参数 linejunk *的默认值。
- 返回
difflib.
IS_CHARACTER_JUNK
(* ch *)- 返回
True
以获取可忽略字符。如果* ch 是空格或制表符,则字符 ch 是可忽略的,否则不可忽略。用作ndiff()中参数 charjunk *的默认值。
- 返回
See also
John W. Ratcliff 和 D.E. Metzener 对类似算法的讨论。该版本于 1988 年 7 月在多布博士的日记发表。
SequenceMatcher Objects
SequenceMatcher类具有以下构造函数:
-
- class *
difflib.
SequenceMatcher
(* isjunk = None , a ='', b ='', autojunk = True *)
- 可选参数* isjunk 必须为
None
(默认值),或者是一个带参数的元素的单参数函数,当且仅当该元素为“垃圾”且应将其忽略时才返回 true。为 isjunk *传递None
等效于lambda x: False
传递;换句话说,不会忽略任何元素。例如,传递:
- class *
lambda x: x in " \t"
如果要将行作为字符序列进行比较,并且不想在空格或硬标签上同步。
可选参数* a 和 b *是要比较的序列;两者都默认为空字符串。两个序列的元素必须为hashable。
可选参数* autojunk *可用于禁用自动垃圾启发式。
版本 3.2 中的新Function:* autojunk *参数。
SequenceMatcher 对象获得三个数据属性:* bjunk 是 b 的元素集,其中 isjunk *为True
; * bpopular *是启发式方法(如果未禁用)认为流行的非垃圾元素集; * b2j 是将 b 其余元素 Map 到它们出现的位置列表的字典。每当 b *用set_seqs()或set_seq2()重置时,这三个都将重置。
版本 3.2 中的新Function:* bjunk 和 bpopular *属性。
SequenceMatcher对象具有以下方法:
set_seqs
(* a , b *)- 设置两个要比较的序列。
SequenceMatcher计算并缓存有关第二个序列的详细信息,因此,如果要将一个序列与多个序列进行比较,请使用set_seq2()一次设置常用序列,然后针对每个其他序列重复调用set_seq1()。
set_seq1
(* a *)- 设置要比较的第一个序列。要比较的第二个 Sequences 不变。
set_seq2
(* b *)- 设置要比较的第二个序列。要比较的第一个序列不变。
find_longest_match
((* alo , ahi , blo , bhi *)- 在
a[alo:ahi]
和b[blo:bhi]
中找到最长的匹配块。
- 在
如果Ellipsis* isjunk 或None
,则find_longest_match()返回(i, j, k)
,使得a[i:i+k]
等于b[j:j+k]
,其中alo <= i <= i+k <= ahi
和blo <= j <= j+k <= bhi
。对于所有满足这些条件的(i', j', k')
,附加条件k >= k'
,i <= i'
,以及i == i'
,j <= j'
也要满足。换句话说,在所有最大匹配块中,返回最早在 a 中开始的块,在所有最大匹配块中,最早返回 a 中的块,返回最早在 b *中开始的块。
>>> s = SequenceMatcher(None, " abcd", "abcd abcd")
>>> s.find_longest_match(0, 5, 0, 9)
Match(a=0, b=4, size=5)
如果提供了* isjunk *,则首先如上所述确定最长的匹配块,但附加的限制是该块中不会出现垃圾元素。然后,pass在两侧匹配(仅)垃圾元素来尽可能扩展该块。因此,结果块永远不会在垃圾上匹配,除非相同的垃圾恰好与某个有趣的匹配相邻。
这是与之前相同的示例,但考虑到空白是垃圾。这样可以防止' abcd'
直接匹配第二个序列尾部的' abcd'
。相反,只有'abcd'
可以匹配,并且与第二个序列中最左边的'abcd'
匹配:
>>> s = SequenceMatcher(lambda x: x==" ", " abcd", "abcd abcd")
>>> s.find_longest_match(0, 5, 0, 9)
Match(a=1, b=0, size=4)
如果没有块匹配,则返回(alo, blo, 0)
。
此方法返回named tuple Match(a, b, size)
。
get_matching_blocks
( )- 返回描述非重叠匹配子序列的三 Tuples 列表。每个三 Tuples 的形式为
(i, j, n)
,表示a[i:i+n] == b[j:j+n]
。三 Tuples 在* i 和 j *中单调增加。
- 返回描述非重叠匹配子序列的三 Tuples 列表。每个三 Tuples 的形式为
最后一个三 Tuples 是一个虚拟对象,其值为(len(a), len(b), 0)
。它是n == 0
的唯一三 Tuples。如果(i, j, n)
和(i', j', n')
是列表中的相邻三 Tuples,而第二个不是列表中的最后一个三 Tuples,则i+n < i'
或j+n < j'
;换句话说,相邻的三 Tuples 总是描述不相邻的相等块。
>>> s = SequenceMatcher(None, "abxcd", "abcd")
>>> s.get_matching_blocks()
[Match(a=0, b=0, size=2), Match(a=3, b=2, size=2), Match(a=5, b=4, size=0)]
get_opcodes
( )- 返回 5Tuples 的列表,描述如何将* a 转换为 b 。每个 Tuples 的形式为
(tag, i1, i2, j1, j2)
。第一个 Tuples 具有i1 == j1 == 0
,其余的 Tuples 的 i1 等于前一个 Tuples 的 i2 ,同样, j1 等于前一个 j2 *。
- 返回 5Tuples 的列表,描述如何将* a 转换为 b 。每个 Tuples 的形式为
- tag *值是字符串,具有以下含义:
Value | Meaning |
---|---|
'replace' |
a[i1:i2] 应替换为b[j1:j2] 。 |
'delete' |
a[i1:i2] 应该被删除。请注意,在这种情况下,j1 == j2 。 |
'insert' |
b[j1:j2] 应该插入a[i1:i1] 。请注意,在这种情况下为i1 == i2 。 |
'equal' |
a[i1:i2] == b[j1:j2] (子序列相等)。 |
For example:
>>> a = "qabxcd"
>>> b = "abycdf"
>>> s = SequenceMatcher(None, a, b)
>>> for tag, i1, i2, j1, j2 in s.get_opcodes():
... print('{:7} a[{}:{}] --> b[{}:{}] {!r:>8} --> {!r}'.format(
... tag, i1, i2, j1, j2, a[i1:i2], b[j1:j2]))
delete a[0:1] --> b[0:0] 'q' --> ''
equal a[1:3] --> b[0:2] 'ab' --> 'ab'
replace a[3:4] --> b[2:3] 'x' --> 'y'
equal a[4:6] --> b[3:5] 'cd' --> 'cd'
insert a[6:6] --> b[5:6] '' --> 'f'
get_grouped_opcodes
(* n = 3 *)- 返回最多+1 个上下文行的generator个组。
从get_opcodes()返回的组开始,此方法会拆分较小的更改集群,并消除没有更改的中间范围。
这些组以与get_opcodes()相同的格式返回。
ratio
( )- 以[0,1]范围内的浮点数形式返回序列相似性的度量。
其中 T 是两个序列中元素的总数,M 是匹配数,这是 2.0 * M /T。请注意,如果序列相同,则为1.0
;如果没有相同之处,则为0.0
。
如果尚未调用get_matching_blocks()或get_opcodes(),则计算成本很高,在这种情况下,您可能需要先tryquick_ratio()或real_quick_ratio()以获得上限。
Note
警告:ratio()调用的结果可能取决于参数的 Sequences。例如:
>>> SequenceMatcher(None, 'tide', 'diet').ratio()
0.25
>>> SequenceMatcher(None, 'diet', 'tide').ratio()
0.5
尽管quick_ratio()
和real_quick_ratio()
总是至少与ratio()
一样大,但由于逼近的不同程度,返回匹配字符与总字符的比率的三种方法可能会得出不同的结果:
>>> s = SequenceMatcher(None, "abcd", "bcde")
>>> s.ratio()
0.75
>>> s.quick_ratio()
0.75
>>> s.real_quick_ratio()
1.0
SequenceMatcher Examples
本示例比较两个字符串,并将空格视为“垃圾”:
>>> s = SequenceMatcher(lambda x: x == " ",
... "private Thread currentThread;",
... "private volatile Thread currentThread;")
ratio()
返回[0,1]中的浮点数,以测量序列的相似性。根据经验,当ratio()
的值超过 0.6 时,表示序列是紧密匹配的:
>>> print(round(s.ratio(), 3))
0.866
如果您只对序列匹配的位置感兴趣,则get_matching_blocks()
很方便:
>>> for block in s.get_matching_blocks():
... print("a[%d] and b[%d] match for %d elements" % block)
a[0] and b[0] match for 8 elements
a[8] and b[17] match for 21 elements
a[29] and b[38] match for 0 elements
请注意,get_matching_blocks()
返回的最后一个 Tuples 总是(len(a), len(b), 0)
,这是唯一的情况,最后一个 Tuples 元素(匹配的元素数)是0
。
如果您想知道如何将第一个序列更改为第二个序列,请使用get_opcodes()
:
>>> for opcode in s.get_opcodes():
... print("%6s a[%d:%d] b[%d:%d]" % opcode)
equal a[0:8] b[0:8]
insert a[8:8] b[8:17]
equal a[8:29] b[17:38]
See also
此模块中的get_close_matches()函数说明如何使用在SequenceMatcher上构建简单的代码来完成有用的工作。
简单的版本控制配方用于使用SequenceMatcher构建的小型应用程序。
Differ Objects
请注意,Differ生成的增量不会声称是“最小”差异。相反,最小差异通常是违反直觉的,因为它们尽可能地同步,有时偶然相距 100 页。将同步点限制为连续匹配可以保留局部性的概念,但偶尔会产生较长的差异。
Differ类具有以下构造函数:
-
- class *
difflib.
Differ
(* linejunk = None , charjunk = None *)
- 可选关键字参数* linejunk 和 charjunk *用于过滤器Function(或
None
):
- class *
linejunk *:一个函数,它接受单个字符串参数,如果字符串为垃圾,则返回 true。默认值为
None
,这意味着没有行被视为垃圾。charjunk *:一个函数,它接受单个字符参数(长度为 1 的字符串),如果字符为垃圾,则返回 true。默认值为
None
,表示没有字符被视为垃圾字符。
这些垃圾过滤Function可加快匹配速度以发现差异,并且不会导致任何不同的行或字符被忽略。阅读find_longest_match()方法的* isjunk *参数的描述以获取解释。
Differ对象pass一种方法使用(生成增量):
compare
(* a , b *)- 比较两条线序列,并生成增量(一条线序列)。
每个序列必须包含以换行符结尾的单个单行字符串。此类序列可以从文件类对象的readlines()方法获得。生成的增量还包含换行符终止的字符串,这些字符串可以pass文件状对象的writelines()方法按原样打印。
Differ Example
本示例比较两个文本。首先,我们设置文本,以换行符结尾的单个单行字符串的序列(此类序列也可以从类似文件的对象的readlines()
方法获得):
>>> text1 = ''' 1. Beautiful is better than ugly.
... 2. Explicit is better than implicit.
... 3. Simple is better than complex.
... 4. Complex is better than complicated.
... '''.splitlines(keepends=True)
>>> len(text1)
4
>>> text1[0][-1]
'\n'
>>> text2 = ''' 1. Beautiful is better than ugly.
... 3. Simple is better than complex.
... 4. Complicated is better than complex.
... 5. Flat is better than nested.
... '''.splitlines(keepends=True)
接下来,我们实例化一个 Differ 对象:
>>> d = Differ()
请注意,在实例化Differ对象时,我们可以传递函数以过滤掉行和字符“垃圾”。有关详细信息,请参见Differ()构造函数。
最后,我们比较两个:
>>> result = list(d.compare(text1, text2))
result
是字符串列表,所以让它漂亮地打印出来:
>>> from pprint import pprint
>>> pprint(result)
[' 1. Beautiful is better than ugly.\n',
'- 2. Explicit is better than implicit.\n',
'- 3. Simple is better than complex.\n',
'+ 3. Simple is better than complex.\n',
'? ++\n',
'- 4. Complex is better than complicated.\n',
'? ^ ---- ^\n',
'+ 4. Complicated is better than complex.\n',
'? ++++ ^ ^\n',
'+ 5. Flat is better than nested.\n']
作为单个多行字符串,它看起来像这样:
>>> import sys
>>> sys.stdout.writelines(result)
1. Beautiful is better than ugly.
- 2. Explicit is better than implicit.
- 3. Simple is better than complex.
+ 3. Simple is better than complex.
? ++
- 4. Complex is better than complicated.
? ^ ---- ^
+ 4. Complicated is better than complex.
? ++++ ^ ^
+ 5. Flat is better than nested.
difflib 的命令行界面
本示例说明如何使用 difflib 创建类似diff
的 Util。它也以Tools/scripts/diff.py
的形式包含在 Python 源代码发行版中。
#!/usr/bin/env python3
""" Command line interface to difflib.py providing diffs in four formats:
* ndiff: lists every line and highlights interline changes.
* context: highlights clusters of changes in a before/after format.
* unified: highlights clusters of changes in an inline format.
* html: generates side by side comparison with change highlights.
"""
import sys, os, difflib, argparse
from datetime import datetime, timezone
def file_mtime(path):
t = datetime.fromtimestamp(os.stat(path).st_mtime,
timezone.utc)
return t.astimezone().isoformat()
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-c', action='store_true', default=False,
help='Produce a context format diff (default)')
parser.add_argument('-u', action='store_true', default=False,
help='Produce a unified format diff')
parser.add_argument('-m', action='store_true', default=False,
help='Produce HTML side by side diff '
'(can use -c and -l in conjunction)')
parser.add_argument('-n', action='store_true', default=False,
help='Produce a ndiff format diff')
parser.add_argument('-l', '--lines', type=int, default=3,
help='Set number of context lines (default 3)')
parser.add_argument('fromfile')
parser.add_argument('tofile')
options = parser.parse_args()
n = options.lines
fromfile = options.fromfile
tofile = options.tofile
fromdate = file_mtime(fromfile)
todate = file_mtime(tofile)
with open(fromfile) as ff:
fromlines = ff.readlines()
with open(tofile) as tf:
tolines = tf.readlines()
if options.u:
diff = difflib.unified_diff(fromlines, tolines, fromfile, tofile, fromdate, todate, n=n)
elif options.n:
diff = difflib.ndiff(fromlines, tolines)
elif options.m:
diff = difflib.HtmlDiff().make_file(fromlines,tolines,fromfile,tofile,context=options.c,numlines=n)
else:
diff = difflib.context_diff(fromlines, tolines, fromfile, tofile, fromdate, todate, n=n)
sys.stdout.writelines(diff)
if __name__ == '__main__':
main()