Skip to content

Commit

Permalink
Fix parsing for calendar names containing '#' (#761)
Browse files Browse the repository at this point in the history
  • Loading branch information
dbarnett authored Sep 17, 2024
1 parent 8c0ef31 commit 0b89828
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 24 deletions.
50 changes: 36 additions & 14 deletions gcalcli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
# Everything you need to know (Google API Calendar v3): http://goo.gl/HfTGQ #
# #
# ######################################################################### #
from argparse import ArgumentTypeError
import json
import os
import pathlib
import re
import signal
import sys
from collections import namedtuple
Expand All @@ -45,23 +47,43 @@
"""


def parse_cal_names(cal_names):
def rsplit_unescaped_hash(string):
# Use regex to find parts before/after last unescaped hash separator.
# Sadly, all the "proper solutions" are even more questionable:
# https://stackoverflow.com/questions/4020539/process-escape-sequences
match = re.match(
r"""(?x)
^((?:\\.|[^\\])*)
[#]
((?:\\.|[^#\\])*)$
""",
string
)
if not match:
return (string, None)
# Unescape and return (part1, part2)
return tuple(re.sub(r'\\(.)', r'\1', p)
for p in match.group(1, 2))


def parse_cal_names(cal_names: list[str], printer: Printer):
cal_colors = {}
for name in cal_names:
cal_color = 'default'
parts = name.split('#')
parts_count = len(parts)
if parts_count >= 1:
cal_name = parts[0]

if len(parts) == 2:
cal_color = valid_color_name(parts[1])

if len(parts) > 2:
raise ValueError('Cannot parse calendar name: "%s"' % name)
p1, p2 = rsplit_unescaped_hash(name)
if p2 is not None:
try:
name, cal_color = p1, valid_color_name(p2)
except ArgumentTypeError:
printer.debug_msg(
f'Using entire name {name!r} as cal name.\n'
f'Change {p1!r} to a valid color name if intended to be a '
'color (or otherwise consider escaping "#" chars to "\\#").'
'\n'
)

cal_colors[cal_name] = cal_color
return [CalName(name=k, color=cal_colors[k]) for k in cal_colors.keys()]
cal_colors[name] = cal_color
return [CalName(name=k, color=v) for k, v in cal_colors.items()]


def run_add_prompt(parsed_args, printer):
Expand Down Expand Up @@ -376,7 +398,7 @@ def set_resolved_calendars(parsed_args, printer: Printer) -> list[str]:
'calendars may be coming from gcalclirc.\n'
)

cal_names = parse_cal_names(parsed_args.calendars)
cal_names = parse_cal_names(parsed_args.calendars, printer=printer)
# Only ignore calendars if they're not explicitly in --calendar list.
parsed_args.ignore_calendars[:] = [
c
Expand Down
22 changes: 12 additions & 10 deletions tests/test_gcalcli.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def test_cal_query(capsys, PatchedGCalI):


def test_add_event(PatchedGCalI):
cal_names = parse_cal_names(['[email protected]'])
cal_names = parse_cal_names(['[email protected]'], printer=None)
gcal = PatchedGCalI(
cal_names=cal_names, allday=False, default_reminders=True)
assert gcal.AddEvent(title='test event',
Expand All @@ -109,7 +109,8 @@ def test_add_event(PatchedGCalI):


def test_add_event_with_cal_prompt(PatchedGCalI, capsys, monkeypatch):
cal_names = parse_cal_names(['[email protected]', '[email protected]'])
cal_names = parse_cal_names(
['[email protected]', '[email protected]'], None)
gcal = PatchedGCalI(
cal_names=cal_names, allday=False, default_reminders=True)
# Fake selecting calendar 0 at the prompt
Expand All @@ -131,7 +132,7 @@ def test_add_event_with_cal_prompt(PatchedGCalI, capsys, monkeypatch):
def test_add_event_override_color(capsys, default_options,
PatchedGCalIForEvents):
default_options.update({'override_color': True})
cal_names = parse_cal_names(['[email protected]'])
cal_names = parse_cal_names(['[email protected]'], None)
gcal = PatchedGCalIForEvents(cal_names=cal_names, **default_options)
gcal.AgendaQuery()
captured = capsys.readouterr()
Expand All @@ -141,15 +142,16 @@ def test_add_event_override_color(capsys, default_options,


def test_quick_add(PatchedGCalI):
cal_names = parse_cal_names(['[email protected]'])
cal_names = parse_cal_names(['[email protected]'], None)
gcal = PatchedGCalI(cal_names=cal_names)
assert gcal.QuickAddEvent(
event_text='quick test event',
reminders=['5m sms'])


def test_quick_add_with_cal_prompt(PatchedGCalI, capsys, monkeypatch):
cal_names = parse_cal_names(['[email protected]', '[email protected]'])
cal_names = parse_cal_names(
['[email protected]', '[email protected]'], None)
gcal = PatchedGCalI(cal_names=cal_names)
# Fake selecting calendar 0 at the prompt
monkeypatch.setattr('sys.stdin', io.StringIO('0\n'))
Expand Down Expand Up @@ -271,14 +273,14 @@ def test_modify_event(PatchedGCalI):


def test_import(PatchedGCalI):
cal_names = parse_cal_names(['[email protected]'])
cal_names = parse_cal_names(['[email protected]'], None)
gcal = PatchedGCalI(cal_names=cal_names, default_reminders=True)
vcal_path = TEST_DATA_DIR + '/vv.txt'
assert gcal.ImportICS(icsFile=open(vcal_path, errors='replace'))


def test_legacy_import(PatchedGCalI):
cal_names = parse_cal_names(['[email protected]'])
cal_names = parse_cal_names(['[email protected]'], None)
gcal = PatchedGCalI(
cal_names=cal_names, default_reminders=True, use_legacy_import=True)
vcal_path = TEST_DATA_DIR + '/vv.txt'
Expand Down Expand Up @@ -323,15 +325,15 @@ def test_parse_cal_names(PatchedGCalI):
# and then assert the right number of events
# for the moment, we assert 0 (which indicates successful completion of
# the code path, but no events printed)
cal_names = parse_cal_names(['j*#green'])
cal_names = parse_cal_names(['j*#green'], None)
gcal = PatchedGCalI(cal_names=cal_names)
assert gcal.AgendaQuery() == 0

cal_names = parse_cal_names(['j*'])
cal_names = parse_cal_names(['j*'], None)
gcal = PatchedGCalI(cal_names=cal_names)
assert gcal.AgendaQuery() == 0

cal_names = parse_cal_names(['[email protected]'])
cal_names = parse_cal_names(['[email protected]'], None)
gcal = PatchedGCalI(cal_names=cal_names)
assert gcal.AgendaQuery() == 0

Expand Down

0 comments on commit 0b89828

Please sign in to comment.