/usr/share/pyshared/vobject/ics_diff.py is in python-vobject 0.8.1c-4ubuntu1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 | """Compare VTODOs and VEVENTs in two iCalendar sources."""
from base import Component, getBehavior, newFromBehavior
def getSortKey(component):
def getUID(component):
return component.getChildValue('uid', '')
# it's not quite as simple as getUID, need to account for recurrenceID and
# sequence
def getSequence(component):
sequence = component.getChildValue('sequence', 0)
return "%05d" % int(sequence)
def getRecurrenceID(component):
recurrence_id = component.getChildValue('recurrence_id', None)
if recurrence_id is None:
return '0000-00-00'
else:
return recurrence_id.isoformat()
return getUID(component) + getSequence(component) + getRecurrenceID(component)
def sortByUID(components):
return sorted(components, key=getSortKey)
def deleteExtraneous(component, ignore_dtstamp=False):
"""
Recursively walk the component's children, deleting extraneous details like
X-VOBJ-ORIGINAL-TZID.
"""
for comp in component.components():
deleteExtraneous(comp, ignore_dtstamp)
for line in component.lines():
if line.params.has_key('X-VOBJ-ORIGINAL-TZID'):
del line.params['X-VOBJ-ORIGINAL-TZID']
if ignore_dtstamp and hasattr(component, 'dtstamp_list'):
del component.dtstamp_list
def diff(left, right):
"""
Take two VCALENDAR components, compare VEVENTs and VTODOs in them,
return a list of object pairs containing just UID and the bits
that didn't match, using None for objects that weren't present in one
version or the other.
When there are multiple ContentLines in one VEVENT, for instance many
DESCRIPTION lines, such lines original order is assumed to be
meaningful. Order is also preserved when comparing (the unlikely case
of) multiple parameters of the same type in a ContentLine
"""
def processComponentLists(leftList, rightList):
output = []
rightIndex = 0
rightListSize = len(rightList)
for comp in leftList:
if rightIndex >= rightListSize:
output.append((comp, None))
else:
leftKey = getSortKey(comp)
rightComp = rightList[rightIndex]
rightKey = getSortKey(rightComp)
while leftKey > rightKey:
output.append((None, rightComp))
rightIndex += 1
if rightIndex >= rightListSize:
output.append((comp, None))
break
else:
rightComp = rightList[rightIndex]
rightKey = getSortKey(rightComp)
if leftKey < rightKey:
output.append((comp, None))
elif leftKey == rightKey:
rightIndex += 1
matchResult = processComponentPair(comp, rightComp)
if matchResult is not None:
output.append(matchResult)
return output
def newComponent(name, body):
if body is None:
return None
else:
c = Component(name)
c.behavior = getBehavior(name)
c.isNative = True
return c
def processComponentPair(leftComp, rightComp):
"""
Return None if a match, or a pair of components including UIDs and
any differing children.
"""
leftChildKeys = leftComp.contents.keys()
rightChildKeys = rightComp.contents.keys()
differentContentLines = []
differentComponents = {}
for key in leftChildKeys:
rightList = rightComp.contents.get(key, [])
if isinstance(leftComp.contents[key][0], Component):
compDifference = processComponentLists(leftComp.contents[key],
rightList)
if len(compDifference) > 0:
differentComponents[key] = compDifference
elif leftComp.contents[key] != rightList:
differentContentLines.append((leftComp.contents[key],
rightList))
for key in rightChildKeys:
if key not in leftChildKeys:
if isinstance(rightComp.contents[key][0], Component):
differentComponents[key] = ([], rightComp.contents[key])
else:
differentContentLines.append(([], rightComp.contents[key]))
if len(differentContentLines) == 0 and len(differentComponents) == 0:
return None
else:
left = newFromBehavior(leftComp.name)
right = newFromBehavior(leftComp.name)
# add a UID, if one existed, despite the fact that they'll always be
# the same
uid = leftComp.getChildValue('uid')
if uid is not None:
left.add( 'uid').value = uid
right.add('uid').value = uid
for name, childPairList in differentComponents.iteritems():
leftComponents, rightComponents = zip(*childPairList)
if len(leftComponents) > 0:
# filter out None
left.contents[name] = filter(None, leftComponents)
if len(rightComponents) > 0:
# filter out None
right.contents[name] = filter(None, rightComponents)
for leftChildLine, rightChildLine in differentContentLines:
nonEmpty = leftChildLine or rightChildLine
name = nonEmpty[0].name
if leftChildLine is not None:
left.contents[name] = leftChildLine
if rightChildLine is not None:
right.contents[name] = rightChildLine
return left, right
vevents = processComponentLists(sortByUID(getattr(left, 'vevent_list', [])),
sortByUID(getattr(right, 'vevent_list', [])))
vtodos = processComponentLists(sortByUID(getattr(left, 'vtodo_list', [])),
sortByUID(getattr(right, 'vtodo_list', [])))
return vevents + vtodos
def prettyDiff(leftObj, rightObj):
for left, right in diff(leftObj, rightObj):
print "<<<<<<<<<<<<<<<"
if left is not None:
left.prettyPrint()
print "==============="
if right is not None:
right.prettyPrint()
print ">>>>>>>>>>>>>>>"
print
from optparse import OptionParser
import icalendar, base
import os
import codecs
def main():
options, args = getOptions()
if args:
ignore_dtstamp = options.ignore
ics_file1, ics_file2 = args
cal1 = base.readOne(file(ics_file1))
cal2 = base.readOne(file(ics_file2))
deleteExtraneous(cal1, ignore_dtstamp=ignore_dtstamp)
deleteExtraneous(cal2, ignore_dtstamp=ignore_dtstamp)
prettyDiff(cal1, cal2)
version = "0.1"
def getOptions():
##### Configuration options #####
usage = "usage: %prog [options] ics_file1 ics_file2"
parser = OptionParser(usage=usage, version=version)
parser.set_description("ics_diff will print a comparison of two iCalendar files ")
parser.add_option("-i", "--ignore-dtstamp", dest="ignore", action="store_true",
default=False, help="ignore DTSTAMP lines [default: False]")
(cmdline_options, args) = parser.parse_args()
if len(args) < 2:
print "error: too few arguments given"
print
print parser.format_help()
return False, False
return cmdline_options, args
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print "Aborted"
|