add proxy for the first part and try to develop the second part

This commit is contained in:
Pierre Geeraert
2025-12-03 12:08:56 +00:00
parent 6c34d510e5
commit 460f18d85a
1095 changed files with 358799 additions and 5 deletions
@@ -0,0 +1,65 @@
import warnings
from ics.attendee import Attendee
from ics.parsers.parser import Parser, option
from ics.utils import iso_to_arrow, parse_duration, unescape_string
class BaseAlarmParser(Parser):
@option(required=True)
def parse_trigger(alarm, line):
if line.params.get("VALUE", [""])[0] == "DATE-TIME":
alarm.trigger = iso_to_arrow(line)
elif line.params.get("VALUE", ["DURATION"])[0] == "DURATION":
alarm.trigger = parse_duration(line.value)
else:
warnings.warn(
"ics.py encountered a TRIGGER of unknown type '%s'. It has been ignored."
% line.params["VALUE"][0]
)
def parse_duration(alarm, line):
if line:
alarm._duration = parse_duration(line.value)
def parse_repeat(alarm, line):
if line:
alarm._repeat = int(line.value)
class CustomAlarmParser(BaseAlarmParser):
def parse_action(alarm, line, required=True):
print(line)
if line:
alarm._action = line.value
class AudioAlarmParser(BaseAlarmParser):
def parse_attach(alarm, line):
if line:
alarm._sound = line
class DisplayAlarmParser(BaseAlarmParser):
@option(required=True)
def parse_description(alarm, line):
alarm.display_text = unescape_string(line.value) if line else None
class EmailAlarmParser(BaseAlarmParser):
@option(required=True)
def parse_description(alarm, line):
alarm.body = unescape_string(line.value) if line else None
@option(required=True)
def parse_summary(alarm, line):
alarm.subject = unescape_string(line.value) if line else None
@option(required=True, multiple=True)
def parse_attendee(alarm, lines):
for line in lines:
alarm.recipients.append(Attendee.parse(line))
class NoneAlarmParser(BaseAlarmParser):
pass
@@ -0,0 +1,34 @@
from ics.parsers.parser import Parser
from ics.utils import unescape_string
class PersonParser(Parser):
def parse_cn(person: "Person", value):
if value:
person.common_name = unescape_string(value)
def parse_dir(person: "Person", value):
if value:
person.dir = unescape_string(value)
def parse_sent_by(person: "Person", value):
if value:
person.sent_by = unescape_string(value)
class AttendeeParser(PersonParser):
def parse_rsvp(attendee: "Attendee", value):
if value:
attendee.rsvp = bool(value)
def parse_role(attendee: "Attendee", value):
if value:
attendee.role = unescape_string(value)
def parse_partstat(attendee: "Attendee", value):
if value:
attendee.partstat = unescape_string(value)
def parse_cutype(attendee: "Attendee", value):
if value:
attendee.cutype = unescape_string(value)
@@ -0,0 +1,98 @@
import re
from ics import Organizer, Attendee
from ics.alarm.utils import get_type_from_container
from ics.parsers.parser import Parser, option
from ics.utils import (iso_precision, iso_to_arrow, parse_duration,
unescape_string)
class EventParser(Parser):
def parse_dtstamp(event, line):
if line:
# get the dict of vtimezones passed to the classmethod
tz_dict = event._classmethod_kwargs["tz"]
event.created = iso_to_arrow(line, tz_dict)
def parse_last_modified(event, line):
if line:
tz_dict = event._classmethod_kwargs["tz"]
event.last_modified = iso_to_arrow(line, tz_dict)
def parse_dtstart(event, line):
if line:
# get the dict of vtimezones passed to the classmethod
tz_dict = event._classmethod_kwargs["tz"]
event.begin = iso_to_arrow(line, tz_dict)
event._begin_precision = iso_precision(line.value)
def parse_duration(event, line):
if line:
# TODO: DRY [1]
if event._end_time: # pragma: no cover
raise ValueError("An event can't have both DTEND and DURATION")
event._duration = parse_duration(line.value)
def parse_dtend(event, line):
if line:
# TODO: DRY [1]
if event._duration:
raise ValueError("An event can't have both DTEND and DURATION")
# get the dict of vtimezones passed to the classmethod
tz_dict = event._classmethod_kwargs["tz"]
event._end_time = iso_to_arrow(line, tz_dict)
# one could also save the end_precision to check that if begin_precision is day, end_precision also is
def parse_summary(event, line):
event.name = unescape_string(line.value) if line else None
def parse_organizer(event, line):
event.organizer = Organizer.parse(line) if line else None
@option(multiple=True)
def parse_attendee(event, lines):
for line in lines:
event.attendees.add(Attendee.parse(line))
def parse_description(event, line):
event.description = unescape_string(line.value) if line else None
def parse_location(event, line):
event.location = unescape_string(line.value) if line else None
def parse_geo(event, line):
if line:
latitude, _, longitude = unescape_string(line.value).partition(";")
event.geo = float(latitude), float(longitude)
def parse_url(event, line):
event.url = unescape_string(line.value) if line else None
def parse_transp(event, line):
if line and line.value in ["TRANSPARENT", "OPAQUE"]:
event.transparent = line.value == "TRANSPARENT"
# TODO : make uid required ?
# TODO : add option somewhere to ignore some errors
def parse_uid(event, line):
if line:
event.uid = line.value
@option(multiple=True)
def parse_valarm(event, lines):
event.alarms = [get_type_from_container(x)._from_container(x) for x in lines]
def parse_status(event, line):
if line:
event.status = line.value
def parse_class(event, line):
if line:
event.classification = line.value
def parse_categories(event, line):
event.categories = set()
if line:
# In the regular expression: Only match unquoted commas.
for cat in re.split("(?<!\\\\),", line.value):
event.categories.update({unescape_string(cat)})
@@ -0,0 +1,80 @@
from dateutil.tz import tzical
from six import StringIO
from ics.event import Event
from ics.grammar.parse import ContentLine
from ics.parsers.parser import Parser, option
from ics.todo import Todo
from ics.utils import remove_sequence, remove_x
class CalendarParser(Parser):
@option(required=True)
def parse_prodid(calendar, prodid):
calendar._creator = prodid.value
_version_default = [ContentLine(name="VERSION", value="2.0")]
@option(required=True, default=_version_default)
def parse_version(calendar, line):
version = line
# TODO : should take care of minver/maxver
if ";" in version.value:
_, calendar.version = version.value.split(";")
else:
calendar.version = version.value
def parse_calscale(calendar, line):
calscale = line
if calscale:
calendar.scale = calscale.value.lower()
calendar.scale_params = calscale.params
else:
calendar.scale = "georgian"
calendar.scale_params = {}
def parse_method(calendar, line):
method = line
if method:
calendar.method = method.value
calendar.method_params = method.params
else:
calendar.method = None
calendar.method_params = {}
@option(multiple=True)
def parse_vtimezone(calendar, vtimezones):
"""Receives a list of VTIMEZONE blocks.
Parses them and adds them to calendar._timezones.
"""
for vtimezone in vtimezones:
remove_x(vtimezone) # Remove non standard lines from the block
remove_sequence(
vtimezone
) # Remove SEQUENCE lines because tzical does not understand them
fake_file = StringIO()
fake_file.write(str(vtimezone)) # Represent the block as a string
fake_file.seek(0)
timezones = tzical(fake_file) # tzical does not like strings
# timezones is a tzical object and could contain multiple timezones
for key in timezones.keys():
calendar._timezones[key] = timezones.get(key)
@option(multiple=True)
def parse_vevent(calendar, lines):
# tz=calendar._timezones gives access to the event factory to the
# timezones list
def event_factory(x):
return Event._from_container(x, tz=calendar._timezones)
calendar.events = set(map(event_factory, lines))
@option(multiple=True)
def parse_vtodo(calendar, lines):
# tz=calendar._timezones gives access to the event factory to the
# timezones list
def todo_factory(x):
return Todo._from_container(x, tz=calendar._timezones)
calendar.todos = set(map(todo_factory, lines))
@@ -0,0 +1,45 @@
from typing import Callable, Dict, List, NamedTuple, Optional, Tuple
from ics.grammar.parse import ContentLine
class ParserOption(NamedTuple):
required: bool = False
multiple: bool = False
default: Optional[List[ContentLine]] = None
class Parser:
@classmethod
def get_parsers(cls) -> Dict[str, Tuple[Callable, ParserOption]]:
methods = [
(method_name, getattr(cls, method_name))
for method_name in dir(cls)
if callable(getattr(cls, method_name))
]
parsers = [
(method_name, method_callable)
for (method_name, method_callable) in methods
if method_name.startswith("parse_")
]
return {
method_name.split("_", 1)[1]
.upper()
.replace("_", "-"): (
method_callable,
getattr(method_callable, "options", ParserOption()),
)
for (method_name, method_callable) in parsers
}
def option(
required: bool = False,
multiple: bool = False,
default: Optional[List[ContentLine]] = None,
) -> Callable:
def decorator(fn):
fn.options = ParserOption(required, multiple, default)
return fn
return decorator
@@ -0,0 +1,83 @@
from typing import TYPE_CHECKING, List
from ics.alarm.utils import get_type_from_container
from ics.grammar.parse import ContentLine
from ics.parsers.parser import Parser, option
from ics.utils import iso_to_arrow, parse_duration, unescape_string
if TYPE_CHECKING:
from .todo_parser import Todo
class TodoParser(Parser):
@option(required=True)
def parse_dtstamp(todo: "Todo", line: ContentLine):
if line:
# get the dict of vtimezones passed to the classmethod
tz_dict = todo._classmethod_kwargs["tz"]
todo.dtstamp = iso_to_arrow(line, tz_dict)
@option(required=True)
def parse_uid(todo: "Todo", line: ContentLine):
if line:
todo.uid = line.value
def parse_completed(todo: "Todo", line: ContentLine):
if line:
# get the dict of vtimezones passed to the classmethod
tz_dict = todo._classmethod_kwargs["tz"]
todo.completed = iso_to_arrow(line, tz_dict)
def parse_created(todo: "Todo", line: ContentLine):
if line:
# get the dict of vtimezones passed to the classmethod
tz_dict = todo._classmethod_kwargs["tz"]
todo.created = iso_to_arrow(line, tz_dict)
def parse_description(todo: "Todo", line: ContentLine):
todo.description = unescape_string(line.value) if line else None
def parse_dtstart(todo: "Todo", line: ContentLine):
if line:
# get the dict of vtimezones passed to the classmethod
tz_dict = todo._classmethod_kwargs["tz"]
todo.begin = iso_to_arrow(line, tz_dict)
def parse_location(todo: "Todo", line: ContentLine):
todo.location = unescape_string(line.value) if line else None
def parse_percent_complete(todo: "Todo", line: ContentLine):
todo.percent = int(line.value) if line else None
def parse_priority(todo: "Todo", line: ContentLine):
todo.priority = int(line.value) if line else None
def parse_summary(todo: "Todo", line: ContentLine):
todo.name = unescape_string(line.value) if line else None
def parse_url(todo: "Todo", line: ContentLine):
todo.url = unescape_string(line.value) if line else None
def parse_due(todo: "Todo", line: ContentLine):
if line:
# TODO: DRY [1]
if todo._duration:
raise ValueError("A todo can't have both DUE and DURATION")
# get the dict of vtimezones passed to the classmethod
tz_dict = todo._classmethod_kwargs["tz"]
todo._due_time = iso_to_arrow(line, tz_dict)
def parse_duration(todo: "Todo", line: ContentLine):
if line:
# TODO: DRY [1]
if todo._due_time: # pragma: no cover
raise ValueError("An todo can't have both DUE and DURATION")
todo._duration = parse_duration(line.value)
@option(multiple=True)
def parse_valarm(todo: "Todo", lines: List[ContentLine]):
todo.alarms = [get_type_from_container(x)._from_container(x) for x in lines]
def parse_status(todo: "Todo", line: ContentLine):
if line:
todo.status = line.value