Skip to content

Commit

Permalink
ics_diff converted
Browse files Browse the repository at this point in the history
  • Loading branch information
rsb-23 committed Feb 17, 2025
1 parent e2403a2 commit 47e34b5
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 112 deletions.
70 changes: 35 additions & 35 deletions programmers-guide/make.bat
Original file line number Diff line number Diff line change
@@ -1,35 +1,35 @@
@ECHO OFF

pushd %~dp0

REM Command file for Sphinx documentation

if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build

%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.https://www.sphinx-doc.org/
exit /b 1
)

if "%1" == "" goto help

%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end

:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%

:end
popd
@ECHO OFF

pushd %~dp0

REM Command file for Sphinx documentation

if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build

%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.https://www.sphinx-doc.org/
exit /b 1
)

if "%1" == "" goto help

%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end

:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%

:end
popd
11 changes: 11 additions & 0 deletions vobject/compatibility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import warnings
from functools import wraps


def deprecated(func):
@wraps(func)
def wrapper(*args, **kwargs):
warnings.warn(f"{func.__name__} is deprecated, use snake_case instead", DeprecationWarning)
return func(*args, **kwargs)

return wrapper
176 changes: 99 additions & 77 deletions vobject/ics_diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,40 @@

import vobject

from .compatibility import deprecated

def getSortKey(component):
def getUID(component):

def get_sort_key(component):
def get_uid(component):
return component.getChildValue("uid", "")

# it's not quite as simple as getUID, need to account for recurrenceID and sequence

def getSequence(component) -> str:
def get_sequence(component) -> str:
sequence = component.getChildValue("sequence", 0)
return f"{int(sequence):05d}"

def getRecurrenceID(component):
def get_recurrence_id(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)
return get_uid(component) + get_sequence(component) + get_recurrence_id(component)


def sortByUID(components):
return sorted(components, key=getSortKey)
def sort_by_uid(components):
return sorted(components, key=get_sort_key)


def deleteExtraneous(component, ignore_dtstamp=False):
def delete_extraneous(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)
delete_extraneous(comp, ignore_dtstamp)
for line in component.lines():
if "X-VOBJ-ORIGINAL-TZID" in line.params:
del line.params["X-VOBJ-ORIGINAL-TZID"]
Expand All @@ -59,118 +61,118 @@ def diff(left, right):
"""

def processComponentLists(leftList, rightList):
def process_component_lists(left_list, right_list):
output = []
rightIndex = 0
rightListSize = len(rightList)
right_index = 0
right_list_size = len(right_list)

for comp in leftList:
if rightIndex >= rightListSize:
for comp in left_list:
if right_index >= right_list_size:
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:
left_key = get_sort_key(comp)
right_comp = right_list[right_index]
right_key = get_sort_key(right_comp)
while left_key > right_key:
output.append((None, right_comp))
right_index += 1
if right_index >= right_list_size:
output.append((comp, None))
break
rightComp = rightList[rightIndex]
rightKey = getSortKey(rightComp)
right_comp = right_list[right_index]
right_key = get_sort_key(right_comp)

if leftKey < rightKey:
if left_key < right_key:
output.append((comp, None))
elif leftKey == rightKey:
rightIndex += 1
matchResult = processComponentPair(comp, rightComp)
if matchResult is not None:
output.append(matchResult)
elif left_key == right_key:
right_index += 1
match_result = process_component_pair(comp, right_comp)
if match_result is not None:
output.append(match_result)

return output

def newComponent(name, body): # pylint:disable=unused-variable
def new_component(name, body): # pylint:disable=unused-variable
if body is None:
return None
c = vobject.base.Component(name)
c.behavior = vobject.base.getBehavior(name)
c.isNative = True
return c

def processComponentPair(leftComp, rightComp):
def process_component_pair(left_comp, right_comp):
"""
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], vobject.base.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], vobject.base.Component):
differentComponents[key] = ([], rightComp.contents[key])
left_child_keys = left_comp.contents.keys()
right_child_keys = right_comp.contents.keys()

different_content_lines = []
different_components = {}

for key in left_child_keys:
right_list = right_comp.contents.get(key, [])
if isinstance(left_comp.contents[key][0], vobject.base.Component):
comp_difference = process_component_lists(left_comp.contents[key], right_list)
if len(comp_difference) > 0:
different_components[key] = comp_difference

