""".. versionadded:: 0.0.9
Control Fantastical using JXA-like syntax.
"""
from datetime import datetime
from typing import Union
import AppKit
from PyXA import XABase
from PyXA import XABaseScriptable
from ..XAProtocols import XAClipboardCodable, XACloseable, XADeletable, XAPrintable
[docs]
class XAFantasticalApplication(XABaseScriptable.XASBApplication):
"""A class for managing and interacting with Fantastical.app.
.. versionadded:: 0.0.9
"""
def __init__(self, properties):
super().__init__(properties)
self.xa_wcls = XAFantasticalWindow
@property
def name(self) -> str:
"""The name of the application."""
return self.xa_scel.name()
@property
def frontmost(self) -> bool:
"""Whether Fantastical is the active application."""
return self.xa_scel.frontmost()
@frontmost.setter
def frontmost(self, frontmost: frontmost):
self.set_property("frontmost", frontmost)
@property
def version(self) -> str:
"""The version of Fantastical.app."""
return self.xa_scel.version()
[docs]
def parse_sentence(
self,
sentence: str,
notes: str = "",
calendar: Union[str, "XAFantasticalCalendar", None] = None,
add_immediately: bool = True,
add_attendees: bool = False,
):
"""Parses the given sentences and creates a corresponding calendar item based on the parsing result.
:param sentence: The sentence to parse
:type sentence: str
:param notes: Notes to attach to the calendar item, defaults to ""
:type notes: str, optional
:param calendar: The calendar to add the item to, defaults to None
:type calendar: Union[str, XAFantasticalCalendar, None], optional
:param add_immediately: Whether to add the item without displaying an event editing dialog, defaults to True
:type add_immediately: bool, optional
:param add_attendees: Whether to invite attendees parsed from the sentence, defaults to False
:type add_attendees: bool, optional
:Example 1: Add simple events to calendars
>>> # Create an event on the default calendar
>>> import PyXA
>>> app = PyXA.Application("Fantastical")
>>> app.parse_sentence("Event 1")
>>>
>>> # Create an event on a calendar specified by a calendar object
>>> cal = app.calendars().by_title("PyXA Development")
>>> app.parse_sentence("Event 2", calendar = cal)
>>>
>>> # Create an event on a calendar specified by a string
>>> app.parse_sentence("Event 3", calendar = "Testing")
:Example 2: Use Fantastical's query parsing to adjust calendar item settings
>>> # Automatically set the time of a task to 8am, show event editing dialog
>>> app.parse_sentence("Wake up at 8am", add_immediately = False)
>>>
>>> # Create a todo
>>> app.parse_sentence("todo today Learn PyXA")
>>>
>>> # Create an event at a location, with an alert, repeating weekly
>>> app.parse_sentence("Meet with Example Person at 10am at 1 Infinite Loop, Cupertino, CA with alert 30 minutes before repeat weekly")
>>>
>>> # Create an event spanning a week
>>> app.parse_sentence("PyXA stuff August 22 to August 29")
.. versionadded:: 0.0.9
"""
if isinstance(calendar, XAFantasticalCalendar):
self.xa_scel.parseSentence_notes_calendar_calendarName_addImmediately_addAttendees_(
sentence, notes, calendar.xa_elem, None, add_immediately, add_attendees
)
elif isinstance(calendar, str):
self.xa_scel.parseSentence_notes_calendar_calendarName_addImmediately_addAttendees_(
sentence, notes, None, calendar, add_immediately, add_attendees
)
else:
self.xa_scel.parseSentence_notes_calendar_calendarName_addImmediately_addAttendees_(
sentence, notes, None, None, add_immediately, add_attendees
)
[docs]
def show_mini_view(self, date: Union[str, datetime, None] = None):
"""Shows the mini calendar view, optionally showing a specific date.
:param date: The date to display, defaults to None
:type date: Union[str, datetime, None], optional
.. versionadded:: 0.0.9
"""
if date is None:
date = ""
if isinstance(date, datetime):
date = date.strftime("%Y-%m-%d")
print(date)
XABase.XAURL(f"x-fantastical3://show/mini/{date}").open()
[docs]
def show_calendar_view(self, date: Union[str, datetime, None] = None):
"""Shows the (large) calendar view, optionally showing a specific date.
:param date: The date to display, defaults to None
:type date: Union[str, datetime, None], optional
.. versionadded:: 0.0.9
"""
if date is None:
date = ""
if isinstance(date, datetime):
date = date.strftime("%Y-%m-%d")
print(date)
XABase.XAURL(f"x-fantastical3://show/calendar/{date}").open()
[docs]
def documents(self, filter: dict = None) -> "XAFantasticalDocumentList":
"""Returns a list of documents, as PyXA objects, matching the given filter.
.. versionadded:: 0.0.9
"""
return self._new_element(
self.xa_scel.documents(), XAFantasticalDocumentList, filter
)
[docs]
def calendars(self, filter: dict = None) -> "XAFantasticalCalendarList":
"""Returns a list of calendars, as PyXA objects, matching the given filter.
.. versionadded:: 0.0.9
"""
return self._new_element(
self.xa_scel.calendars(), XAFantasticalCalendarList, filter
)
[docs]
def selected_calendar_items(
self, filter: dict = None
) -> "XAFantasticalSelectedCalendarItemList":
"""Returns a list of selected calendar items, as PyXA objects, matching the given filter.
.. versionadded:: 0.0.9
"""
return self._new_element(
self.xa_scel.selectedCalendarItems(),
XAFantasticalSelectedCalendarItemList,
filter,
)
[docs]
class XAFantasticalDocumentList(XABase.XAList):
"""A wrapper around lists of Fantastical documents that employs fast enumeration techniques.
All properties of documents can be called as methods on the wrapped list, returning a list containing each document's value for the property.
.. versionadded:: 0.0.9
"""
def __init__(self, properties: dict, filter: Union[dict, None] = None):
super().__init__(properties, XAFantasticalDocument, filter)
[docs]
def name(self) -> list[str]:
return list(self.xa_elem.arrayByApplyingSelector_("name") or [])
[docs]
def modified(self) -> list[bool]:
return list(self.xa_elem.arrayByApplyingSelector_("modified") or [])
[docs]
def file(self) -> list[XABase.XAPath]:
ls = self.xa_elem.arrayByApplyingSelector_("file") or []
return [XABase.XAPath(x) for x in ls]
[docs]
def by_name(self, name: str) -> "XAFantasticalDocument":
return self.by_property("name", name)
[docs]
def by_modified(self, modified: bool) -> "XAFantasticalDocument":
return self.by_property("modified", modified)
[docs]
def by_file(self, file: XABase.XAPath) -> "XAFantasticalDocument":
return self.by_property("file", file.xa_elem)
def __repr__(self):
return "<" + str(type(self)) + str(self.name()) + ">"
[docs]
class XAFantasticalDocument(
XABase.XAObject, XACloseable, XADeletable, XAPrintable, XAClipboardCodable
):
"""A document in Fantastical.app.
.. versionadded:: 0.0.9
"""
def __init__(self, properties):
super().__init__(properties)
@property
def name(self) -> str:
return self.xa_elem.name()
@name.setter
def name(self, name: str):
"""The document's name."""
self.set_property("name", name)
@property
def modified(self) -> bool:
"""Whether the document has been modified since it was last saved."""
return self.xa_elem.modified()
@property
def file(self) -> XABase.XAPath:
"""The document's path."""
return XABase.XAPath(self.xa_elem.file())
@file.setter
def file(self, file: Union[str, XABase.XAPath]):
if isinstance(file, str):
file = XABase.XAPath(file)
self.set_property("file", file.xa_elem)
[docs]
def print(
self, print_properties: Union[dict, None] = None, show_dialog: bool = True
) -> "XAPrintable":
"""Prints the object.
Child classes of XAPrintable should override this method as necessary.
:param show_dialog: Whether to show the print dialog, defaults to True
:type show_dialog: bool, optional
:param print_properties: Properties to set for printing, defaults to None
:type print_properties: Union[dict, None], optional
:return: A reference to the PyXA object that called this method.
:rtype: XACanPrintPath
.. versionadded:: 0.0.9
"""
if print_properties is None:
print_properties = {}
self.xa_elem.print_printDialog_withProperties_(
self.xa_elem, show_dialog, print_properties
)
return self
[docs]
def get_clipboard_representation(self) -> list[Union[AppKit.NSURL, str]]:
"""Gets a clipboard-codable representation of the document.
When the clipboard content is set to a Fantastical document, the document's URL and source code are added to the clipboard.
:return: The document's path and text content
:rtype: list[Union[AppKit.NSURL, str]]
.. versionadded:: 0.0.9
"""
return [self.path.xa_elem, str(self.text)]
def __repr__(self):
return "<" + str(type(self)) + str(self.name) + ">"
[docs]
class XAFantasticalWindow(XABaseScriptable.XASBWindow, XAPrintable, XACloseable):
"""A window of Fantastical.app."""
def __init__(self, properties):
super().__init__(properties)
@property
def document(self) -> XAFantasticalDocument:
"""The document currently displayed in the window."""
return self._new_element(self.xa_elem.document(), XAFantasticalDocument)
@document.setter
def document(self, document: XAFantasticalDocument):
self.set_property("document", document.xa_elem)
[docs]
class XAFantasticalCalendarList(XABase.XAList):
"""A wrapper around lists of Fantastical calendars that employs fast enumeration techniques.
All properties of calendars can be called as methods on the wrapped list, returning a list containing each calendar's value for the property.
.. versionadded:: 0.0.9
"""
def __init__(self, properties: dict, filter: Union[dict, None] = None):
super().__init__(properties, XAFantasticalCalendar, filter)
[docs]
def title(self) -> list[str]:
return list(self.xa_elem.arrayByApplyingSelector_("title") or [])
[docs]
def id(self) -> list[str]:
return list(self.xa_elem.arrayByApplyingSelector_("id") or [])
[docs]
def by_title(self, title: str) -> "XAFantasticalCalendar":
return self.by_property("title", title)
[docs]
def by_id(self, id: str) -> "XAFantasticalCalendar":
return self.by_property("id", id)
def __repr__(self):
return "<" + str(type(self)) + str(self.title()) + ">"
[docs]
class XAFantasticalCalendar(XABase.XAObject):
"""A class in Fantastical.app.
.. versionadded:: 0.0.9
"""
def __init__(self, properties):
super().__init__(properties)
@property
def title(self) -> str:
"""The calendar's title."""
return self.xa_elem.title()
@title.setter
def title(self, title: str):
self.set_property("title", title)
@property
def id(self) -> str:
"""The unique identifier for the calendar."""
return self.xa_elem.id()
def __repr__(self):
return "<" + str(type(self)) + str(self.title) + ">"
[docs]
class XAFantasticalCalendarItemList(XABase.XAList):
"""A wrapper around lists of Fantastical calendar items that employs fast enumeration techniques.
All properties of calendar items can be called as methods on the wrapped list, returning a list containing each calendar item's value for the property.
.. versionadded:: 0.0.9
"""
def __init__(
self, properties: dict, filter: Union[dict, None] = None, obj_class=None
):
if obj_class is None:
obj_class = XAFantasticalCalendarItem
super().__init__(properties, obj_class, filter)
[docs]
def id(self) -> list[str]:
return list(self.xa_elem.arrayByApplyingSelector_("id") or [])
[docs]
def title(self) -> list[str]:
return list(self.xa_elem.arrayByApplyingSelector_("title") or [])
[docs]
def start_date(self) -> list[datetime]:
return list(self.xa_elem.arrayByApplyingSelector_("startDate") or [])
[docs]
def end_date(self) -> list[datetime]:
return list(self.xa_elem.arrayByApplyingSelector_("endDate") or [])
[docs]
def notes(self) -> list[str]:
return list(self.xa_elem.arrayByApplyingSelector_("notes") or [])
[docs]
def url(self) -> list[XABase.XAURL]:
ls = self.xa_elem.arrayByApplyingSelector_("URL") or []
return [XABase.XAURL(x) for x in ls]
[docs]
def show_url(self) -> list[XABase.XAURL]:
return list(self.xa_elem.arrayByApplyingSelector_("showURL") or [])
[docs]
def is_recurring(self) -> list[bool]:
return list(self.xa_elem.arrayByApplyingSelector_("isRecurring") or [])
[docs]
def is_all_day(self) -> list[bool]:
return list(self.xa_elem.arrayByApplyingSelector_("isAllDay") or [])
[docs]
def by_id(self, id: str) -> "XAFantasticalCalendarItem":
return self.by_property("id", id)
[docs]
def by_title(self, title: str) -> "XAFantasticalCalendarItem":
return self.by_property("title", title)
[docs]
def by_start_date(self, start_date: datetime) -> "XAFantasticalCalendarItem":
return self.by_property("startDate", start_date)
[docs]
def by_end_date(self, end_date: datetime) -> "XAFantasticalCalendarItem":
return self.by_property("endDate", end_date)
[docs]
def by_notes(self, notes: str) -> "XAFantasticalCalendarItem":
return self.by_property("notes", notes)
[docs]
def by_url(self, url: XABase.XAURL) -> "XAFantasticalCalendarItem":
return self.by_property("URL", url.xa_elem)
[docs]
def by_show_url(self, show_url: XABase.XAURL) -> "XAFantasticalCalendarItem":
return self.by_property("showURL", show_url.xa_elem)
[docs]
def by_is_recurring(self, is_recurring: bool) -> "XAFantasticalCalendarItem":
return self.by_property("isRecurring", is_recurring)
[docs]
def by_is_all_day(self, is_all_day: bool) -> "XAFantasticalCalendarItem":
return self.by_property("isAllDay", is_all_day)
def __repr__(self):
return "<" + str(type(self)) + str(self.title()) + ">"
[docs]
class XAFantasticalCalendarItem(XABase.XAObject):
"""An insertion point between two objects in Fantastical.app.
.. versionadded:: 0.0.9
"""
def __init__(self, properties):
super().__init__(properties)
@property
def id(self) -> str:
"""The unique identifier for the item."""
return self.xa_elem.id()
@property
def title(self) -> str:
"""The event title."""
return self.xa_elem.title()
@property
def start_date(self) -> datetime:
"""The start date of the event."""
return self.xa_elem.startDate()
@property
def end_date(self) -> datetime:
"""The end date of the event."""
return self.xa_elem.endDate()
@property
def notes(self) -> str:
"""The notes for the event."""
return self.xa_elem.notes() or ""
@property
def url(self) -> XABase.XAURL:
"""The related URL for the event."""
return XABase.XAURL(self.xa_elem.URL())
@property
def show_url(self) -> XABase.XAURL:
"""The show URL for the event."""
return XABase.XAURL(self.xa_elem.showURL())
@property
def is_recurring(self) -> bool:
"""True if the item is a recurring item."""
return self.xa_elem.isRecurring()
@property
def is_all_day(self) -> bool:
"""True if the item spans an entire day."""
return self.xa_elem.isAllDay()
[docs]
def save(self):
self.xa_elem.saveIn_as_(None, 0)
def __repr__(self):
return "<" + str(type(self)) + str(self.title) + ">"
[docs]
class XAFantasticalSelectedCalendarItemList(XAFantasticalCalendarItemList):
"""A wrapper around lists of Fantastical selected calendar items that employs fast enumeration techniques.
All properties of selected calendar items can be called as methods on the wrapped list, returning a list containing each items's value for the property.
.. versionadded:: 0.0.9
"""
def __init__(self, properties: dict, filter: Union[dict, None] = None):
super().__init__(properties, filter, XAFantasticalSelectedCalendarItem)
[docs]
class XAFantasticalSelectedCalendarItem(XAFantasticalCalendarItem):
def __init__(self, properties):
super().__init__(properties)
[docs]
class XAFantasticalCalendarEvent(XAFantasticalCalendarItem):
def __init__(self, properties):
super().__init__(properties)
@property
def location(self) -> str:
"""The event location."""
return self.xa_elem.location()
@location.setter
def location(self, location: str):
self.set_property("location", location)
[docs]
class XAFantasticalTaskItem(XAFantasticalCalendarItem):
def __init__(self, properties):
super().__init__(properties)
@property
def priority(self) -> int:
"""The event priority; higher number means lower priority."""
return self.xa_elem.priority()
@priority.setter
def priority(self, priority: int):
self.set_property("priority", priority)