Status: | Merged |
---|---|
Merged at revision: | 457 |
Proposed branch: | lp:~spud/spud/xmldiff |
Merge into: | lp:spud |
Diff against target: |
2760 lines (+2526/-13) 17 files modified
Makefile.in (+10/-0) debian/control (+10/-1) debian/rules (+4/-1) diamond/bin/diamond (+24/-3) diamond/diamond/diffview.py (+558/-0) diamond/diamond/interface.py (+30/-7) diamond/gui/gui.glade (+15/-1) dxdiff/COPYING (+15/-0) dxdiff/GPL-3 (+676/-0) dxdiff/dxdiff/bimap.py (+49/-0) dxdiff/dxdiff/diff.py (+26/-0) dxdiff/dxdiff/dxdiff (+87/-0) dxdiff/dxdiff/editscript.py (+79/-0) dxdiff/dxdiff/fmes.py (+466/-0) dxdiff/dxdiff/lcs.py (+284/-0) dxdiff/dxdiff/utils.py (+172/-0) dxdiff/setup.py (+21/-0) |
To merge this branch: | bzr merge lp:~spud/spud/xmldiff |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Patrick Farrell | Pending | ||
Review via email:
|
Commit message
Description of the change
Adds xmldiff tool and diff view to Diamond.
To post a comment you must log in.
lp:~spud/spud/xmldiff
updated
- 454. By Patrick Farrell
-
Set some executable flags (how did these get lost?)
Revision history for this message