elif left_comp.contents[key] != right_list:
different_content_lines.append((left_comp.contents[key], right_list))

for key in right_child_keys:
if key not in left_child_keys:
if isinstance(right_comp.contents[key][0], vobject.base.Component):
different_components[key] = ([], right_comp.contents[key])
else:
differentContentLines.append(([], rightComp.contents[key]))
different_content_lines.append(([], right_comp.contents[key]))

if not differentContentLines and not differentComponents:
if not different_content_lines and not different_components:
return None

left = vobject.newFromBehavior(leftComp.name)
right = vobject.newFromBehavior(leftComp.name)
left = vobject.newFromBehavior(left_comp.name)
right = vobject.newFromBehavior(left_comp.name)
# add a UID, if one existed, despite the fact that they'll always be
# the same
uid = leftComp.getChildValue("uid")
uid = left_comp.getChildValue("uid")
if uid is not None:
left.add("uid").value = uid
right.add("uid").value = uid

for name, childPairList in differentComponents.items():
leftComponents, rightComponents = zip(*childPairList)
if len(leftComponents) > 0:
for name, child_pair_list in different_components.items():
left_components, right_components = zip(*child_pair_list)
if len(left_components) > 0:
# filter out None
left.contents[name] = filter(None, leftComponents)
if len(rightComponents) > 0:
left.contents[name] = filter(None, left_components)
if len(right_components) > 0:
# filter out None
right.contents[name] = filter(None, rightComponents)
right.contents[name] = filter(None, right_components)

for leftChildLine, rightChildLine in differentContentLines:
nonEmpty = leftChildLine or rightChildLine
for left_child_line, right_child_line in different_content_lines:
nonEmpty = left_child_line or right_child_line
name = nonEmpty[0].name
if leftChildLine is not None:
left.contents[name] = leftChildLine
if rightChildLine is not None:
right.contents[name] = rightChildLine
if left_child_line is not None:
left.contents[name] = left_child_line
if right_child_line is not None:
right.contents[name] = right_child_line

return left, right

vevents = processComponentLists(
sortByUID(getattr(left, "vevent_list", [])), sortByUID(getattr(right, "vevent_list", []))
vevents = process_component_lists(
sort_by_uid(getattr(left, "vevent_list", [])), sort_by_uid(getattr(right, "vevent_list", []))
)

vtodos = processComponentLists(
sortByUID(getattr(left, "vtodo_list", [])), sortByUID(getattr(right, "vtodo_list", []))
vtodos = process_component_lists(
sort_by_uid(getattr(left, "vtodo_list", [])), sort_by_uid(getattr(right, "vtodo_list", []))
)

return vevents + vtodos


def prettyDiff(leftObj, rightObj):
for left, right in diff(leftObj, rightObj):
def pretty_diff(left_obj, right_obj):
for left, right in diff(left_obj, right_obj):
print("<<<<<<<<<<<<<<<")
if left is not None:
left.prettyPrint()
Expand All @@ -185,9 +187,9 @@ def main():
with open(args.ics_file1) as f, open(args.ics_file2) as g:
cal1 = vobject.readOne(f)
cal2 = vobject.readOne(g)
deleteExtraneous(cal1, ignore_dtstamp=args.ignore)
deleteExtraneous(cal2, ignore_dtstamp=args.ignore)
prettyDiff(cal1, cal2)
delete_extraneous(cal1, ignore_dtstamp=args.ignore)
delete_extraneous(cal2, ignore_dtstamp=args.ignore)
pretty_diff(cal1, cal2)


def get_arguments():
Expand All @@ -208,6 +210,26 @@ def get_arguments():
return parser.parse_args()


@deprecated
def getSortKey(component):
return get_sort_key(component)


@deprecated
def sortByUID(components):
return sort_by_uid(components)


@deprecated
def deleteExtraneous(component, ignore_dtstamp=False):
return delete_extraneous(component, ignore_dtstamp)


@deprecated
def prettyDiff(leftObj, rightObj):
return pretty_diff(leftObj, rightObj)


if __name__ == "__main__":
try:
main()
Expand Down

0 comments on commit 47e34b5

Please sign in to comment.