Patrick Farrell (pefarrell) wrote : | # |
lp:~spud/spud/xmldiff
updated
- 455. By Fraser Waters
-
Added xmldiff/
- 456. By Fraser Waters
-
Added diffview.py
- 457. By Fraser Waters
-
Fixing pathing bugs, still more to go.
- 458. By Fraser Waters
-
Fixed more pathing bugs.
- 459. By Fraser Waters
-
Pathing bugs squashed.
- 460. By Fraser Waters
-
Renamed xmldiff to dxdiff (Diamond Xml diff). Added dxdiff to packages.
- 461. By Fraser Waters
-
Can no longer diff against save if no save.
- 462. By Fraser Waters
-
Added diamond -d [FILE] option.
- 463. By Fraser Waters
-
Don't delete text() nodes
- 464. By Fraser Waters
-
remove xpath names, fix __floodfill
- 465. By Fraser Waters
-
Properly fixed __floodfill
- 466. By Fraser Waters
-
Fixed indexing bugs
- 467. By Fraser Waters
-
Diff timed, fixed typo in _match
- 468. By Fraser Waters
-
dxdiff bug fixed
- 469. By Fraser Waters
-
Fixed indent typo, removed debuging code
- 470. By Fraser Waters
-
Removed more debug code
- 471. By Fraser Waters
-
Changed getopt to gnu_getopt
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'Makefile.in' |
2 | --- Makefile.in 2011-08-04 21:30:53 +0000 |
3 | +++ Makefile.in 2011-08-24 16:38:18 +0000 |
4 | @@ -101,11 +101,21 @@ |
5 | cd python; for python in $(shell pyversions -r); do $$python setup.py install --prefix=$(DESTDIR)@prefix@ --install-layout=deb; done; cd .. |
6 | endif |
7 | |
8 | +install-dxdiff: |
9 | +ifeq ($(origin DESTDIR),undefined) |
10 | + cd dxdiff; python setup.py install --prefix=$(DESTDIR)@prefix@; cd .. |
11 | +else |
12 | + cd dxdiff; for python in $(shell pyversions -r); do $$python setup.py install --prefix=$(DESTDIR)@prefix@ --install-layout=deb; done; cd .. |
13 | +endif |
14 | + |
15 | clean: |
16 | @cd doc; $(MAKE) clean |
17 | rm -f *.o libspud.a libspud.so *.o *.la *.mod *.lo |
18 | rm -rf .libs |
19 | @cd src/tests; $(MAKE) clean |
20 | + @cd python; rm -rf build; cd .. |
21 | + @cd dxdiff; rm -rf build; cd .. |
22 | + |
23 | |
24 | distclean: clean |
25 | @cd src/tests; $(MAKE) distclean |
26 | |
27 | === modified file 'config.guess' (properties changed: +x to -x) |
28 | === modified file 'config.sub' (properties changed: +x to -x) |
29 | === modified file 'debian/control' |
30 | --- debian/control 2011-08-15 20:50:30 +0000 |
31 | +++ debian/control 2011-08-24 16:38:18 +0000 |
32 | @@ -9,7 +9,7 @@ |
33 | |
34 | Package: diamond |
35 | Architecture: all |
36 | -Depends: python-gtk2, python-lxml, libxml2-utils, python, spudtools, python-gtksourceview2 |
37 | +Depends: python-gtk2, python-lxml, libxml2-utils, python, spudtools, python-gtksourceview2, python-dxdiff |
38 | Recommends: python-psyco |
39 | Suggests: |
40 | Conflicts: python-diamond |
41 | @@ -62,3 +62,12 @@ |
42 | file is generated using a spud-compatible RELAX NG schema and a |
43 | schema-aware editor such as Diamond. This package contains the Python |
44 | bindings for libspud. |
45 | + |
46 | +Package: python-dxdiff |
47 | +Section: python |
48 | +Architecture: any |
49 | +XB-Python-Version: ${python:Versions} |
50 | +Depends: ${python:Depends}, ${misc:Depends} |
51 | +Description: An XML aware diff tool. |
52 | + DXdiff (Diamond Xml diff) is an XML aware diff tool for finding edit scripts |
53 | + between two XML files. |
54 | |
55 | === modified file 'debian/rules' |
56 | --- debian/rules 2011-08-16 22:52:44 +0000 |
57 | +++ debian/rules 2011-08-24 16:38:18 +0000 |
58 | @@ -83,6 +83,9 @@ |
59 | install-pyspud: |
60 | $(MAKE) install-pyspud DESTDIR=$(CURDIR)/debian/python-spud |
61 | |
62 | +install-dxdiff: |
63 | + $(MAKE) install-dxdiff DESTDIR=$(CURDIR)/debian/python-dxdiff |
64 | + |
65 | binary-arch: build-libspud install-libspud install-spudtools install-pyspud |
66 | dh_testdir |
67 | dh_testroot |
68 | @@ -114,7 +117,7 @@ |
69 | dh_md5sums |
70 | dh_builddeb |
71 | |
72 | -binary-indep: build-diamond install-diamond build-libspud install-libspud install-spudtools |
73 | +binary-indep: build-diamond install-diamond build-libspud install-libspud install-spudtools install-dxdiff |
74 | dh_testdir -i |
75 | dh_testroot -i |
76 | DH_PYCENTRAL=include-links dh_pycentral -i |
77 | |
78 | === modified file 'diamond/bin/diamond' |
79 | --- diamond/bin/diamond 2011-07-05 11:42:44 +0000 |
80 | +++ diamond/bin/diamond 2011-08-24 16:38:18 +0000 |
81 | @@ -29,6 +29,8 @@ |
82 | # do this right at the start, so we can find the diamond modules |
83 | diamond_path = os.path.join( os.path.realpath(os.path.dirname(__file__)), os.pardir ) |
84 | sys.path.insert(0, diamond_path) |
85 | +dxdiff_path = os.path.join( os.path.realpath(os.path.dirname(__file__)), os.pardir, os.pardir, "dxdiff" ) |
86 | +sys.path.insert(0, dxdiff_path) |
87 | |
88 | import diamond.debug as debug |
89 | |
90 | @@ -48,6 +50,7 @@ |
91 | "-f Forks at startup\n" + \ |
92 | "-s [SCHEMAFILE] Use the supplied schema file *\n" + \ |
93 | "-t [TRONFILE] Use the supplied schematron file for extended validation\n" + \ |
94 | + "-d [FILE] Diff against the supplied file. (FILE must be specified)\n" + \ |
95 | "-v Verbosity switch - if supplied Diamond prints additional\n" + \ |
96 | " debugging information to standard output and standard error\n" + \ |
97 | "\n" + \ |
98 | @@ -59,7 +62,7 @@ |
99 | def main(): |
100 | |
101 | try: |
102 | - opts, args = getopt.getopt(sys.argv[1:], "hvfs:t:") |
103 | + opts, args = getopt.gnu_getopt(sys.argv[1:], "hvfs:t:d:") |
104 | except: |
105 | Help() |
106 | sys.exit(1) |
107 | @@ -99,8 +102,13 @@ |
108 | except IndexError: |
109 | input_filename = None |
110 | |
111 | + if sys.platform == "win32" or sys.platform == "win64": |
112 | + possible_logofiles = [os.path.join(diamond_path, "gui", "diamond.png")] |
113 | + else: |
114 | + possible_logofiles = [os.path.join(diamond_path, "gui", "diamond.svg"), "/usr/share/diamond/gui/diamond.svg"] |
115 | + |
116 | logofile = None |
117 | - for possible_logofile in [os.path.join(diamond_path, "gui", "diamond.svg"), "/usr/share/diamond/gui/diamond.svg"]: |
118 | + for possible_logofile in possible_logofiles: |
119 | try: |
120 | os.stat(possible_logofile) |
121 | logofile = possible_logofile |
122 | @@ -209,7 +217,20 @@ |
123 | |
124 | i.main_window.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) |
125 | i.open_file(schemafile = input_schemafile, filename = input_filename) |
126 | - i.main_window.window.set_cursor(None) |
127 | + i.main_window.window.set_cursor(None) |
128 | + |
129 | + # see if the user wants to diff against a file |
130 | + input_difffile = None |
131 | + for opt in opts: |
132 | + if opt[0] == "-d": |
133 | + input_difffile = opt[1] |
134 | + |
135 | + if input_difffile: |
136 | + if input_filename: |
137 | + i.on_diff(None, input_difffile) |
138 | + else: |
139 | + debug.dprint("Cannot diff if against nothing.", 0) |
140 | + |
141 | |
142 | gtk.main() |
143 | |
144 | |
145 | === added file 'diamond/diamond/diffview.py' |
146 | --- diamond/diamond/diffview.py 1970-01-01 00:00:00 +0000 |
147 | +++ diamond/diamond/diffview.py 2011-08-24 16:38:18 +0000 |
148 | @@ -0,0 +1,558 @@ |
149 | +#!/usr/bin/env python |
150 | + |
151 | +# This file is part of Diamond. |
152 | +# |
153 | +# Diamond is free software: you can redistribute it and/or modify |
154 | +# it under the terms of the GNU General Public License as published by |
155 | +# the Free Software Foundation, either version 3 of the License, or |
156 | +# (at your option) any later version. |
157 | +# |
158 | +# Diamond is distributed in the hope that it will be useful, |
159 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
160 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
161 | +# GNU General Public License for more details. |
162 | +# |
163 | +# You should have received a copy of the GNU General Public License |
164 | +# along with Diamond. If not, see <http://www.gnu.org/licenses/>. |
165 | + |
166 | +import os |
167 | +import os.path |
168 | +import sys |
169 | +import cStringIO as StringIO |
170 | + |
171 | +import gobject |
172 | +import gtk |
173 | + |
174 | +from lxml import etree |
175 | + |
176 | +import attributewidget |
177 | +import databuttonswidget |
178 | +import datawidget |
179 | +import mixedtree |
180 | + |
181 | +#diff_path = os.path.join( os.path.realpath(os.path.dirname(__file__)), os.pardir, os.pardir, "xmldiff") |
182 | +#sys.path.insert(0, diff_path) |
183 | + |
184 | +import dxdiff.diff as xmldiff |
185 | + |
186 | +class DiffView(gtk.Window): |
187 | + |
188 | + def __init__(self, path, tree): |
189 | + gtk.Window.__init__(self) |
190 | + self.__add_controls() |
191 | + |
192 | + if path and os.path.isfile(path): |
193 | + filename = path |
194 | + else: |
195 | + dialog = gtk.FileChooserDialog(title = "Diff against", |
196 | + action = gtk.FILE_CHOOSER_ACTION_OPEN, |
197 | + buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) |
198 | + if path: |
199 | + dialog.set_current_folder(path) |
200 | + |
201 | + response = dialog.run() |
202 | + if response != gtk.RESPONSE_OK: |
203 | + dialog.destroy() |
204 | + self.destroy() |
205 | + return |
206 | + |
207 | + filename = dialog.get_filename() |
208 | + dialog.destroy() |
209 | + |
210 | + tree1 = etree.parse(filename) |
211 | + tree2 = etree.ElementTree(tree.write_core(None)) |
212 | + |
213 | + editscript = xmldiff.diff(tree1, tree2) |
214 | + self.__update(tree1, editscript) |
215 | + |
216 | + self.show_all() |
217 | + |
218 | + def __add_controls(self): |
219 | + self.set_default_size(800, 600) |
220 | + self.set_title("Diff View") |
221 | + |
222 | + mainvbox = gtk.VBox() |
223 | + |
224 | + menubar = gtk.MenuBar() |
225 | + edititem = gtk.MenuItem("_Edit") |
226 | + menubar.append(edititem) |
227 | + |
228 | + agr = gtk.AccelGroup() |
229 | + self.add_accel_group(agr) |
230 | + |
231 | + self.popup = editmenu = gtk.Menu() |
232 | + edititem.set_submenu(editmenu) |
233 | + copyitem = gtk.MenuItem("Copy") |
234 | + copyitem.connect("activate", self.on_copy) |
235 | + key, mod = gtk.accelerator_parse("<Control>C") |
236 | + copyitem.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) |
237 | + editmenu.append(copyitem) |
238 | + |
239 | + mainvbox.pack_start(menubar, expand = False) |
240 | + |
241 | + hpane = gtk.HPaned() |
242 | + |
243 | + self.treeview = gtk.TreeView() |
244 | + |
245 | + self.treeview.get_selection().set_mode(gtk.SELECTION_SINGLE) |
246 | + self.treeview.get_selection().connect("changed", self.on_select_row) |
247 | + |
248 | + # Node column |
249 | + celltext = gtk.CellRendererText() |
250 | + column = gtk.TreeViewColumn("Node", celltext) |
251 | + column.set_cell_data_func(celltext, self.set_celltext) |
252 | + |
253 | + self.treeview.append_column(column) |
254 | + |
255 | + # 0: The node tag |
256 | + # 1: The attributes dict |
257 | + # 2: The value of the node if any |
258 | + # 3: The old value of the node |
259 | + # 4: "insert", "delete", "update", "" |
260 | + self.treestore = gtk.TreeStore(gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT) |
261 | + self.treeview.set_model(self.treestore) |
262 | + self.treeview.set_enable_search(False) |
263 | + self.treeview.connect("button_press_event", self.on_treeview_button_press) |
264 | + self.treeview.connect("popup_menu", self.on_treeview_popup) |
265 | + hpane.pack1(self.treeview) |
266 | + |
267 | + vpane = gtk.VPaned() |
268 | + frame = gtk.Frame() |
269 | + label = gtk.Label() |
270 | + label.set_markup("<b>Attributes</b>") |
271 | + frame.set_label_widget(label) |
272 | + frame.set_shadow_type(gtk.SHADOW_NONE) |
273 | + |
274 | + self.attribview = gtk.TreeView() |
275 | + |
276 | + celltext = gtk.CellRendererText() |
277 | + keycolumn = gtk.TreeViewColumn("Key", celltext) |
278 | + keycolumn.set_cell_data_func(celltext, self.set_cellkey) |
279 | + |
280 | + self.attribview.append_column(keycolumn) |
281 | + |
282 | + celltext = gtk.CellRendererText() |
283 | + valuecolumn = gtk.TreeViewColumn("Value", celltext) |
284 | + valuecolumn.set_cell_data_func(celltext, self.set_cellvalue) |
285 | + |
286 | + self.attribview.append_column(valuecolumn) |
287 | + |
288 | + frame.add(self.attribview) |
289 | + vpane.pack1(frame) |
290 | + |
291 | + frame = gtk.Frame() |
292 | + label = gtk.Label() |
293 | + label.set_markup("<b>Data</b>") |
294 | + frame.set_label_widget(label) |
295 | + frame.set_shadow_type(gtk.SHADOW_NONE) |
296 | + |
297 | + self.dataview = gtk.TextView() |
298 | + self.dataview.set_cursor_visible(False) |
299 | + self.dataview.set_editable(False) |
300 | + self.__create_tags(self.dataview.get_buffer()) |
301 | + |
302 | + frame.add(self.dataview) |
303 | + vpane.pack2(frame) |
304 | + |
305 | + hpane.pack2(vpane) |
306 | + mainvbox.pack_start(hpane) |
307 | + self.add(mainvbox) |
308 | + |
309 | + def on_treeview_button_press(self, treeview, event): |
310 | + pathinfo = treeview.get_path_at_pos(int(event.x), int(event.y)) |
311 | + if event.button == 3: |
312 | + if pathinfo is not None: |
313 | + treeview.get_selection().select_path(pathinfo[0]) |
314 | + self.show_popup(None, event.button, event.time) |
315 | + return True |
316 | + |
317 | + def popup_location(self, widget, user_data): |
318 | + column = self.treeview.get_column(0) |
319 | + path = self.treeview.get_selection().get_selected()[1] |
320 | + area = self.treeview.get_cell_area(path, column) |
321 | + tx, ty = area.x, area.y |
322 | + x, y = self.treeview.tree_to_widget_coords(tx, ty) |
323 | + return (x, y, True) |
324 | + |
325 | + def on_treeview_popup(self, treeview): |
326 | + self.show_popup(None, self.popup_location, gtk.get_current_event_time()) |
327 | + return |
328 | + |
329 | + def show_popup(self, func, button, time): |
330 | + self.popup.popup( None, None, func, button, time) |
331 | + return |
332 | + |
333 | + def __update(self, tree, editscript): |
334 | + self.__set_treestore(tree.getroot()) |
335 | + self.__parse_editscript(editscript) |
336 | + self.__floodfill(self.treestore.get_iter_root()) |
337 | + |
338 | + def __set_treestore(self, tree, iter = None): |
339 | + |
340 | + attrib = {} |
341 | + for key, value in tree.attrib.iteritems(): |
342 | + # (new, old, edit) |
343 | + attrib[key] = (value, value, None) |
344 | + |
345 | + child_iter = self.treestore.append(iter, [tree.tag, attrib, tree.text, tree.text, None]) |
346 | + for child in tree: |
347 | + self.__set_treestore(child, child_iter) |
348 | + |
349 | + def __parse_editscript(self, editscript): |
350 | + for edit in editscript: |
351 | + iter, key = self.__get_iter(edit["location"]) |
352 | + if key: |
353 | + attribs = self.treestore.get_value(iter, 1) |
354 | + old = attribs[key][1] |
355 | + if edit["type"] == "delete": |
356 | + attribs[key] = (None, old, "delete") |
357 | + elif edit["type"] == "update": |
358 | + attribs[key] = (edit["value"], old, "update") |
359 | + elif edit["type"] == "move": |
360 | + attribs[key] = (None, old, "delete") |
361 | + self.__insert(self.__get_iter(edit["value"])[0], key + " " + old, 0) |
362 | + |
363 | + else: |
364 | + |
365 | + if edit["type"] == "insert": |
366 | + self.__insert(iter, edit["value"], int(edit["index"])) |
367 | + elif edit["type"] == "delete": |
368 | + self.treestore.set(iter, 2, None) |
369 | + self.treestore.set(iter, 4, "delete") |
370 | + elif edit["type"] == "update": |
371 | + self.treestore.set(iter, 2, edit["value"]) |
372 | + elif edit["type"] == "move": |
373 | + self.__move(iter, edit["value"], int(edit["index"])) |
374 | + |
375 | + def __floodfill(self, iter, parentedit = None): |
376 | + """ |
377 | + Floodfill the tree with the correct edit types. |
378 | + If something has changed below you, "subupdate" |
379 | + If your value or attrs has changed "update" |
380 | + If insert, all below insert |
381 | + If delete, all below delete |
382 | + """ |
383 | + attribs, new, old, edit = self.treestore.get(iter, 1, 2, 3, 4) |
384 | + |
385 | + if parentedit == "insert": |
386 | + edit = "insert" |
387 | + elif parentedit == "delete": |
388 | + edit = "delete" |
389 | + |
390 | + if edit == "insert" or edit == "delete": |
391 | + for key, (valuenew, valueold, valueedit) in attribs.iteritems(): |
392 | + attribs[key] = (valuenew, valueold, edit) |
393 | + |
394 | + child = self.treestore.iter_children(iter) |
395 | + while child is not None: |
396 | + self.__floodfill(child, edit) |
397 | + child = self.treestore.iter_next(child) |
398 | + |
399 | + self.treestore.set(iter, 4, edit) |
400 | + else: |
401 | + update = False |
402 | + for key in attribs: |
403 | + # edit value |
404 | + if attribs[key][2] is not None: |
405 | + update = True |
406 | + break |
407 | + if new != old: |
408 | + update = True |
409 | + |
410 | + if update: |
411 | + self.treestore.set(iter, 4, "update") |
412 | + else: |
413 | + child = self.treestore.iter_children(iter) |
414 | + while child is not None: |
415 | + change = self.__floodfill(child, edit) |
416 | + if change is not None: |
417 | + self.treestore.set(iter, 4, "subupdate") |
418 | + child = self.treestore.iter_next(child) |
419 | + |
420 | + return self.treestore.get_value(iter, 4) |
421 | + |
422 | + def __insert(self, iter, value, index): |
423 | + if " " in value: |
424 | + key, value = value.split(" ") |
425 | + attrib = self.treestore.get_value(iter, 1) |
426 | + attrib[key] = (value, None, "insert") |
427 | + else: |
428 | + before = self.__iter_nth_child(iter, index - 1) |
429 | + if before: |
430 | + self.treestore.insert_before(iter, before, [value, {}, None, None, "insert"]) |
431 | + else: |
432 | + self.treestore.append(iter, [value, {}, None, None, "insert"]) |
433 | + |
434 | + def __move(self, iter, value, index): |
435 | + """ |
436 | + Copy the entire subtree at iter to the path at value[index], |
437 | + mark all of iter as deleted, and all of the copy inserted. |
438 | + """ |
439 | + tag, attrib, text = self.treestore.get(iter, 0, 1, 2) |
440 | + self.treestore.set(iter, 2, None) |
441 | + self.treestore.set(iter, 4, "delete") |
442 | + |
443 | + destiter = self.__get_iter(value)[0] |
444 | + |
445 | + before = self.__iter_nth_child(destiter, index - 1) |
446 | + if before: |
447 | + destiter = self.treestore.insert_before(destiter, before, [tag, attrib, text, None, "insert"]) |
448 | + else: |
449 | + destiter = self.treestore.append(destiter, [tag, attrib, text, None, "insert"]) |
450 | + |
451 | + def move(iterfrom, iterto): |
452 | + for childfrom in self.__iter_children(iterfrom): |
453 | + tag, attrib, text = self.treestore.get(childfrom, 0, 1, 2) |
454 | + self.treestore.set(childfrom, 2, None) |
455 | + self.treestore.set(childfrom, 4, "delete") |
456 | + |
457 | + childto = self.treestore.append(iterto, [tag, attrib, text, None, "insert"]) |
458 | + move(childfrom, childto) |
459 | + |
460 | + move(iter, destiter) |
461 | + |
462 | + def __iter_children(self, iter): |
463 | + child = self.treestore.iter_children(iter) |
464 | + |
465 | + while child: |
466 | + active = self.treestore.get_value(child, 4) != "delete" |
467 | + if active: |
468 | + yield child |
469 | + child = self.treestore.iter_next(child) |
470 | + |
471 | + def __iter_nth_child(self, iter, n): |
472 | + |
473 | + for child in self.__iter_children(iter): |
474 | + if n == 0: |
475 | + return child |
476 | + else: |
477 | + n -= 1 |
478 | + return None |
479 | + |
480 | + def __get_iter(self, path, iter = None): |
481 | + """ |
482 | + Convert the given XML path to an iter into the treestore. |
483 | + """ |
484 | + |
485 | + if iter is None: |
486 | + iter = self.treestore.get_iter_first() |
487 | + |
488 | + tag, edit = self.treestore.get(iter, 0, 4) |
489 | + if edit == "delete": |
490 | + return None # don't search deleted paths |
491 | + |
492 | + parentiter = self.treestore.iter_parent(iter) |
493 | + if parentiter: |
494 | + siblings = [] |
495 | + for siblingiter in self.__iter_children(parentiter): |
496 | + siblingtag = self.treestore.get_value(siblingiter, 0) |
497 | + if siblingtag == tag: |
498 | + siblings.append(self.treestore.get_path(siblingiter)) |
499 | + |
500 | + if len(siblings) != 1: |
501 | + index = "[" + str(siblings.index(self.treestore.get_path(iter)) + 1) + "]" |
502 | + else: |
503 | + index = "" |
504 | + |
505 | + tag = "/" + tag + index |
506 | + else: |
507 | + tag = "/" + tag |
508 | + |
509 | + index = path.find("/", 1) |
510 | + if index == -1: |
511 | + index = len(path) |
512 | + |
513 | + root = path[:index] |
514 | + path = path[index:] |
515 | + |
516 | + #check we match root |
517 | + if root != tag: |
518 | + return None |
519 | + |
520 | + if path: |
521 | + # check for text() |
522 | + if path == "/text()": |
523 | + return (iter, None) |
524 | + |
525 | + # check attributes |
526 | + if path.startswith("/@"): |
527 | + attrib = self.treestore.get_value(iter, 1) |
528 | + for key in attrib: |
529 | + if path == "/@" + key: |
530 | + return (iter, key) |
531 | + return None |
532 | + |
533 | + # check children |
534 | + for iter in self.__iter_children(iter): |
535 | + edit = self.treestore.get_value(iter, 4) |
536 | + if edit != "delete": |
537 | + result = self.__get_iter(path, iter) |
538 | + if result: |
539 | + return result |
540 | + |
541 | + return None |
542 | + else: |
543 | + # must be us |
544 | + return (iter, None) |
545 | + |
546 | + def on_select_row(self, selection): |
547 | + """ |
548 | + Called when a row is selected. |
549 | + """ |
550 | + (model, row) = selection.get_selected() |
551 | + if row is None: |
552 | + return |
553 | + |
554 | + attrib, new, old, edit = model.get(row, 1, 2, 3, 4) |
555 | + |
556 | + databuffer = self.dataview.get_buffer() |
557 | + tag = databuffer.get_tag_table().lookup("tag") |
558 | + if new or old: |
559 | + self.__set_textdiff(self.dataview.get_buffer(), old, new) |
560 | + tag.set_property("background-set", False) |
561 | + tag.set_property("foreground", "black") |
562 | + else: |
563 | + databuffer.set_text("No data") |
564 | + self.__set_cell_property(tag, None) |
565 | + tag.set_property("foreground", "grey") |
566 | + |
567 | + bounds = databuffer.get_bounds() |
568 | + databuffer.apply_tag(tag, bounds[0], bounds[1]) |
569 | + |
570 | + # 0: Key |
571 | + # 1: Value |
572 | + # 2: Old value |
573 | + # 3: "insert", "delete", "update", "" |
574 | + |
575 | + attribstore = gtk.TreeStore(gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT) |
576 | + |
577 | + for key, (new, old, diff) in attrib.iteritems(): |
578 | + attribstore.append(None, [key, new, old, diff]) |
579 | + |
580 | + self.attribview.set_model(attribstore) |
581 | + |
582 | + def __set_textdiff(self, databuffer, old, new): |
583 | + text1 = old.splitlines() if old else [] |
584 | + text2 = new.splitlines() if new else [] |
585 | + |
586 | + from difflib import Differ |
587 | + differ = Differ() |
588 | + result = differ.compare(text1, text2) |
589 | + result = [line + "\n" for line in result if not line.startswith("? ")] |
590 | + |
591 | + databuffer.set_text("") |
592 | + for line in result: |
593 | + iter = databuffer.get_end_iter() |
594 | + if line.startswith(" "): |
595 | + databuffer.insert(iter, line) |
596 | + elif line.startswith("+ "): |
597 | + databuffer.insert_with_tags_by_name(iter, line, "add") |
598 | + elif line.startswith("- "): |
599 | + databuffer.insert_with_tags_by_name(iter, line, "rem") |
600 | + |
601 | + |
602 | + def __create_tags(self, databuffer): |
603 | + databuffer.create_tag("tag") |
604 | + add = databuffer.create_tag("add") |
605 | + rem = databuffer.create_tag("rem") |
606 | + |
607 | + add.set_property("background", "lightgreen") |
608 | + rem.set_property("background", "indianred") |
609 | + |
610 | + def __set_cell_property(self, cell, edit): |
611 | + if edit is None: |
612 | + cell.set_property("foreground", "black") |
613 | + elif edit == "insert": |
614 | + cell.set_property("foreground", "green") |
615 | + elif edit == "delete": |
616 | + cell.set_property("foreground", "red") |
617 | + elif edit == "update": |
618 | + cell.set_property("foreground", "blue") |
619 | + elif edit == "subupdate": |
620 | + cell.set_property("foreground", "cornflowerblue") |
621 | + |
622 | + def set_celltext(self, column, cell, model, iter): |
623 | + |
624 | + tag, text, edit = model.get(iter, 0, 2, 4) |
625 | + |
626 | + cell.set_property("text", tag) |
627 | + self.__set_cell_property(cell, edit) |
628 | + |
629 | + def set_cellkey(self, column, cell, model, iter): |
630 | + |
631 | + key, edit = model.get(iter, 0, 3) |
632 | + cell.set_property("text", key) |
633 | + self.__set_cell_property(cell, edit) |
634 | + |
635 | + def set_cellvalue(self, column, cell, model, iter): |
636 | + |
637 | + new, old, edit = model.get(iter, 1, 2, 3) |
638 | + if edit == "delete": |
639 | + cell.set_property("text", old) |
640 | + else: |
641 | + cell.set_property("text", new) |
642 | + self.__set_cell_property(cell, edit) |
643 | + |
644 | + def _get_focus_widget(self, parent): |
645 | + """ |
646 | + Gets the widget that is a child of parent with the focus. |
647 | + """ |
648 | + focus = parent.get_focus_child() |
649 | + if focus is None or (focus.flags() & gtk.HAS_FOCUS): |
650 | + return focus |
651 | + else: |
652 | + return self._get_focus_widget(focus) |
653 | + |
654 | + def _handle_clipboard(self, widget, signal): |
655 | + """ |
656 | + This finds the currently focused widget. |
657 | + If no widget is focused or the focused widget doesn't support |
658 | + the given clipboard operation use the treeview (False), otherwise |
659 | + signal the widget to handel the clipboard operation (True). |
660 | + """ |
661 | + widget = self._get_focus_widget(self) |
662 | + |
663 | + if widget is None or widget is self.treeview: |
664 | + return False |
665 | + |
666 | + if gobject.signal_lookup(signal + "-clipboard", widget): |
667 | + widget.emit(signal + "-clipboard") |
668 | + return True |
669 | + else: |
670 | + return False |
671 | + |
672 | + def __get_treestore(self, iter): |
673 | + |
674 | + tag, attrib, text = self.treestore.get(iter, 0, 1, 2) |
675 | + |
676 | + tree = etree.Element(tag) |
677 | + |
678 | + for key, (newvalue, oldvalue, edit) in attrib.iteritems(): |
679 | + tree.attrib[key] = newvalue |
680 | + |
681 | + child_iter = self.treestore.iter_children(iter) |
682 | + while child_iter: |
683 | + child = self.__get_treestore(child_iter) |
684 | + tree.append(child) |
685 | + child_iter = self.treestore.iter_next(child_iter) |
686 | + |
687 | + return tree |
688 | + |
689 | + def on_copy(self, widget=None): |
690 | + if self._handle_clipboard(widget, "copy"): |
691 | + return |
692 | + |
693 | + (model, row) = self.treeview.get_selection().get_selected() |
694 | + if row is None: |
695 | + return |
696 | + |
697 | + tree = etree.ElementTree(self.__get_treestore(row)) |
698 | + |
699 | + ios = StringIO.StringIO() |
700 | + tree.write(ios, pretty_print = True, xml_declaration = False, encoding = "utf-8") |
701 | + |
702 | + clipboard = gtk.clipboard_get() |
703 | + clipboard.set_text(ios.getvalue()) |
704 | + clipboard.store() |
705 | + |
706 | + ios.close() |
707 | |
708 | === modified file 'diamond/diamond/interface.py' |
709 | --- diamond/diamond/interface.py 2011-08-16 15:22:31 +0000 |
710 | +++ diamond/diamond/interface.py 2011-08-24 16:38:18 +0000 |
711 | @@ -18,6 +18,7 @@ |
712 | import os |
713 | import os.path |
714 | import re |
715 | +import time |
716 | import sys |
717 | import tempfile |
718 | import cStringIO as StringIO |
719 | @@ -47,6 +48,7 @@ |
720 | import descriptionwidget |
721 | import databuttonswidget |
722 | import datawidget |
723 | +import diffview |
724 | import sliceview |
725 | |
726 | from lxml import etree |
727 | @@ -140,6 +142,8 @@ |
728 | "on_copy": self.on_copy, |
729 | "on_paste": self.on_paste, |
730 | "on_slice": self.on_slice, |
731 | + "on_diff": self.on_diff, |
732 | + "on_diffsave": self.on_diffsave, |
733 | "on_group": self.on_group, |
734 | "on_ungroup": self.on_ungroup} |
735 | |
736 | @@ -601,15 +605,16 @@ |
737 | "You should have received a copy of the GNU General Public License\n"+ |
738 | "along with Diamond. If not, see http://www.gnu.org/licenses/.") |
739 | |
740 | - logo = gtk.gdk.pixbuf_new_from_file(self.logofile) |
741 | - |
742 | + if self.logofile is not None: |
743 | + logo = gtk.gdk.pixbuf_new_from_file(self.logofile) |
744 | + about.set_logo(logo) |
745 | + |
746 | try: |
747 | image = about.get_children()[0].get_children()[0].get_children()[0] |
748 | image.set_tooltip_text("Diamond: it's clearer than GEM") |
749 | except: |
750 | pass |
751 | - |
752 | - about.set_logo(logo) |
753 | + |
754 | about.show() |
755 | |
756 | return |
757 | @@ -730,13 +735,31 @@ |
758 | msg += "Warning: added xml attributes:\n" |
759 | for ele in added_attrs: |
760 | msg += ele + "\n" |
761 | - |
762 | + |
763 | dialogs.long_message(self.main_window, msg) |
764 | - |
765 | - self.set_saved(False) |
766 | + |
767 | + self.set_saved(False) |
768 | |
769 | return |
770 | |
771 | + def __diff(self, path): |
772 | + self.statusbar.set_statusbar("Calculating diff... (this may take a while)") |
773 | + start = time.clock() |
774 | + diffview.DiffView(path, self.tree) |
775 | + seconds = time.clock() - start |
776 | + self.statusbar.set_statusbar("Diff calculated (took " + str(seconds) + " seconds)") |
777 | + |
778 | + def on_diff(self, widget = None, path = None): |
779 | + if path is None: |
780 | + path = os.path.dirname(self.filename) if self.filename else None |
781 | + self.__diff(path) |
782 | + |
783 | + def on_diffsave(self, widget = None): |
784 | + if self.filename: |
785 | + self.__diff(self.filename) |
786 | + else: |
787 | + dialogs.error(self.main_window, "No save to diff against.") |
788 | + |
789 | def on_slice(self, widget = None): |
790 | if not self.selected_node.is_sliceable(): |
791 | self.statusbar.set_statusbar("Cannot slice on this element.") |
792 | |
793 | === modified file 'diamond/diamond/triangle_reader.py' (properties changed: +x to -x) |
794 | === modified file 'diamond/gui/gui.glade' |
795 | --- diamond/gui/gui.glade 2011-07-26 11:06:34 +0000 |
796 | +++ diamond/gui/gui.glade 2011-08-24 16:38:18 +0000 |
797 | @@ -131,7 +131,7 @@ |
798 | <signal name="activate" handler="on_paste"/> |
799 | <accelerator key="V" signal="activate" modifiers="GDK_CONTROL_MASK"/> |
800 | </widget> |
801 | - </child> |
802 | + </child> |
803 | <child> |
804 | <widget class="GtkMenuItem" id="menuitemGroup"> |
805 | <property name="visible">True</property> |
806 | @@ -221,6 +221,20 @@ |
807 | <widget class="GtkMenu" id="menu4"> |
808 | <property name="visible">True</property> |
809 | <child> |
810 | + <widget class="GtkMenuItem" id="menuitemDiff"> |
811 | + <property name="visible">True</property> |
812 | + <property name="label" translatable="yes">Diff</property> |
813 | + <signal name="activate" handler="on_diff"/> |
814 | + </widget> |
815 | + </child> |
816 | + <child> |
817 | + <widget class="GtkMenuItem" id="menuitemDiffSave"> |
818 | + <property name="visible">True</property> |
819 | + <property name="label" translatable="yes">Diff against last save</property> |
820 | + <signal name="activate" handler="on_diffsave"/> |
821 | + </widget> |
822 | + </child> |
823 | + <child> |
824 | <widget class="GtkCheckMenuItem" id="display_properties"> |
825 | <property name="visible">True</property> |
826 | <property name="tooltip" translatable="yes">Display the option properties on the right hand side of the main window.</property> |
827 | |
828 | === modified file 'diamond/tests/plist/plist.py' (properties changed: +x to -x) |
829 | === modified file 'diamond/tests/read_xml/find_hidden_xmldata.py' (properties changed: +x to -x) |
830 | === modified file 'diamond/tests/schema/test_simple_schema.py' (properties changed: +x to -x) |
831 | === added directory 'dxdiff' |
832 | === added file 'dxdiff/COPYING' |
833 | --- dxdiff/COPYING 1970-01-01 00:00:00 +0000 |
834 | +++ dxdiff/COPYING 2011-08-24 16:38:18 +0000 |
835 | @@ -0,0 +1,15 @@ |
836 | +dxdiff is copyright (C) 2011 Imperial College London and others. |
837 | +For a full list of contributors see the AUTHORS file. |
838 | + |
839 | +dxdiff is free software: you can redistribute it and/or modify |
840 | +it under the terms of the GNU General Public License as published by |
841 | +the Free Software Foundation, either version 3 of the License, or |
842 | +(at your option) any later version. |
843 | + |
844 | +This program is distributed in the hope that it will be useful, |
845 | +but WITHOUT ANY WARRANTY; without even the implied warranty of |
846 | +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
847 | +GNU General Public License for more details. |
848 | + |
849 | +The complete text of the GNU General Public License can be found |
850 | +in the file `GPL-3'. |
851 | |
852 | === added file 'dxdiff/GPL-3' |
853 | --- dxdiff/GPL-3 1970-01-01 00:00:00 +0000 |
854 | +++ dxdiff/GPL-3 2011-08-24 16:38:18 +0000 |
855 | @@ -0,0 +1,676 @@ |
856 | + |
857 | + GNU GENERAL PUBLIC LICENSE |
858 | + Version 3, 29 June 2007 |
859 | + |
860 | + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> |
861 | + Everyone is permitted to copy and distribute verbatim copies |
862 | + of this license document, but changing it is not allowed. |
863 | + |
864 | + Preamble |
865 | + |
866 | + The GNU General Public License is a free, copyleft license for |
867 | +software and other kinds of works. |
868 | + |
869 | + The licenses for most software and other practical works are designed |
870 | +to take away your freedom to share and change the works. By contrast, |
871 | +the GNU General Public License is intended to guarantee your freedom to |
872 | +share and change all versions of a program--to make sure it remains free |
873 | +software for all its users. We, the Free Software Foundation, use the |
874 | +GNU General Public License for most of our software; it applies also to |
875 | +any other work released this way by its authors. You can apply it to |
876 | +your programs, too. |
877 | + |
878 | + When we speak of free software, we are referring to freedom, not |
879 | +price. Our General Public Licenses are designed to make sure that you |
880 | +have the freedom to distribute copies of free software (and charge for |
881 | +them if you wish), that you receive source code or can get it if you |
882 | +want it, that you can change the software or use pieces of it in new |
883 | +free programs, and that you know you can do these things. |
884 | + |
885 | + To protect your rights, we need to prevent others from denying you |
886 | +these rights or asking you to surrender the rights. Therefore, you have |
887 | +certain responsibilities if you distribute copies of the software, or if |
888 | +you modify it: responsibilities to respect the freedom of others. |
889 | + |
890 | + For example, if you distribute copies of such a program, whether |
891 | +gratis or for a fee, you must pass on to the recipients the same |
892 | +freedoms that you received. You must make sure that they, too, receive |
893 | +or can get the source code. And you must show them these terms so they |
894 | +know their rights. |
895 | + |
896 | + Developers that use the GNU GPL protect your rights with two steps: |
897 | +(1) assert copyright on the software, and (2) offer you this License |
898 | +giving you legal permission to copy, distribute and/or modify it. |
899 | + |
900 | + For the developers' and authors' protection, the GPL clearly explains |
901 | +that there is no warranty for this free software. For both users' and |
902 | +authors' sake, the GPL requires that modified versions be marked as |
903 | +changed, so that their problems will not be attributed erroneously to |
904 | +authors of previous versions. |
905 | + |
906 | + Some devices are designed to deny users access to install or run |
907 | +modified versions of the software inside them, although the manufacturer |
908 | +can do so. This is fundamentally incompatible with the aim of |
909 | +protecting users' freedom to change the software. The systematic |
910 | +pattern of such abuse occurs in the area of products for individuals to |
911 | +use, which is precisely where it is most unacceptable. Therefore, we |
912 | +have designed this version of the GPL to prohibit the practice for those |
913 | +products. If such problems arise substantially in other domains, we |
914 | +stand ready to extend this provision to those domains in future versions |
915 | +of the GPL, as needed to protect the freedom of users. |
916 | + |
917 | + Finally, every program is threatened constantly by software patents. |
918 | +States should not allow patents to restrict development and use of |
919 | +software on general-purpose computers, but in those that do, we wish to |
920 | +avoid the special danger that patents applied to a free program could |
921 | +make it effectively proprietary. To prevent this, the GPL assures that |
922 | +patents cannot be used to render the program non-free. |
923 | + |
924 | + The precise terms and conditions for copying, distribution and |
925 | +modification follow. |
926 | + |
927 | + TERMS AND CONDITIONS |
928 | + |
929 | + 0. Definitions. |
930 | + |
931 | + "This License" refers to version 3 of the GNU General Public License. |
932 | + |
933 | + "Copyright" also means copyright-like laws that apply to other kinds of |
934 | +works, such as semiconductor masks. |
935 | + |
936 | + "The Program" refers to any copyrightable work licensed under this |
937 | +License. Each licensee is addressed as "you". "Licensees" and |
938 | +"recipients" may be individuals or organizations. |
939 | + |
940 | + To "modify" a work means to copy from or adapt all or part of the work |
941 | +in a fashion requiring copyright permission, other than the making of an |
942 | +exact copy. The resulting work is called a "modified version" of the |
943 | +earlier work or a work "based on" the earlier work. |
944 | + |
945 | + A "covered work" means either the unmodified Program or a work based |
946 | +on the Program. |
947 | + |
948 | + To "propagate" a work means to do anything with it that, without |
949 | +permission, would make you directly or secondarily liable for |
950 | +infringement under applicable copyright law, except executing it on a |
951 | +computer or modifying a private copy. Propagation includes copying, |
952 | +distribution (with or without modification), making available to the |
953 | +public, and in some countries other activities as well. |
954 | + |
955 | + To "convey" a work means any kind of propagation that enables other |
956 | +parties to make or receive copies. Mere interaction with a user through |
957 | +a computer network, with no transfer of a copy, is not conveying. |
958 | + |
959 | + An interactive user interface displays "Appropriate Legal Notices" |
960 | +to the extent that it includes a convenient and prominently visible |
961 | +feature that (1) displays an appropriate copyright notice, and (2) |
962 | +tells the user that there is no warranty for the work (except to the |
963 | +extent that warranties are provided), that licensees may convey the |
964 | +work under this License, and how to view a copy of this License. If |
965 | +the interface presents a list of user commands or options, such as a |
966 | +menu, a prominent item in the list meets this criterion. |
967 | + |
968 | + 1. Source Code. |
969 | + |
970 | + The "source code" for a work means the preferred form of the work |
971 | +for making modifications to it. "Object code" means any non-source |
972 | +form of a work. |
973 | + |
974 | + A "Standard Interface" means an interface that either is an official |
975 | +standard defined by a recognized standards body, or, in the case of |
976 | +interfaces specified for a particular programming language, one that |
977 | +is widely used among developers working in that language. |
978 | + |
979 | + The "System Libraries" of an executable work include anything, other |
980 | +than the work as a whole, that (a) is included in the normal form of |
981 | +packaging a Major Component, but which is not part of that Major |
982 | +Component, and (b) serves only to enable use of the work with that |
983 | +Major Component, or to implement a Standard Interface for which an |
984 | +implementation is available to the public in source code form. A |
985 | +"Major Component", in this context, means a major essential component |
986 | +(kernel, window system, and so on) of the specific operating system |
987 | +(if any) on which the executable work runs, or a compiler used to |
988 | +produce the work, or an object code interpreter used to run it. |
989 | + |
990 | + The "Corresponding Source" for a work in object code form means all |
991 | +the source code needed to generate, install, and (for an executable |
992 | +work) run the object code and to modify the work, including scripts to |
993 | +control those activities. However, it does not include the work's |
994 | +System Libraries, or general-purpose tools or generally available free |
995 | +programs which are used unmodified in performing those activities but |
996 | +which are not part of the work. For example, Corresponding Source |
997 | +includes interface definition files associated with source files for |
998 | +the work, and the source code for shared libraries and dynamically |
999 | +linked subprograms that the work is specifically designed to require, |
1000 | +such as by intimate data communication or control flow between those |
1001 | +subprograms and other parts of the work. |
1002 | + |
1003 | + The Corresponding Source need not include anything that users |
1004 | +can regenerate automatically from other parts of the Corresponding |
1005 | +Source. |
1006 | + |
1007 | + The Corresponding Source for a work in source code form is that |
1008 | +same work. |
1009 | + |
1010 | + 2. Basic Permissions. |
1011 | + |
1012 | + All rights granted under this License are granted for the term of |
1013 | +copyright on the Program, and are irrevocable provided the stated |
1014 | +conditions are met. This License explicitly affirms your unlimited |
1015 | +permission to run the unmodified Program. The output from running a |
1016 | +covered work is covered by this License only if the output, given its |
1017 | +content, constitutes a covered work. This License acknowledges your |
1018 | +rights of fair use or other equivalent, as provided by copyright law. |
1019 | + |
1020 | + You may make, run and propagate covered works that you do not |
1021 | +convey, without conditions so long as your license otherwise remains |
1022 | +in force. You may convey covered works to others for the sole purpose |
1023 | +of having them make modifications exclusively for you, or provide you |
1024 | +with facilities for running those works, provided that you comply with |
1025 | +the terms of this License in conveying all material for which you do |
1026 | +not control copyright. Those thus making or running the covered works |
1027 | +for you must do so exclusively on your behalf, under your direction |
1028 | +and control, on terms that prohibit them from making any copies of |
1029 | +your copyrighted material outside their relationship with you. |
1030 | + |
1031 | + Conveying under any other circumstances is permitted solely under |
1032 | +the conditions stated below. Sublicensing is not allowed; section 10 |
1033 | +makes it unnecessary. |
1034 | + |
1035 | + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. |
1036 | + |
1037 | + No covered work shall be deemed part of an effective technological |
1038 | +measure under any applicable law fulfilling obligations under article |
1039 | +11 of the WIPO copyright treaty adopted on 20 December 1996, or |
1040 | +similar laws prohibiting or restricting circumvention of such |
1041 | +measures. |
1042 | + |
1043 | + When you convey a covered work, you waive any legal power to forbid |
1044 | +circumvention of technological measures to the extent such circumvention |
1045 | +is effected by exercising rights under this License with respect to |
1046 | +the covered work, and you disclaim any intention to limit operation or |
1047 | +modification of the work as a means of enforcing, against the work's |
1048 | +users, your or third parties' legal rights to forbid circumvention of |
1049 | +technological measures. |
1050 | + |
1051 | + 4. Conveying Verbatim Copies. |
1052 | + |
1053 | + You may convey verbatim copies of the Program's source code as you |
1054 | +receive it, in any medium, provided that you conspicuously and |
1055 | +appropriately publish on each copy an appropriate copyright notice; |
1056 | +keep intact all notices stating that this License and any |
1057 | +non-permissive terms added in accord with section 7 apply to the code; |
1058 | +keep intact all notices of the absence of any warranty; and give all |
1059 | +recipients a copy of this License along with the Program. |
1060 | + |
1061 | + You may charge any price or no price for each copy that you convey, |
1062 | +and you may offer support or warranty protection for a fee. |
1063 | + |
1064 | + 5. Conveying Modified Source Versions. |
1065 | + |
1066 | + You may convey a work based on the Program, or the modifications to |
1067 | +produce it from the Program, in the form of source code under the |
1068 | +terms of section 4, provided that you also meet all of these conditions: |
1069 | + |
1070 | + a) The work must carry prominent notices stating that you modified |
1071 | + it, and giving a relevant date. |
1072 | + |
1073 | + b) The work must carry prominent notices stating that it is |
1074 | + released under this License and any conditions added under section |
1075 | + 7. This requirement modifies the requirement in section 4 to |
1076 | + "keep intact all notices". |
1077 | + |
1078 | + c) You must license the entire work, as a whole, under this |
1079 | + License to anyone who comes into possession of a copy. This |
1080 | + License will therefore apply, along with any applicable section 7 |
1081 | + additional terms, to the whole of the work, and all its parts, |
1082 | + regardless of how they are packaged. This License gives no |
1083 | + permission to license the work in any other way, but it does not |
1084 | + invalidate such permission if you have separately received it. |
1085 | + |
1086 | + d) If the work has interactive user interfaces, each must display |
1087 | + Appropriate Legal Notices; however, if the Program has interactive |
1088 | + interfaces that do not display Appropriate Legal Notices, your |
1089 | + work need not make them do so. |
1090 | + |
1091 | + A compilation of a covered work with other separate and independent |
1092 | +works, which are not by their nature extensions of the covered work, |
1093 | +and which are not combined with it such as to form a larger program, |
1094 | +in or on a volume of a storage or distribution medium, is called an |
1095 | +"aggregate" if the compilation and its resulting copyright are not |
1096 | +used to limit the access or legal rights of the compilation's users |
1097 | +beyond what the individual works permit. Inclusion of a covered work |
1098 | +in an aggregate does not cause this License to apply to the other |
1099 | +parts of the aggregate. |
1100 | + |
1101 | + 6. Conveying Non-Source Forms. |
1102 | + |
1103 | + You may convey a covered work in object code form under the terms |
1104 | +of sections 4 and 5, provided that you also convey the |
1105 | +machine-readable Corresponding Source under the terms of this License, |
1106 | +in one of these ways: |
1107 | + |
1108 | + a) Convey the object code in, or embodied in, a physical product |
1109 | + (including a physical distribution medium), accompanied by the |
1110 | + Corresponding Source fixed on a durable physical medium |
1111 | + customarily used for software interchange. |
1112 | + |
1113 | + b) Convey the object code in, or embodied in, a physical product |
1114 | + (including a physical distribution medium), accompanied by a |
1115 | + written offer, valid for at least three years and valid for as |
1116 | + long as you offer spare parts or customer support for that product |
1117 | + model, to give anyone who possesses the object code either (1) a |
1118 | + copy of the Corresponding Source for all the software in the |
1119 | + product that is covered by this License, on a durable physical |
1120 | + medium customarily used for software interchange, for a price no |
1121 | + more than your reasonable cost of physically performing this |
1122 | + conveying of source, or (2) access to copy the |
1123 | + Corresponding Source from a network server at no charge. |
1124 | + |
1125 | + c) Convey individual copies of the object code with a copy of the |
1126 | + written offer to provide the Corresponding Source. This |
1127 | + alternative is allowed only occasionally and noncommercially, and |
1128 | + only if you received the object code with such an offer, in accord |
1129 | + with subsection 6b. |
1130 | + |
1131 | + d) Convey the object code by offering access from a designated |
1132 | + place (gratis or for a charge), and offer equivalent access to the |
1133 | + Corresponding Source in the same way through the same place at no |
1134 | + further charge. You need not require recipients to copy the |
1135 | + Corresponding Source along with the object code. If the place to |
1136 | + copy the object code is a network server, the Corresponding Source |
1137 | + may be on a different server (operated by you or a third party) |
1138 | + that supports equivalent copying facilities, provided you maintain |
1139 | + clear directions next to the object code saying where to find the |
1140 | + Corresponding Source. Regardless of what server hosts the |
1141 | + Corresponding Source, you remain obligated to ensure that it is |
1142 | + available for as long as needed to satisfy these requirements. |
1143 | + |
1144 | + e) Convey the object code using peer-to-peer transmission, provided |
1145 | + you inform other peers where the object code and Corresponding |
1146 | + Source of the work are being offered to the general public at no |
1147 | + charge under subsection 6d. |
1148 | + |
1149 | + A separable portion of the object code, whose source code is excluded |
1150 | +from the Corresponding Source as a System Library, need not be |
1151 | +included in conveying the object code work. |
1152 | + |
1153 | + A "User Product" is either (1) a "consumer product", which means any |
1154 | +tangible personal property which is normally used for personal, family, |
1155 | +or household purposes, or (2) anything designed or sold for incorporation |
1156 | +into a dwelling. In determining whether a product is a consumer product, |
1157 | +doubtful cases shall be resolved in favor of coverage. For a particular |
1158 | +product received by a particular user, "normally used" refers to a |
1159 | +typical or common use of that class of product, regardless of the status |
1160 | +of the particular user or of the way in which the particular user |
1161 | +actually uses, or expects or is expected to use, the product. A product |
1162 | +is a consumer product regardless of whether the product has substantial |
1163 | +commercial, industrial or non-consumer uses, unless such uses represent |
1164 | +the only significant mode of use of the product. |
1165 | + |
1166 | + "Installation Information" for a User Product means any methods, |
1167 | +procedures, authorization keys, or other information required to install |
1168 | +and execute modified versions of a covered work in that User Product from |
1169 | +a modified version of its Corresponding Source. The information must |
1170 | +suffice to ensure that the continued functioning of the modified object |
1171 | +code is in no case prevented or interfered with solely because |
1172 | +modification has been made. |
1173 | + |
1174 | + If you convey an object code work under this section in, or with, or |
1175 | +specifically for use in, a User Product, and the conveying occurs as |
1176 | +part of a transaction in which the right of possession and use of the |
1177 | +User Product is transferred to the recipient in perpetuity or for a |
1178 | +fixed term (regardless of how the transaction is characterized), the |
1179 | +Corresponding Source conveyed under this section must be accompanied |
1180 | +by the Installation Information. But this requirement does not apply |
1181 | +if neither you nor any third party retains the ability to install |
1182 | +modified object code on the User Product (for example, the work has |
1183 | +been installed in ROM). |
1184 | + |
1185 | + The requirement to provide Installation Information does not include a |
1186 | +requirement to continue to provide support service, warranty, or updates |
1187 | +for a work that has been modified or installed by the recipient, or for |
1188 | +the User Product in which it has been modified or installed. Access to a |
1189 | +network may be denied when the modification itself materially and |
1190 | +adversely affects the operation of the network or violates the rules and |
1191 | +protocols for communication across the network. |
1192 | + |
1193 | + Corresponding Source conveyed, and Installation Information provided, |
1194 | +in accord with this section must be in a format that is publicly |
1195 | +documented (and with an implementation available to the public in |
1196 | +source code form), and must require no special password or key for |
1197 | +unpacking, reading or copying. |
1198 | + |
1199 | + 7. Additional Terms. |
1200 | + |
1201 | + "Additional permissions" are terms that supplement the terms of this |
1202 | +License by making exceptions from one or more of its conditions. |
1203 | +Additional permissions that are applicable to the entire Program shall |
1204 | +be treated as though they were included in this License, to the extent |
1205 | +that they are valid under applicable law. If additional permissions |
1206 | +apply only to part of the Program, that part may be used separately |
1207 | +under those permissions, but the entire Program remains governed by |
1208 | +this License without regard to the additional permissions. |
1209 | + |
1210 | + When you convey a copy of a covered work, you may at your option |
1211 | +remove any additional permissions from that copy, or from any part of |
1212 | +it. (Additional permissions may be written to require their own |
1213 | +removal in certain cases when you modify the work.) You may place |
1214 | +additional permissions on material, added by you to a covered work, |
1215 | +for which you have or can give appropriate copyright permission. |
1216 | + |
1217 | + Notwithstanding any other provision of this License, for material you |
1218 | +add to a covered work, you may (if authorized by the copyright holders of |
1219 | +that material) supplement the terms of this License with terms: |
1220 | + |
1221 | + a) Disclaiming warranty or limiting liability differently from the |
1222 | + terms of sections 15 and 16 of this License; or |
1223 | + |
1224 | + b) Requiring preservation of specified reasonable legal notices or |
1225 | + author attributions in that material or in the Appropriate Legal |
1226 | + Notices displayed by works containing it; or |
1227 | + |
1228 | + c) Prohibiting misrepresentation of the origin of that material, or |
1229 | + requiring that modified versions of such material be marked in |
1230 | + reasonable ways as different from the original version; or |
1231 | + |
1232 | + d) Limiting the use for publicity purposes of names of licensors or |
1233 | + authors of the material; or |
1234 | + |
1235 | + e) Declining to grant rights under trademark law for use of some |
1236 | + trade names, trademarks, or service marks; or |
1237 | + |
1238 | + f) Requiring indemnification of licensors and authors of that |
1239 | + material by anyone who conveys the material (or modified versions of |
1240 | + it) with contractual assumptions of liability to the recipient, for |
1241 | + any liability that these contractual assumptions directly impose on |
1242 | + those licensors and authors. |
1243 | + |
1244 | + All other non-permissive additional terms are considered "further |
1245 | +restrictions" within the meaning of section 10. If the Program as you |
1246 | +received it, or any part of it, contains a notice stating that it is |
1247 | +governed by this License along with a term that is a further |
1248 | +restriction, you may remove that term. If a license document contains |
1249 | +a further restriction but permits relicensing or conveying under this |
1250 | +License, you may add to a covered work material governed by the terms |
1251 | +of that license document, provided that the further restriction does |
1252 | +not survive such relicensing or conveying. |
1253 | + |
1254 | + If you add terms to a covered work in accord with this section, you |
1255 | +must place, in the relevant source files, a statement of the |
1256 | +additional terms that apply to those files, or a notice indicating |
1257 | +where to find the applicable terms. |
1258 | + |
1259 | + Additional terms, permissive or non-permissive, may be stated in the |
1260 | +form of a separately written license, or stated as exceptions; |
1261 | +the above requirements apply either way. |
1262 | + |
1263 | + 8. Termination. |
1264 | + |
1265 | + You may not propagate or modify a covered work except as expressly |
1266 | +provided under this License. Any attempt otherwise to propagate or |
1267 | +modify it is void, and will automatically terminate your rights under |
1268 | +this License (including any patent licenses granted under the third |
1269 | +paragraph of section 11). |
1270 | + |
1271 | + However, if you cease all violation of this License, then your |
1272 | +license from a particular copyright holder is reinstated (a) |
1273 | +provisionally, unless and until the copyright holder explicitly and |
1274 | +finally terminates your license, and (b) permanently, if the copyright |
1275 | +holder fails to notify you of the violation by some reasonable means |
1276 | +prior to 60 days after the cessation. |
1277 | + |
1278 | + Moreover, your license from a particular copyright holder is |
1279 | +reinstated permanently if the copyright holder notifies you of the |
1280 | +violation by some reasonable means, this is the first time you have |
1281 | +received notice of violation of this License (for any work) from that |
1282 | +copyright holder, and you cure the violation prior to 30 days after |
1283 | +your receipt of the notice. |
1284 | + |
1285 | + Termination of your rights under this section does not terminate the |
1286 | +licenses of parties who have received copies or rights from you under |
1287 | +this License. If your rights have been terminated and not permanently |
1288 | +reinstated, you do not qualify to receive new licenses for the same |
1289 | +material under section 10. |
1290 | + |
1291 | + 9. Acceptance Not Required for Having Copies. |
1292 | + |
1293 | + You are not required to accept this License in order to receive or |
1294 | +run a copy of the Program. Ancillary propagation of a covered work |
1295 | +occurring solely as a consequence of using peer-to-peer transmission |
1296 | +to receive a copy likewise does not require acceptance. However, |
1297 | +nothing other than this License grants you permission to propagate or |
1298 | +modify any covered work. These actions infringe copyright if you do |
1299 | +not accept this License. Therefore, by modifying or propagating a |
1300 | +covered work, you indicate your acceptance of this License to do so. |
1301 | + |
1302 | + 10. Automatic Licensing of Downstream Recipients. |
1303 | + |
1304 | + Each time you convey a covered work, the recipient automatically |
1305 | +receives a license from the original licensors, to run, modify and |
1306 | +propagate that work, subject to this License. You are not responsible |
1307 | +for enforcing compliance by third parties with this License. |
1308 | + |
1309 | + An "entity transaction" is a transaction transferring control of an |
1310 | +organization, or substantially all assets of one, or subdividing an |
1311 | +organization, or merging organizations. If propagation of a covered |
1312 | +work results from an entity transaction, each party to that |
1313 | +transaction who receives a copy of the work also receives whatever |
1314 | +licenses to the work the party's predecessor in interest had or could |
1315 | +give under the previous paragraph, plus a right to possession of the |
1316 | +Corresponding Source of the work from the predecessor in interest, if |
1317 | +the predecessor has it or can get it with reasonable efforts. |
1318 | + |
1319 | + You may not impose any further restrictions on the exercise of the |
1320 | +rights granted or affirmed under this License. For example, you may |
1321 | +not impose a license fee, royalty, or other charge for exercise of |
1322 | +rights granted under this License, and you may not initiate litigation |
1323 | +(including a cross-claim or counterclaim in a lawsuit) alleging that |
1324 | +any patent claim is infringed by making, using, selling, offering for |
1325 | +sale, or importing the Program or any portion of it. |
1326 | + |
1327 | + 11. Patents. |
1328 | + |
1329 | + A "contributor" is a copyright holder who authorizes use under this |
1330 | +License of the Program or a work on which the Program is based. The |
1331 | +work thus licensed is called the contributor's "contributor version". |
1332 | + |
1333 | + A contributor's "essential patent claims" are all patent claims |
1334 | +owned or controlled by the contributor, whether already acquired or |
1335 | +hereafter acquired, that would be infringed by some manner, permitted |
1336 | +by this License, of making, using, or selling its contributor version, |
1337 | +but do not include claims that would be infringed only as a |
1338 | +consequence of further modification of the contributor version. For |
1339 | +purposes of this definition, "control" includes the right to grant |
1340 | +patent sublicenses in a manner consistent with the requirements of |
1341 | +this License. |
1342 | + |
1343 | + Each contributor grants you a non-exclusive, worldwide, royalty-free |
1344 | +patent license under the contributor's essential patent claims, to |
1345 | +make, use, sell, offer for sale, import and otherwise run, modify and |
1346 | +propagate the contents of its contributor version. |
1347 | + |
1348 | + In the following three paragraphs, a "patent license" is any express |
1349 | +agreement or commitment, however denominated, not to enforce a patent |
1350 | +(such as an express permission to practice a patent or covenant not to |
1351 | +sue for patent infringement). To "grant" such a patent license to a |
1352 | +party means to make such an agreement or commitment not to enforce a |
1353 | +patent against the party. |
1354 | + |
1355 | + If you convey a covered work, knowingly relying on a patent license, |
1356 | +and the Corresponding Source of the work is not available for anyone |
1357 | +to copy, free of charge and under the terms of this License, through a |
1358 | +publicly available network server or other readily accessible means, |
1359 | +then you must either (1) cause the Corresponding Source to be so |
1360 | +available, or (2) arrange to deprive yourself of the benefit of the |
1361 | +patent license for this particular work, or (3) arrange, in a manner |
1362 | +consistent with the requirements of this License, to extend the patent |
1363 | +license to downstream recipients. "Knowingly relying" means you have |
1364 | +actual knowledge that, but for the patent license, your conveying the |
1365 | +covered work in a country, or your recipient's use of the covered work |
1366 | +in a country, would infringe one or more identifiable patents in that |
1367 | +country that you have reason to believe are valid. |
1368 | + |
1369 | + If, pursuant to or in connection with a single transaction or |
1370 | +arrangement, you convey, or propagate by procuring conveyance of, a |
1371 | +covered work, and grant a patent license to some of the parties |
1372 | +receiving the covered work authorizing them to use, propagate, modify |
1373 | +or convey a specific copy of the covered work, then the patent license |
1374 | +you grant is automatically extended to all recipients of the covered |
1375 | +work and works based on it. |
1376 | + |
1377 | + A patent license is "discriminatory" if it does not include within |
1378 | +the scope of its coverage, prohibits the exercise of, or is |
1379 | +conditioned on the non-exercise of one or more of the rights that are |
1380 | +specifically granted under this License. You may not convey a covered |
1381 | +work if you are a party to an arrangement with a third party that is |
1382 | +in the business of distributing software, under which you make payment |
1383 | +to the third party based on the extent of your activity of conveying |
1384 | +the work, and under which the third party grants, to any of the |
1385 | +parties who would receive the covered work from you, a discriminatory |
1386 | +patent license (a) in connection with copies of the covered work |
1387 | +conveyed by you (or copies made from those copies), or (b) primarily |
1388 | +for and in connection with specific products or compilations that |
1389 | +contain the covered work, unless you entered into that arrangement, |
1390 | +or that patent license was granted, prior to 28 March 2007. |
1391 | + |
1392 | + Nothing in this License shall be construed as excluding or limiting |
1393 | +any implied license or other defenses to infringement that may |
1394 | +otherwise be available to you under applicable patent law. |
1395 | + |
1396 | + 12. No Surrender of Others' Freedom. |
1397 | + |
1398 | + If conditions are imposed on you (whether by court order, agreement or |
1399 | +otherwise) that contradict the conditions of this License, they do not |
1400 | +excuse you from the conditions of this License. If you cannot convey a |
1401 | +covered work so as to satisfy simultaneously your obligations under this |
1402 | +License and any other pertinent obligations, then as a consequence you may |
1403 | +not convey it at all. For example, if you agree to terms that obligate you |
1404 | +to collect a royalty for further conveying from those to whom you convey |
1405 | +the Program, the only way you could satisfy both those terms and this |
1406 | +License would be to refrain entirely from conveying the Program. |
1407 | + |
1408 | + 13. Use with the GNU Affero General Public License. |
1409 | + |
1410 | + Notwithstanding any other provision of this License, you have |
1411 | +permission to link or combine any covered work with a work licensed |
1412 | +under version 3 of the GNU Affero General Public License into a single |
1413 | +combined work, and to convey the resulting work. The terms of this |
1414 | +License will continue to apply to the part which is the covered work, |
1415 | +but the special requirements of the GNU Affero General Public License, |
1416 | +section 13, concerning interaction through a network will apply to the |
1417 | +combination as such. |
1418 | + |
1419 | + 14. Revised Versions of this License. |
1420 | + |
1421 | + The Free Software Foundation may publish revised and/or new versions of |
1422 | +the GNU General Public License from time to time. Such new versions will |
1423 | +be similar in spirit to the present version, but may differ in detail to |
1424 | +address new problems or concerns. |
1425 | + |
1426 | + Each version is given a distinguishing version number. If the |
1427 | +Program specifies that a certain numbered version of the GNU General |
1428 | +Public License "or any later version" applies to it, you have the |
1429 | +option of following the terms and conditions either of that numbered |
1430 | +version or of any later version published by the Free Software |
1431 | +Foundation. If the Program does not specify a version number of the |
1432 | +GNU General Public License, you may choose any version ever published |
1433 | +by the Free Software Foundation. |
1434 | + |
1435 | + If the Program specifies that a proxy can decide which future |
1436 | +versions of the GNU General Public License can be used, that proxy's |
1437 | +public statement of acceptance of a version permanently authorizes you |
1438 | +to choose that version for the Program. |
1439 | + |
1440 | + Later license versions may give you additional or different |
1441 | +permissions. However, no additional obligations are imposed on any |
1442 | +author or copyright holder as a result of your choosing to follow a |
1443 | +later version. |
1444 | + |
1445 | + 15. Disclaimer of Warranty. |
1446 | + |
1447 | + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY |
1448 | +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT |
1449 | +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY |
1450 | +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, |
1451 | +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
1452 | +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM |
1453 | +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF |
1454 | +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. |
1455 | + |
1456 | + 16. Limitation of Liability. |
1457 | + |
1458 | + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING |
1459 | +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS |
1460 | +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY |
1461 | +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE |
1462 | +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF |
1463 | +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD |
1464 | +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), |
1465 | +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF |
1466 | +SUCH DAMAGES. |
1467 | + |
1468 | + 17. Interpretation of Sections 15 and 16. |
1469 | + |
1470 | + If the disclaimer of warranty and limitation of liability provided |
1471 | +above cannot be given local legal effect according to their terms, |
1472 | +reviewing courts shall apply local law that most closely approximates |
1473 | +an absolute waiver of all civil liability in connection with the |
1474 | +Program, unless a warranty or assumption of liability accompanies a |
1475 | +copy of the Program in return for a fee. |
1476 | + |
1477 | + END OF TERMS AND CONDITIONS |
1478 | + |
1479 | + How to Apply These Terms to Your New Programs |
1480 | + |
1481 | + If you develop a new program, and you want it to be of the greatest |
1482 | +possible use to the public, the best way to achieve this is to make it |
1483 | +free software which everyone can redistribute and change under these terms. |
1484 | + |
1485 | + To do so, attach the following notices to the program. It is safest |
1486 | +to attach them to the start of each source file to most effectively |
1487 | +state the exclusion of warranty; and each file should have at least |
1488 | +the "copyright" line and a pointer to where the full notice is found. |
1489 | + |
1490 | + <one line to give the program's name and a brief idea of what it does.> |
1491 | + Copyright (C) <year> <name of author> |
1492 | + |
1493 | + This program is free software: you can redistribute it and/or modify |
1494 | + it under the terms of the GNU General Public License as published by |
1495 | + the Free Software Foundation, either version 3 of the License, or |
1496 | + (at your option) any later version. |
1497 | + |
1498 | + This program is distributed in the hope that it will be useful, |
1499 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
1500 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1501 | + GNU General Public License for more details. |
1502 | + |
1503 | + You should have received a copy of the GNU General Public License |
1504 | + along with this program. If not, see <http://www.gnu.org/licenses/>. |
1505 | + |
1506 | +Also add information on how to contact you by electronic and paper mail. |
1507 | + |
1508 | + If the program does terminal interaction, make it output a short |
1509 | +notice like this when it starts in an interactive mode: |
1510 | + |
1511 | + <program> Copyright (C) <year> <name of author> |
1512 | + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. |
1513 | + This is free software, and you are welcome to redistribute it |
1514 | + under certain conditions; type `show c' for details. |
1515 | + |
1516 | +The hypothetical commands `show w' and `show c' should show the appropriate |
1517 | +parts of the General Public License. Of course, your program's commands |
1518 | +might be different; for a GUI interface, you would use an "about box". |
1519 | + |
1520 | + You should also get your employer (if you work as a programmer) or school, |
1521 | +if any, to sign a "copyright disclaimer" for the program, if necessary. |
1522 | +For more information on this, and how to apply and follow the GNU GPL, see |
1523 | +<http://www.gnu.org/licenses/>. |
1524 | + |
1525 | + The GNU General Public License does not permit incorporating your program |
1526 | +into proprietary programs. If your program is a subroutine library, you |
1527 | +may consider it more useful to permit linking proprietary applications with |
1528 | +the library. If this is what you want to do, use the GNU Lesser General |
1529 | +Public License instead of this License. But first, please read |
1530 | +<http://www.gnu.org/philosophy/why-not-lgpl.html>. |
1531 | + |
1532 | |
1533 | === added directory 'dxdiff/dxdiff' |
1534 | === added file 'dxdiff/dxdiff/__init__.py' |
1535 | === added file 'dxdiff/dxdiff/bimap.py' |
1536 | --- dxdiff/dxdiff/bimap.py 1970-01-01 00:00:00 +0000 |
1537 | +++ dxdiff/dxdiff/bimap.py 2011-08-24 16:38:18 +0000 |
1538 | @@ -0,0 +1,49 @@ |
1539 | +#!/usr/bin/env python |
1540 | + |
1541 | +# This file is part of dxdiff. |
1542 | +# |
1543 | +# dxdiff is free software: you can redistribute it and/or modify |
1544 | +# it under the terms of the GNU General Public License as published by |
1545 | +# the Free Software Foundation, either version 3 of the License, or |
1546 | +# (at your option) any later version. |
1547 | +# |
1548 | +# dxdiff is distributed in the hope that it will be useful, |
1549 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1550 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1551 | +# GNU General Public License for more details. |
1552 | +# |
1553 | +# You should have received a copy of the GNU General Public License |
1554 | +# along with Diamond. If not, see <http://www.gnu.org/licenses/>. |
1555 | + |
1556 | + |
1557 | +class Bimap: |
1558 | + """ |
1559 | + Bimap is a simple wrapper class over two dicts, |
1560 | + it behaves as a bi-directional map. |
1561 | + """ |
1562 | + |
1563 | + def __init__(self): |
1564 | + self.left = {} |
1565 | + self.right = {} |
1566 | + |
1567 | + def __len__(self): |
1568 | + return len(self.left) |
1569 | + |
1570 | + def __iter__(self): |
1571 | + # we iter over the left dict so that the left item is |
1572 | + # on the left side of the tuple returned |
1573 | + for item in self.left.iteritems(): |
1574 | + yield item |
1575 | + |
1576 | + def __contains__(self, item): |
1577 | + # check that the left dict contains left and points to right |
1578 | + try: |
1579 | + left, right = item |
1580 | + return self.left[left] == right |
1581 | + except KeyError: |
1582 | + return False |
1583 | + |
1584 | + def add(self, item): |
1585 | + x, y = item |
1586 | + self.left[x] = y |
1587 | + self.right[y] = x |
1588 | |
1589 | === added file 'dxdiff/dxdiff/diff.py' |
1590 | --- dxdiff/dxdiff/diff.py 1970-01-01 00:00:00 +0000 |
1591 | +++ dxdiff/dxdiff/diff.py 2011-08-24 16:38:18 +0000 |
1592 | @@ -0,0 +1,26 @@ |
1593 | +#!/usr/bin/env python |
1594 | + |
1595 | +# This file is part of dxdiff. |
1596 | +# |
1597 | +# dxdiff is free software: you can redistribute it and/or modify |
1598 | +# it under the terms of the GNU General Public License as published by |
1599 | +# the Free Software Foundation, either version 3 of the License, or |
1600 | +# (at your option) any later version. |
1601 | +# |
1602 | +# dxdiff is distributed in the hope that it will be useful, |
1603 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1604 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1605 | +# GNU General Public License for more details. |
1606 | +# |
1607 | +# You should have received a copy of the GNU General Public License |
1608 | +# along with Diamond. If not, see <http://www.gnu.org/licenses/>. |
1609 | + |
1610 | +import fmes |
1611 | + |
1612 | +def diff(xmlold, xmlnew): |
1613 | + """ |
1614 | + Compares two xml trees. |
1615 | + Returns an editscript to transform old into new. |
1616 | + """ |
1617 | + |
1618 | + return fmes.diff(xmlold, xmlnew) |
1619 | |
1620 | === added file 'dxdiff/dxdiff/dxdiff' |
1621 | --- dxdiff/dxdiff/dxdiff 1970-01-01 00:00:00 +0000 |
1622 | +++ dxdiff/dxdiff/dxdiff 2011-08-24 16:38:18 +0000 |
1623 | @@ -0,0 +1,87 @@ |
1624 | +#!/usr/bin/env python |
1625 | + |
1626 | +# This file is part of dxdiff. |
1627 | +# |
1628 | +# dxdiff is free software: you can redistribute it and/or modify |
1629 | +# it under the terms of the GNU General Public License as published by |
1630 | +# the Free Software Foundation, either version 3 of the License, or |
1631 | +# (at your option) any later version. |
1632 | +# |
1633 | +# dxdiff is distributed in the hope that it will be useful, |
1634 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1635 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1636 | +# GNU General Public License for more details. |
1637 | +# |
1638 | +# You should have received a copy of the GNU General Public License |
1639 | +# along with Diamond. If not, see <http://www.gnu.org/licenses/>. |
1640 | + |
1641 | +from getopt import getopt |
1642 | +import sys |
1643 | +import os |
1644 | +from lxml import etree |
1645 | +from dxdiff.diff import diff |
1646 | + |
1647 | +def __display_help(): |
1648 | + """ |
1649 | + Prints usage information to standard output. |
1650 | + """ |
1651 | + |
1652 | + print "\n".join(["Usage: dxdiff [OPTIONS] ... [FILE1] [FILE2]", |
1653 | + "", |
1654 | + "An XML aware diff tool. [FILE1] and [FILE2] are the XML files to be compared.", |
1655 | + "[FILE1] should be the old file.", |
1656 | + "", |
1657 | + "Options:", |
1658 | + "" |
1659 | + "-h Display this message", |
1660 | + ""]) |
1661 | + |
1662 | +def __main(): |
1663 | + """ |
1664 | + Main routine to run dxdiff |
1665 | + """ |
1666 | + |
1667 | + try: |
1668 | + opts, args = getopt(sys.argv[1:], "hso:") |
1669 | + except: |
1670 | + __display_help() |
1671 | + sys.exit(1) |
1672 | + |
1673 | + if len(args) != 2: |
1674 | + __display_help() |
1675 | + sys.exit(1) |
1676 | + |
1677 | + if ("h", "") in opts: |
1678 | + __display_help() |
1679 | + |
1680 | + output_file = None |
1681 | + for opt in opts: |
1682 | + if opt[0] == "-o": |
1683 | + output_file = opt[1] |
1684 | + |
1685 | + file1 = args[0] |
1686 | + file2 = args[1] |
1687 | + |
1688 | + try: |
1689 | + os.stat(file1) |
1690 | + except OSError: |
1691 | + print "Could not find " + file1 + "!" |
1692 | + sys.exit(1) |
1693 | + |
1694 | + try: |
1695 | + os.stat(file2) |
1696 | + except OSError: |
1697 | + print "Could not find " + file2 + "!" |
1698 | + sys.exit(1) |
1699 | + |
1700 | + xmlold = etree.parse(file1) |
1701 | + xmlnew = etree.parse(file2) |
1702 | + |
1703 | + editscript = diff(xmlold, xmlnew) |
1704 | + if ("s", "") not in opts: |
1705 | + print editscript |
1706 | + if output_file is not None: |
1707 | + editscript.write(output_file) |
1708 | + |
1709 | +if __name__ == "__main__": |
1710 | + __main() |
1711 | |
1712 | === added file 'dxdiff/dxdiff/editscript.py' |
1713 | --- dxdiff/dxdiff/editscript.py 1970-01-01 00:00:00 +0000 |
1714 | +++ dxdiff/dxdiff/editscript.py 2011-08-24 16:38:18 +0000 |
1715 | @@ -0,0 +1,79 @@ |
1716 | +#!/usr/bin/env python |
1717 | + |
1718 | +# This file is part of dxdiff. |
1719 | +# |
1720 | +# dxdiff is free software: you can redistribute it and/or modify |
1721 | +# it under the terms of the GNU General Public License as published by |
1722 | +# the Free Software Foundation, either version 3 of the License, or |
1723 | +# (at your option) any later version. |
1724 | +# |
1725 | +# dxdiff is distributed in the hope that it will be useful, |
1726 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1727 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1728 | +# GNU General Public License for more details. |
1729 | +# |
1730 | +# You should have received a copy of the GNU General Public License |
1731 | +# along with Diamond. If not, see <http://www.gnu.org/licenses/>. |
1732 | + |
1733 | +from lxml import etree |
1734 | + |
1735 | +class EditScript: |
1736 | + |
1737 | + def __init__(self): |
1738 | + self.script = [] |
1739 | + |
1740 | + def __str__(self): |
1741 | + return etree.tostring(self.to_xml(), pretty_print = True) |
1742 | + |
1743 | + def __len__(self): |
1744 | + return len(self.script) |
1745 | + |
1746 | + def __getitem__(self, key): |
1747 | + return self.script[key] |
1748 | + |
1749 | + def __iter__(self): |
1750 | + return self.script.__iter__() |
1751 | + |
1752 | + def update(self, path, value, userdata = None): |
1753 | + self.script.append({ "type": "update", |
1754 | + "location": path, |
1755 | + "value": value, |
1756 | + "userdata": userdata }) |
1757 | + |
1758 | + def insert(self, path, index, tag, value = None, userdata = None): |
1759 | + self.script.append({ "type": "insert", |
1760 | + "location": path, |
1761 | + "index": index, |
1762 | + "value": tag + (" " + value if value is not None else ""), |
1763 | + "userdata": userdata}) |
1764 | + |
1765 | + def delete(self, path, userdata = None): |
1766 | + self.script.append({ "type": "delete", |
1767 | + "location": path, |
1768 | + "userdata": userdata}) |
1769 | + |
1770 | + def move(self, path, destination, index, userdata = None): |
1771 | + self.script.append({ "type": "move", |
1772 | + "location": path, |
1773 | + "index": index, |
1774 | + "value": destination, |
1775 | + "userdata": userdata }) |
1776 | + |
1777 | + def to_xml(self): |
1778 | + tree = etree.Element("xmldiff") |
1779 | + |
1780 | + for edit in self.script: |
1781 | + node = etree.Element(edit["type"], location = edit["location"]) |
1782 | + if "index" in edit: |
1783 | + node.attrib["index"] = edit["index"] |
1784 | + if edit["userdata"] is not None: |
1785 | + node.attrib["userdata"] = edit["userdata"] |
1786 | + |
1787 | + if "value" in edit: |
1788 | + node.text = edit["value"] |
1789 | + tree.append(node) |
1790 | + |
1791 | + return etree.ElementTree(tree) |
1792 | + |
1793 | + def write(self, path): |
1794 | + self.to_xml().write(path, pretty_print = True, xml_declaration = True, encoding = "utf-8") |
1795 | |
1796 | === added file 'dxdiff/dxdiff/fmes.py' |
1797 | --- dxdiff/dxdiff/fmes.py 1970-01-01 00:00:00 +0000 |
1798 | +++ dxdiff/dxdiff/fmes.py 2011-08-24 16:38:18 +0000 |
1799 | @@ -0,0 +1,466 @@ |
1800 | +#!/usr/bin/env python |
1801 | + |
1802 | +# This file is part of dxdiff. |
1803 | +# |
1804 | +# dxdiff is free software: you can redistribute it and/or modify |
1805 | +# it under the terms of the GNU General Public License as published by |
1806 | +# the Free Software Foundation, either version 3 of the License, or |
1807 | +# (at your option) any later version. |
1808 | +# |
1809 | +# dxdiff is distributed in the hope that it will be useful, |
1810 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1811 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1812 | +# GNU General Public License for more details. |
1813 | +# |
1814 | +# You should have received a copy of the GNU General Public License |
1815 | +# along with Diamond. If not, see <http://www.gnu.org/licenses/>. |
1816 | +""" |
1817 | +Diff xml trees using a modification of FMES [http://infolab.stanford.edu/pub/papers/tdiff3-8.ps] |
1818 | +""" |
1819 | + |
1820 | +from lxml import etree |
1821 | +from collections import deque |
1822 | +from bimap import Bimap |
1823 | +from editscript import EditScript |
1824 | + |
1825 | +import lcs |
1826 | +import utils |
1827 | + |
1828 | +class Dom: |
1829 | + def __init__(self, tag, value, parent, attribute = False): |
1830 | + self.tag = tag |
1831 | + self.value = value |
1832 | + self.parent = parent |
1833 | + self.children = [] |
1834 | + |
1835 | + if value is None: |
1836 | + self.typetag = "/Element" |
1837 | + elif attribute: |
1838 | + self.typetag = "/Attribute" |
1839 | + else: |
1840 | + self.typetag = "/Text" |
1841 | + |
1842 | + if parent: |
1843 | + self.depth = parent.depth + 1 |
1844 | + else: |
1845 | + self.depth = 0 |
1846 | + |
1847 | + self.inorder = False |
1848 | + |
1849 | + def elements(self): |
1850 | + return [child for child in self.children if child.is_element()] |
1851 | + |
1852 | + def attributes(self): |
1853 | + return [child for child in self.children if child.is_attribute()] |
1854 | + |
1855 | + def text(self): |
1856 | + return [child for child in self.children if child.is_text()] |
1857 | + |
1858 | + def is_element(self): |
1859 | + return self.typetag == "/Element" |
1860 | + |
1861 | + def is_text(self): |
1862 | + return self.typetag == "/Text" |
1863 | + |
1864 | + def is_attribute(self): |
1865 | + return self.typetag == "/Attribute" |
1866 | + |
1867 | + def __repr__(self): |
1868 | + return "<" + self.label + ">" + (self.value or "") |
1869 | + |
1870 | + def __str__(self, indent = ""): |
1871 | + title = indent + "<" + self.path() + ">" + (self.value or "") + "\n" + indent |
1872 | + children = ("\n" + indent).join(child.__str__(indent + " ") for child in self.children) |
1873 | + return title + children |
1874 | + |
1875 | + def path(self): |
1876 | + """ |
1877 | + Finds the path of this element. |
1878 | + """ |
1879 | + if self.is_text(): |
1880 | + return self.parent.path() + "/text()" |
1881 | + |
1882 | + if self.is_attribute(): |
1883 | + return self.parent.path() + "/@" + self.tag |
1884 | + |
1885 | + if self.parent: |
1886 | + siblings = [sibling for sibling in self.parent.elements() if sibling.tag == self.tag] |
1887 | + if len(siblings) != 1: |
1888 | + index = "[" + str(siblings.index(self) + 1) + "]" |
1889 | + else: |
1890 | + index = "" |
1891 | + return self.parent.path() + "/" + self.tag + index |
1892 | + else: |
1893 | + return "/" + self.tag |
1894 | + |
1895 | + def find(self, path): |
1896 | + |
1897 | + if self.is_text(): |
1898 | + if path == "/text()": |
1899 | + return self |
1900 | + else: return None |
1901 | + |
1902 | + if self.is_attribute(): |
1903 | + if path == "/@" + self.tag: |
1904 | + return self |
1905 | + else: return None |
1906 | + |
1907 | + index = path.find("/", 1) |
1908 | + if index == -1: |
1909 | + index = len(path) |
1910 | + |
1911 | + root = path[:index] |
1912 | + path = path[index:] |
1913 | + |
1914 | + if self.parent: |
1915 | + siblings = [sibling for sibling in self.parent.elements() if sibling.tag == self.tag] |
1916 | + if len(siblings) != 1: |
1917 | + index = "[" + str(siblings.index(self) + 1) + "]" |
1918 | + else: |
1919 | + index = "" |
1920 | + |
1921 | + if root != "/" + self.tag + index: |
1922 | + return None |
1923 | + else: |
1924 | + if root != "/" + self.tag: |
1925 | + return None |
1926 | + |
1927 | + if path: |
1928 | + for child in self.children: |
1929 | + result = child.find(path) |
1930 | + if result: |
1931 | + return result |
1932 | + else: |
1933 | + return self |
1934 | + |
1935 | + def _real_index(self, parent, index): |
1936 | + if index == 0: |
1937 | + return 0 |
1938 | + |
1939 | + elements = parent.elements() |
1940 | + if len(elements) < index: |
1941 | + return len(parent.children) |
1942 | + return parent.children.index(elements[index - 1]) |
1943 | + |
1944 | + def insert(self, tag, tagtype, value, path, index): |
1945 | + parent = self.find(path) |
1946 | + |
1947 | + node = Dom(tag, value, parent, tagtype == "/Attribute") |
1948 | + parent.children.insert(self._real_index(parent, index), node) |
1949 | + node.label = _strip_indexers(node.path()) + node.typetag |
1950 | + return node |
1951 | + |
1952 | + def update(self, path, value): |
1953 | + node = self.find(path) |
1954 | + |
1955 | + node.value = value |
1956 | + return node |
1957 | + |
1958 | + def move(self, from_path, to_path, index): |
1959 | + node = self.find(from_path) |
1960 | + node.parent.children.remove(node) |
1961 | + |
1962 | + parent = self.find(to_path) |
1963 | + parent.children.insert(self._real_index(parent, index), node) |
1964 | + node.parent = parent |
1965 | + |
1966 | + def delete(self, path): |
1967 | + node = self.find(path) |
1968 | + node.parent.children.remove(node) |
1969 | + node.parent = None |
1970 | + |
1971 | +def _get_text(tree): |
1972 | + """ |
1973 | + Returns the text and child tails. |
1974 | + """ |
1975 | + return "".join([tree.text or ""] + [child.tail or "" for child in tree]).strip() |
1976 | + |
1977 | +def _strip_indexers(path): |
1978 | + """ |
1979 | + Strips out indexers from an path. |
1980 | + """ |
1981 | + while True: |
1982 | + lindex = path.find("[") |
1983 | + if lindex == -1: |
1984 | + break |
1985 | + rindex = path.find("]", lindex) |
1986 | + path = path[:lindex] + path[rindex + 1:] |
1987 | + return path |
1988 | + |
1989 | +def dom(root, tree = None, parent = None): |
1990 | + |
1991 | + if tree is None: |
1992 | + tree = root.getroot() |
1993 | + |
1994 | + xpath = root.getpath(tree) |
1995 | + path = _strip_indexers(xpath) |
1996 | + |
1997 | + node = Dom(tree.tag, None, parent) |
1998 | + node.label = path + node.typetag |
1999 | + node.xpath = xpath |
2000 | + |
2001 | + text = _get_text(tree) |
2002 | + if text: |
2003 | + text = Dom(tree.tag, text, node) |
2004 | + text.label = path + text.typetag |
2005 | + text.xpath = xpath + "/text()" |
2006 | + node.children.append(text) |
2007 | + |
2008 | + for key, value in tree.items(): |
2009 | + attr = Dom(key, value, node, True) |
2010 | + attr.label = path + "/@" + key + attr.typetag |
2011 | + attr.xpath = path + "/@" + key |
2012 | + node.children.append(attr) |
2013 | + |
2014 | + for child in tree: |
2015 | + node.children.append(dom(root, child, node)) |
2016 | + |
2017 | + return node |
2018 | + |
2019 | +def get_leaf_nodes(tree): |
2020 | + """ |
2021 | + Gets all the leaf nodes of an xml tree. |
2022 | + """ |
2023 | + if tree.children: |
2024 | + return utils.flatten([get_leaf_nodes(child) for child in tree.children]) |
2025 | + else: |
2026 | + return [tree] |
2027 | + |
2028 | +def get_parent_nodes(tree): |
2029 | + """ |
2030 | + Returns all the non leaf nodes of an xml tree. |
2031 | + """ |
2032 | + |
2033 | + if tree.children: |
2034 | + return utils.flatten([get_parent_nodes(child) for child in tree.children]) + [tree] |
2035 | + else: |
2036 | + return [] |
2037 | + |
2038 | +def get_depth(tree): |
2039 | + """ |
2040 | + Returns the maximum depth of a tree. |
2041 | + """ |
2042 | + depth = tree.depth |
2043 | + for child in tree.children: |
2044 | + depth = max(depth, get_depth(child)) |
2045 | + return depth |
2046 | + |
2047 | +def get_depth_nodes(tree, depth): |
2048 | + """ |
2049 | + Gets all the nodes of a certain depth of an xml tree. |
2050 | + """ |
2051 | + if tree.depth == depth: |
2052 | + return [tree] |
2053 | + else: |
2054 | + if tree.children: |
2055 | + return utils.flatten([get_depth_nodes(child, depth) for child in tree.children]) |
2056 | + else: |
2057 | + return [] |
2058 | + |
2059 | +def get_chain(nodes, label): |
2060 | + return [node for node in nodes if node.label == label] |
2061 | + |
2062 | +def compare_value(value1, value2): |
2063 | + if value1 is None and value2 is None: |
2064 | + return 0.0 |
2065 | + if value1 is None or value2 is None: |
2066 | + return 1.0 |
2067 | + return 1.0 - (float(len(lcs.lcs(lcs.path(value1, value2)))) / max(len(value1), len(value2))) |
2068 | + |
2069 | +def leaf_equal(f, M, l1, l2): |
2070 | + return l1.label == l2.label and compare_value(l1.value, l2.value) <= f |
2071 | + |
2072 | +def common(children1, children2, M): |
2073 | + return [(x, y) for (x, y) in M if x in children1 and y in children2] |
2074 | + |
2075 | +def compare_children(children1, children2, M): |
2076 | + return (float(len(common(children1, children2, M))) / max(len(children1), len(children2))) |
2077 | + |
2078 | +def node_equal(t, M, n1, n2): |
2079 | + return n1.label == n2.label and compare_children(n1.children, n2.children, M) > t |
2080 | + |
2081 | +def depth_equal(f, t, M, n1, n2): |
2082 | + if n1.children or n2.children: |
2083 | + return node_equal(t, M, n1, n2) |
2084 | + else: |
2085 | + return leaf_equal(f, M, n1, n2) |
2086 | + |
2087 | +def _match(nodes1, nodes2, M, equal): |
2088 | + nodes = nodes1 + nodes2 |
2089 | + for label in utils.nub([node.label for node in nodes]): |
2090 | + |
2091 | + s1 = get_chain(nodes1, label) |
2092 | + s2 = get_chain(nodes2, label) |
2093 | + |
2094 | + path = lcs.lcs(lcs.path(s1, s2, equal)) |
2095 | + |
2096 | + for x, y in path: |
2097 | + M.add((s1[x], s2[y])) |
2098 | + for x, y in reversed(path): |
2099 | + s1.pop(x) |
2100 | + s2.pop(y) |
2101 | + |
2102 | + for x in range(len(s1)): |
2103 | + for y in range(len(s2)): |
2104 | + if equal(s1[x], s2[y]): |
2105 | + M.add((s1[x], s2[y])) |
2106 | + s2.pop(y) |
2107 | + break |
2108 | + |
2109 | +def fastmatch(t1, t2): |
2110 | + """ |
2111 | + Calculates a match between t1 and t2. |
2112 | + See figure 10 in reference. |
2113 | + """ |
2114 | + M = Bimap() |
2115 | + |
2116 | + depth = max(get_depth(t1), get_depth(t2)) |
2117 | + |
2118 | + while 0 <= depth: |
2119 | + nodes1 = get_depth_nodes(t1, depth) |
2120 | + nodes2 = get_depth_nodes(t2, depth) |
2121 | + |
2122 | + equal = utils.partial(depth_equal, 0.6, 0.5, M) |
2123 | + |
2124 | + _match(nodes1, nodes2, M, equal) |
2125 | + |
2126 | + depth -= 1 |
2127 | + |
2128 | + return M |
2129 | + |
2130 | +def breadth_iter(tree): |
2131 | + Q = deque() |
2132 | + Q.append(tree) |
2133 | + while Q: |
2134 | + t = Q.popleft() |
2135 | + if t is not tree: |
2136 | + yield t |
2137 | + if t.parent is not None or t is tree: #check we haven't deleted it |
2138 | + for child in t.children: |
2139 | + Q.append(child) |
2140 | + |
2141 | +def postorder_iter(tree): |
2142 | + S = [] |
2143 | + O = [] |
2144 | + S.append(tree) |
2145 | + while S: |
2146 | + t = S.pop() |
2147 | + O.append(t) |
2148 | + for child in t.children: |
2149 | + S.append(child) |
2150 | + while O: |
2151 | + t = O.pop() |
2152 | + if t is not tree: |
2153 | + yield t |
2154 | + |
2155 | +def editscript(t1, t2): |
2156 | + """ |
2157 | + Finds an editscript between t1 and t2. |
2158 | + See figure 8 in reference. |
2159 | + """ |
2160 | + |
2161 | + E = EditScript() |
2162 | + M = fastmatch(t1, t2) |
2163 | + |
2164 | + M.add((t1, t2)) |
2165 | + alignchildren(t1, t2, M, E, t1, t2) |
2166 | + |
2167 | + for x in breadth_iter(t2): |
2168 | + y = x.parent |
2169 | + z = M.right[y] |
2170 | + |
2171 | + if x not in M.right: |
2172 | + if x.typetag == "/Text": #Can't insert Text, do an update |
2173 | + E.update(z.path(), x.value, x.xpath if hasattr(x, "xpath") else None) |
2174 | + w = t1.insert(x.tag, x.typetag, x.value, z.path(), 0) |
2175 | + M.add((w, x)) |
2176 | + else: |
2177 | + x.inorder = True |
2178 | + k = findpos(M, x) |
2179 | + E.insert(z.path(), str(k), x.tag, x.value, x.xpath if hasattr(x, "xpath") else None) |
2180 | + w = t1.insert(x.tag, x.typetag, x.value, z.path(), k) |
2181 | + M.add((w, x)) |
2182 | + else: # y is not None: |
2183 | + w = M.right[x] |
2184 | + v = w.parent |
2185 | + if w.value != x.value: |
2186 | + E.update(w.path(), x.value, w.xpath if hasattr(w, "xpath") else None) |
2187 | + t1.update(w.path(), x.value) |
2188 | + if (v, y) not in M: |
2189 | + x.inorder = True |
2190 | + k = findpos(M, x) |
2191 | + E.move(w.path(), z.path(), str(k), w.xpath if hasattr(w, "xpath") else None) |
2192 | + t1.move(w.path(), z.path(), k) |
2193 | + |
2194 | + alignchildren(t1, t2, M, E, w, x) |
2195 | + |
2196 | + for w in breadth_iter(t1): |
2197 | + if w not in M.left: |
2198 | + if w.typetag == "/Text": #Can't delete Text, do an update |
2199 | + E.update(w.path(), "", w.xpath if hasattr(w, "xpath") else None) |
2200 | + t1.update(w.path(), "") |
2201 | + else: |
2202 | + E.delete(w.path(), w.xpath if hasattr(w, "xpath") else None) |
2203 | + t1.delete(w.path()) |
2204 | + |
2205 | + return E |
2206 | + |
2207 | +def alignchildren(t1, t2, M, E, w, x): |
2208 | + """ |
2209 | + See figure 9 in reference. |
2210 | + """ |
2211 | + |
2212 | + for c in w.elements(): |
2213 | + c.inorder = False |
2214 | + for c in x.elements(): |
2215 | + c.inorder = False |
2216 | + |
2217 | + s1 = [child for child in w.elements() if child in M.left and M.left[child].parent == x] |
2218 | + s2 = [child for child in x.elements() if child in M.right and M.right[child].parent == w] |
2219 | + |
2220 | + def equal(a, b): |
2221 | + return (a, b) in M |
2222 | + |
2223 | + S = [(s1[x], s2[y]) for x, y in lcs.lcs(lcs.path(s1, s2, equal))] |
2224 | + for (a, b) in S: |
2225 | + a.inorder = b.inorder = True |
2226 | + |
2227 | + for a in s1: |
2228 | + for b in s2: |
2229 | + if (a, b) in M and (a, b) not in S: |
2230 | + k = findpos(M, b) |
2231 | + E.move(a.path(), w.path(), k, a.xpath if hasattr(a, "xpath") else None) |
2232 | + t1.move(a.path(), w.path(), str(k)) |
2233 | + a.inorder = b.inorder = True |
2234 | + |
2235 | +def findpos(M, x): |
2236 | + """ |
2237 | + See figure 9 in reference. |
2238 | + """ |
2239 | + if x.is_text() or x.is_attribute(): |
2240 | + return 0 |
2241 | + |
2242 | + y = x.parent |
2243 | + children = y.elements() |
2244 | + |
2245 | + #find the rightmost inorder node left of x (v) |
2246 | + index = children.index(x) |
2247 | + v = None |
2248 | + for i in range(index): |
2249 | + c = children[i] |
2250 | + if c.inorder: |
2251 | + v = c |
2252 | + |
2253 | + if v is None: |
2254 | + return 1 |
2255 | + |
2256 | + u = M.right[v] |
2257 | + children = u.parent.elements() |
2258 | + index = children.index(u) + 1 |
2259 | + return index + 1 |
2260 | + |
2261 | +def diff(tree1, tree2): |
2262 | + t1 = dom(tree1) |
2263 | + t2 = dom(tree2) |
2264 | + E = editscript(t1, t2) |
2265 | + return E |
2266 | |
2267 | === added file 'dxdiff/dxdiff/lcs.py' |
2268 | --- dxdiff/dxdiff/lcs.py 1970-01-01 00:00:00 +0000 |
2269 | +++ dxdiff/dxdiff/lcs.py 2011-08-24 16:38:18 +0000 |
2270 | @@ -0,0 +1,284 @@ |
2271 | +#!/usr/bin/env python |
2272 | + |
2273 | +# This file is part of dxdiff. |
2274 | +# |
2275 | +# dxdiff is free software: you can redistribute it and/or modify |
2276 | +# it under the terms of the GNU General Public License as published by |
2277 | +# the Free Software Foundation, either version 3 of the License, or |
2278 | +# (at your option) any later version. |
2279 | +# |
2280 | +# dxdiff is distributed in the hope that it will be useful, |
2281 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2282 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2283 | +# GNU General Public License for more details. |
2284 | +# |
2285 | +# You should have received a copy of the GNU General Public License |
2286 | +# along with Diamond. If not, see <http://www.gnu.org/licenses/>. |
2287 | +""" |
2288 | +Find the LCS (Longest Common Subsequence). Uses [http://www.xmailserver.org/diff2.pdf] |
2289 | +""" |
2290 | + |
2291 | +from utils import irange |
2292 | + |
2293 | +def __path(V, D, k): |
2294 | + if D == 0: |
2295 | + return [(xy, xy) for xy in irange(V[0][0])] |
2296 | + |
2297 | + tx = V[D][k] |
2298 | + |
2299 | + if k == -D or (k != D and V[D][k - 1] < V[D][k + 1]): |
2300 | + x = V[D][k + 1] |
2301 | + y = x - (k + 1) |
2302 | + k = k + 1 |
2303 | + y = y + 1 |
2304 | + else: |
2305 | + x = V[D][k - 1] |
2306 | + y = x - (k - 1) |
2307 | + k = k - 1 |
2308 | + x = x + 1 |
2309 | + |
2310 | + return __path(V, D - 1, k) + [(x + d, y + d) for d in irange(tx - x)] |
2311 | + |
2312 | +def __eq(a, b): return a == b |
2313 | + |
2314 | +def path(a, b, eq = __eq): |
2315 | + """ |
2316 | + Finds the path through the match grid of sequence a and b, |
2317 | + using the function eq to determine equality. |
2318 | + Returns path |
2319 | + """ |
2320 | + |
2321 | + m = len(a) |
2322 | + n = len(b) |
2323 | + mn = m + n |
2324 | + |
2325 | + if mn == 0: # two empty sequences |
2326 | + return [(0, 0)] |
2327 | + |
2328 | + Vd = [] |
2329 | + V = {1: 0} |
2330 | + |
2331 | + for D in irange(mn): |
2332 | + for k in irange(-D, D, 2): |
2333 | + if k == -D or (k != D and V[k - 1] < V[k + 1]): |
2334 | + x = V[k + 1] |
2335 | + else: |
2336 | + x = V[k - 1] + 1 |
2337 | + y = x - k |
2338 | + |
2339 | + while x < m and y < n and eq(a[x], b[y]): |
2340 | + x += 1 |
2341 | + y += 1 |
2342 | + |
2343 | + V[k] = x |
2344 | + |
2345 | + if x >= m and y >= n: |
2346 | + Vd.append(V.copy()) |
2347 | + return __path(Vd, D, k) |
2348 | + |
2349 | + Vd.append(V.copy()) |
2350 | + |
2351 | + raise Exception("lcs should not reach here") |
2352 | + |
2353 | +def lcs(path): |
2354 | + """ |
2355 | + Given an edit script path returns the longest common subseqence. |
2356 | + """ |
2357 | + |
2358 | + result = [] |
2359 | + |
2360 | + for i in range(1, len(path)): |
2361 | + x, y = path[i] |
2362 | + px, py = path[i - 1] |
2363 | + dx, dy = x - px, y - py |
2364 | + if dx == 1 and dy == 1: |
2365 | + result.append((px, py)) |
2366 | + |
2367 | + return result |
2368 | + |
2369 | +def ses(path, b): |
2370 | + """ |
2371 | + Returns an edit script for a given match grid path. |
2372 | + The edit script transforms sequence A of the match grid |
2373 | + into sequence B via deletions ("D", index) and inserations |
2374 | + ("I", A index, B value). |
2375 | + """ |
2376 | + |
2377 | + patch = [] |
2378 | + for i in range(len(path) - 1): |
2379 | + x, y = path[i] |
2380 | + nx, ny = path[i + 1] |
2381 | + dx, dy = nx - x, ny - y |
2382 | + if dx == 1 and dy == 1: |
2383 | + pass #match |
2384 | + elif dx == 1: |
2385 | + patch.append(("D", x)) |
2386 | + else: #dy == 1: |
2387 | + patch.append(("I", x, b[y])) |
2388 | + |
2389 | + return patch |
2390 | + |
2391 | +def patch(patch, a): |
2392 | + """ |
2393 | + Given a sequence and a patch from the ses function transforms a into b |
2394 | + """ |
2395 | + |
2396 | + seq = type(a) |
2397 | + result = seq() |
2398 | + i = 0 |
2399 | + |
2400 | + for op in patch: |
2401 | + while i < op[1]: |
2402 | + result += seq(a[i]) |
2403 | + i += 1 |
2404 | + |
2405 | + if op[0] == "D": |
2406 | + i += 1 |
2407 | + else: |
2408 | + result += seq(op[2]) |
2409 | + |
2410 | + while i < len(a): |
2411 | + result += seq(a[i]) |
2412 | + i += 1 |
2413 | + |
2414 | + return result |
2415 | + |
2416 | +################## |
2417 | +### Unit Tests ### |
2418 | +################## |
2419 | + |
2420 | +import unittest |
2421 | + |
2422 | +class __Test_lcs(unittest.TestCase): |
2423 | + def test_zero(self): |
2424 | + self.assertEqual(path("", ""), [(0, 0)]) |
2425 | + self.assertEqual(path("", "a"), [(0, 0), (0, 1)]) |
2426 | + self.assertEqual(path("a", ""), [(0, 0), (1, 0)]) |
2427 | + |
2428 | + def test_single(self): |
2429 | + self.assertEqual(path("a", "a"), [(0, 0), (1, 1)]) |
2430 | + self.assertEqual(path("a", "b"), [(0, 0), (1, 0), (1, 1)]) |
2431 | + |
2432 | + def test_short(self): |
2433 | + self.assertEqual(path("ab", "ab"), [(0, 0), (1, 1), (2, 2)]) |
2434 | + self.assertEqual(path("ab", "ac"), [(0, 0), (1, 1), (2, 1), (2, 2)]) |
2435 | + self.assertEqual(path("abcabba", "cbabac"), [(0, 0), (1, 0), (2, 0), (3, 1), (3, 2), (4, 3), (5, 4), (6, 4), (7, 5), (7, 6)]) |
2436 | + self.assertEqual(path("hello", "help me"), [(0, 0), (1, 1), (2, 2), (3, 3), (4, 3), (5, 3), (5, 4), (5, 5), (5, 6), (5, 7)]) |
2437 | + |
2438 | + def test_long(self): |
2439 | + self.assertEqual(path("hello", "night night"), |
2440 | + [(0, 0), (0, 1), (0, 2), (0, 3), (1, 4), (2, 4), |
2441 | + (3, 4), (4, 4), (5, 4), (5, 5), (5, 6), (5, 7), |
2442 | + (5, 8), (5, 9), (5, 10), (5, 11)]) |
2443 | + |
2444 | + self.assertEqual(path([ |
2445 | + "This part of the", |
2446 | + "document has stayed the", |
2447 | + "same from version to", |
2448 | + "version. It shouldn't", |
2449 | + "be shown if it doesn't", |
2450 | + "change. Otherwise, that", |
2451 | + "would not be helping to", |
2452 | + "compress the size of the", |
2453 | + "changes.", |
2454 | + "", |
2455 | + "This paragraph contains", |
2456 | + "text that is outdated.", |
2457 | + "It will be deleted in the", |
2458 | + "near future.", |
2459 | + "", |
2460 | + "It is important to spell", |
2461 | + "check this dokument. On", |
2462 | + "the other hand, a", |
2463 | + "misspelled word isn't", |
2464 | + "the end of the world.", |
2465 | + "Nothing in the rest of", |
2466 | + "this paragraph needs to", |
2467 | + "be changed. Things can", |
2468 | + "be added after it." |
2469 | + ], [ |
2470 | + "This is an important", |
2471 | + "notice! It should", |
2472 | + "therefore be located at", |
2473 | + "the beginning of this", |
2474 | + "document!", |
2475 | + "", |
2476 | + "This part of the", |
2477 | + "document has stayed the", |
2478 | + "same from version to", |
2479 | + "version. It shouldn't", |
2480 | + "be shown if it doesn't", |
2481 | + "change. Otherwise, that", |
2482 | + "would not be helping to", |
2483 | + "compress anything.", |
2484 | + "", |
2485 | + "It is important to spell", |
2486 | + "check this document. On", |
2487 | + "the other hand, a", |
2488 | + "misspelled word isn't", |
2489 | + "the end of the world.", |
2490 | + "Nothing in the rest of", |
2491 | + "this paragraph needs to", |
2492 | + "be changed. Things can", |
2493 | + "be added after it.", |
2494 | + "", |
2495 | + "This paragraph contains", |
2496 | + "important new additions", |
2497 | + "to this document.", |
2498 | + ]), [(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), |
2499 | + (0, 5), (0, 6), (1, 7), (2, 8), (3, 9), |
2500 | + (4, 10), (5, 11), (6, 12), (7, 13), (8, 13), |
2501 | + (9, 13), (9, 14), (10, 15), (11, 15), (12, 15), |
2502 | + (13, 15), (14, 15), (15, 15), (16, 16), (17, 16), |
2503 | + (17, 17), (18, 18), (19, 19), (20, 20), (21, 21), |
2504 | + (22, 22), (23, 23), (24, 24), (24, 25), (24, 26), |
2505 | + (24, 27), (24, 28)]) |
2506 | + |
2507 | +class __Test_diff(unittest.TestCase): |
2508 | + def test_zero(self): |
2509 | + p = path("", "") |
2510 | + |
2511 | + self.assertEqual(ses(p, ""), []) |
2512 | + |
2513 | + def test_delete(self): |
2514 | + p = path("a", "") |
2515 | + self.assertEqual(ses(p, ""), [("D", 0)]) |
2516 | + |
2517 | + p = path("abcd", "") |
2518 | + self.assertEqual(ses(p, ""), [("D", 0), ("D", 1), ("D", 2), ("D", 3)]) |
2519 | + |
2520 | + p = path("abcd", "cd") |
2521 | + self.assertEqual(ses(p, "cd"), [("D", 0), ("D", 1)]) |
2522 | + |
2523 | + p = path("abcd", "ab") |
2524 | + self.assertEqual(ses(p, "ab"), [("D", 2), ("D", 3)]) |
2525 | + |
2526 | + p = path("abcd", "bc") |
2527 | + self.assertEqual(ses(p, "bc"), [("D", 0), ("D", 3)]) |
2528 | + |
2529 | + def test_insert(self): |
2530 | + p = path("", "a") |
2531 | + self.assertEqual(ses(p, "a"), [("I", 0, "a")]) |
2532 | + |
2533 | + p = path("", "abcd") |
2534 | + self.assertEqual(ses(p, "abcd"), [("I", 0, "a"), ("I", 0, "b"), ("I", 0, "c"), ("I", 0, "d")]) |
2535 | + |
2536 | + def test_delins(self): |
2537 | + p = path("abcd", "abef") |
2538 | + self.assertEqual(ses(p, "abef"), [("D", 2), ("D", 3), ("I", 4, "e"), ("I", 4, "f")]) |
2539 | + |
2540 | +class __Test_patch(unittest.TestCase): |
2541 | + def do_patch(self, a, b): |
2542 | + self.assertEqual(patch(ses(path(a, b), b), a), b) |
2543 | + |
2544 | + def test_patch(self): |
2545 | + self.do_patch("", "hello") |
2546 | + self.do_patch("hello", "") |
2547 | + self.do_patch("hello", "hello") |
2548 | + self.do_patch("hello", "night night") |
2549 | + self.do_patch("hello", "help me") |
2550 | + self.do_patch("test bob", "kill bob") |
2551 | + self.do_patch("test bill", "test jane") |
2552 | + |
2553 | +if __name__ == "__main__": |
2554 | + unittest.main() |
2555 | |
2556 | === added file 'dxdiff/dxdiff/utils.py' |
2557 | --- dxdiff/dxdiff/utils.py 1970-01-01 00:00:00 +0000 |
2558 | +++ dxdiff/dxdiff/utils.py 2011-08-24 16:38:18 +0000 |
2559 | @@ -0,0 +1,172 @@ |
2560 | +#!/usr/bin/env python |
2561 | + |
2562 | +# This file is part of dxdiff. |
2563 | +# |
2564 | +# dxdiff is free software: you can redistribute it and/or modify |
2565 | +# it under the terms of the GNU General Public License as published by |
2566 | +# the Free Software Foundation, either version 3 of the License, or |
2567 | +# (at your option) any later version. |
2568 | +# |
2569 | +# dxdiff is distributed in the hope that it will be useful, |
2570 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2571 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2572 | +# GNU General Public License for more details. |
2573 | +# |
2574 | +# You should have received a copy of the GNU General Public License |
2575 | +# along with Diamond. If not, see <http://www.gnu.org/licenses/>. |
2576 | + |
2577 | +def flatten(l): |
2578 | + """ |
2579 | + Flattens a list of lists into a list. |
2580 | + """ |
2581 | + return [item for sublist in l for item in sublist] |
2582 | + |
2583 | +def nub(l, reverse=False): |
2584 | + """ |
2585 | + Removes duplicates from a list. |
2586 | + If reverse is true keeps the last duplicate item |
2587 | + as opposed to the first. |
2588 | + """ |
2589 | + if reverse: |
2590 | + seen = {} |
2591 | + result = [] |
2592 | + for item in reversed(l): |
2593 | + if item in seen: continue |
2594 | + seen[item] = 1 |
2595 | + result.append(item) |
2596 | + return reversed(result) |
2597 | + else: |
2598 | + seen = {} |
2599 | + result = [] |
2600 | + for item in l: |
2601 | + if item in seen: continue |
2602 | + seen[item] = 1 |
2603 | + result.append(item) |
2604 | + return result |
2605 | + |
2606 | +def partial(fn, *cargs, **ckwargs): |
2607 | + """ |
2608 | + Partial function application, taken from PEP 309. |
2609 | + """ |
2610 | + ckwargs = ckwargs.copy() |
2611 | + def call_fn(*fargs, **fkwargs): |
2612 | + d = ckwargs |
2613 | + d.update(fkwargs) |
2614 | + return fn(*(cargs + fargs), **d) |
2615 | + return call_fn |
2616 | + |
2617 | +def irange(*args): |
2618 | + """ |
2619 | + Similar to range but stop is an inclusive upper bound. |
2620 | + """ |
2621 | + if len(args) == 0: |
2622 | + raise TypeError("irange expected at least 1 arguments, got 0") |
2623 | + elif len(args) == 1: |
2624 | + stop = args[0] |
2625 | + start = 0 |
2626 | + step = 1 |
2627 | + elif len(args) == 2: |
2628 | + start, stop = args |
2629 | + step = 1 |
2630 | + elif len(args) == 3: |
2631 | + start, stop, step = args |
2632 | + else: |
2633 | + raise TypeError("irange expected at most 3 arguments, got " + str(len(args))) |
2634 | + |
2635 | + if step == 0: |
2636 | + raise ValueError("irange() step argument must not be zero") |
2637 | + |
2638 | + stop = stop + 1 if step > 0 else stop - 1 |
2639 | + return range(start, stop, step) |
2640 | + |
2641 | +################## |
2642 | +### Unit Tests ### |
2643 | +################## |
2644 | + |
2645 | +import unittest |
2646 | + |
2647 | +class __Test_flatten(unittest.TestCase): |
2648 | + def test_type(self): |
2649 | + self.assertRaises(TypeError, flatten, 1, 2, 3) |
2650 | + |
2651 | + def test_zero(self): |
2652 | + self.assertEqual(flatten([]), []) |
2653 | + self.assertEqual(flatten([[]]), []) |
2654 | + self.assertEqual(flatten([[], []]), []) |
2655 | + |
2656 | + def test_one(self): |
2657 | + self.assertEqual(flatten([[1]]), [1]) |
2658 | + self.assertEqual(flatten([[1, 2], [3, 4]]), [1, 2, 3, 4]) |
2659 | + |
2660 | + def test_two(self): |
2661 | + self.assertEqual(flatten([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]), [[1, 2], [3, 4], [5, 6], [7, 8]]) |
2662 | + |
2663 | +class __Test_nub(unittest.TestCase): |
2664 | + def test_zero(self): |
2665 | + self.assertEqual(nub([]), []) |
2666 | + |
2667 | + def test_nodups(self): |
2668 | + self.assertEqual(nub([1, 2, 3, 4]), [1, 2, 3, 4]) |
2669 | + |
2670 | + def test_dups(self): |
2671 | + self.assertEqual(nub([1, 1, 2, 3, 4, 2]), [1, 2, 3, 4]) |
2672 | + |
2673 | +class __Test_partial(unittest.TestCase): |
2674 | + def printer(*args, **kargs): |
2675 | + result = [] |
2676 | + for arg in args: |
2677 | + result.append(str(args)) |
2678 | + for k, v in kargs.items(): |
2679 | + result.append(str(k) + ": " + str(v)) |
2680 | + return ' '.join(result) |
2681 | + |
2682 | + def test_zero(self): |
2683 | + self.assertEqual(partial(self.printer)(), self.printer()) |
2684 | + |
2685 | + def test_args(self): |
2686 | + self.assertEqual(partial(self.printer, 1)(), self.printer(1)) |
2687 | + self.assertEqual(partial(self.printer)(1), self.printer(1)) |
2688 | + |
2689 | + def test_kargs(self): |
2690 | + self.assertEqual(partial(self.printer, a = 0)(), self.printer(a = 0)) |
2691 | + self.assertEqual(partial(self.printer)(a = 0), self.printer(a = 0)) |
2692 | + |
2693 | +class __Test_irange(unittest.TestCase): |
2694 | + def test_type(self): |
2695 | + self.assertRaises(TypeError, irange) |
2696 | + self.assertRaises(TypeError, irange, 0, 1, 2, 3) |
2697 | + self.assertRaises(TypeError, irange, 0, 1, 2, 3, 4) |
2698 | + |
2699 | + def test_zerostep(self): |
2700 | + self.assertRaises(ValueError, irange, 0, 0, 0) |
2701 | + self.assertRaises(ValueError, irange, 1, 2, 0) |
2702 | + |
2703 | + def test_zero(self): |
2704 | + self.assertEqual(irange(0), [0]) |
2705 | + |
2706 | + def test_stop(self): |
2707 | + self.assertEqual(irange(1), [0, 1]) |
2708 | + self.assertEqual(irange(2), [0, 1, 2]) |
2709 | + |
2710 | + def test_start(self): |
2711 | + self.assertEqual(irange(5, 10), [5, 6, 7, 8, 9, 10]) |
2712 | + |
2713 | + def test_step(self): |
2714 | + self.assertEqual(irange(0, 4, 2), [0, 2, 4]) |
2715 | + self.assertEqual(irange(0, 3, 2), [0, 2]) |
2716 | + |
2717 | + def test_negative(self): |
2718 | + self.assertEqual(irange(-10, -5), [-10, -9, -8, -7, -6, -5]) |
2719 | + self.assertEqual(irange(-5, 5), [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]) |
2720 | + |
2721 | + def test_negstep(self): |
2722 | + self.assertEqual(irange(5, 0, -1), [5, 4, 3, 2, 1, 0]) |
2723 | + self.assertEqual(irange(2, -2, -1), [2, 1, 0, -1, -2]) |
2724 | + |
2725 | + def test_norange(self): |
2726 | + self.assertEqual(irange(5, 0), []) |
2727 | + self.assertEqual(irange(1, -1), []) |
2728 | + self.assertEqual(irange(0, 5, -1), []) |
2729 | + |
2730 | +if __name__ == "__main__": |
2731 | + unittest.main() |
2732 | |
2733 | === added file 'dxdiff/setup.py' |
2734 | --- dxdiff/setup.py 1970-01-01 00:00:00 +0000 |
2735 | +++ dxdiff/setup.py 2011-08-24 16:38:18 +0000 |
2736 | @@ -0,0 +1,21 @@ |
2737 | +from distutils.core import setup |
2738 | +import os |
2739 | +import os.path |
2740 | +import glob |
2741 | + |
2742 | +try: |
2743 | + destdir = os.environ["DESTDIR"] |
2744 | +except KeyError: |
2745 | + destdir = "" |
2746 | + |
2747 | +setup( |
2748 | + name='dxdiff', |
2749 | + version='1.0', |
2750 | + description="An XML aware diff tool.", |
2751 | + author = "The ICOM team", |
2752 | + author_email = "fraser.waters08@imperial.ac.uk", |
2753 | + url = "http://amcg.ese.ic.ac.uk", |
2754 | + packages = ['dxdiff'], |
2755 | + scripts=["dxdiff/dxdiff"], |
2756 | + ) |
2757 | + |
2758 | |
2759 | === modified file 'examples/show_ballistics' (properties changed: +x to -x) |
2760 | === modified file 'install-sh' (properties changed: +x to -x) |
[pef@caoimhe: /tmp/xmldiff/ diamond] $ python bin/diamond diamond/ bin/../ diamond/ interface. py", line 50, in <module>
Traceback (most recent call last):
File "bin/diamond", line 228, in <module>
main()
File "bin/diamond", line 92, in main
import diamond.interface as interface
File "/tmp/xmldiff/
import diffview
ImportError: No module named diffview
Did you forget to bzr add?