Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions gcalcli/argparsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,13 @@ def get_output_parser(parents=[]):
default=False,
help='Use Tab Separated Value output',
)
output_parser.add_argument(
'--json',
action='store_true',
dest='json',
default=False,
help='Use JSON output',
)
output_parser.add_argument(
'--nostarted',
action='store_true',
Expand Down
93 changes: 88 additions & 5 deletions gcalcli/details.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ def get(cls, event):
"""Return simple string representation for columnar output."""
raise NotImplementedError

@classmethod
def data(cls, event):
"""Return plain data for formatted output."""
return NotImplementedError

@classmethod
def patch(cls, cal, event, fieldname, value):
"""Patch event from value."""
Expand All @@ -52,6 +57,10 @@ class SingleFieldHandler(Handler):
def get(cls, event):
return [cls._get(event).strip()]

@classmethod
def data(cls, event):
return cls._get(event).strip()

@classmethod
def patch(cls, cal, event, fieldname, value):
return cls._patch(event, value)
Expand Down Expand Up @@ -94,6 +103,10 @@ def get(cls, event):

return start_fields + end_fields

@classmethod
def data(cls, event):
return dict(zip(cls.fieldnames, cls.get(event)))

@classmethod
def patch(cls, cal, event, fieldname, value):
instant_name, _, unit = fieldname.partition('_')
Expand Down Expand Up @@ -124,7 +137,7 @@ def patch(cls, cal, event, fieldname, value):
instant['timeZone'] = cal['timeZone']


class Length(Time):
class Length(Handler):
"""Handler for event duration."""

fieldnames = ['length']
Expand All @@ -133,6 +146,10 @@ class Length(Time):
def get(cls, event):
return [str(event['e'] - event['s'])]

@classmethod
def data(cls, event):
return str(event['e'] - event['s'])

@classmethod
def patch(cls, cal, event, fieldname, value):
# start_date and start_time must be an earlier TSV field than length
Expand All @@ -158,7 +175,12 @@ class Url(Handler):

@classmethod
def get(cls, event):
return [event.get(prop, '') for prop in URL_PROPS.values()]
return [event.get(prop, '').strip() for prop in URL_PROPS.values()]

@classmethod
def data(cls, event):
return {key: event.get(prop, '').strip()
for key, prop in URL_PROPS.items()}

@classmethod
def patch(cls, cal, event, fieldname, value):
Expand All @@ -185,6 +207,10 @@ class Conference(Handler):

fieldnames = list(ENTRY_POINT_PROPS.keys())

CONFERENCE_PROPS = OrderedDict([('meeting_code', 'meetingCode'),
('passcode', 'passcode'),
('region_code', 'regionCode')])

@classmethod
def get(cls, event):
if 'conferenceData' not in event:
Expand All @@ -199,6 +225,19 @@ def get(cls, event):
return [entry_point.get(prop, '')
for prop in ENTRY_POINT_PROPS.values()]

@classmethod
def data(cls, event):
if 'conferenceData' not in event:
return []

PROPS = {**ENTRY_POINT_PROPS, **cls.CONFERENCE_PROPS}

value = []
for entryPoint in event['conferenceData'].get('entryPoints', []):
value.append({key: entryPoint.get(prop, '').strip()
for key, prop in PROPS.items()})
return value

@classmethod
def patch(cls, cal, event, fieldname, value):
if not value:
Expand All @@ -215,6 +254,45 @@ def patch(cls, cal, event, fieldname, value):
entry_point[prop] = value


class Attendees(Handler):
"""Handler for event attendees."""

fieldnames = ['attendees']

ATTENDEE_PROPS = \
OrderedDict([('attendee_email', 'email'),
('attendee_response_status', 'responseStatus')])

@classmethod
def get(cls, event):
if 'attendees' not in event:
return ['']

# only display the attendee emails for TSV
return [';'.join([attendee.get('email', '').strip()
for attendee in event['attendees']])]

@classmethod
def data(cls, event):
value = []
for attendee in event.get('attendees', []):
value.append({key: attendee.get(prop, '').strip()
for key, prop in cls.ATTENDEE_PROPS.items()})
return value

@classmethod
def patch(cls, cal, event, fieldname, value):
if not value:
return

attendees = event.setdefault('attendees', [])
if not attendees:
attendees.append({})

attendee = attendees[0]
attendee[fieldname] = value


class Title(SingleFieldHandler):
"""Handler for title."""

Expand Down Expand Up @@ -265,7 +343,11 @@ class Email(SingleFieldHandler):

@classmethod
def _get(cls, event):
return event['creator'].get('email', '')
if 'organizer' in event:
return event['organizer'].get('email', '')
if 'creator' in event:
return event['creator'].get('email', '')
return event['gcalcli_cal']['id']


class ID(SimpleSingleFieldHandler):
Expand All @@ -274,7 +356,7 @@ class ID(SimpleSingleFieldHandler):
fieldnames = ['id']


class Action(SimpleSingleFieldHandler):
class Action(SingleFieldHandler):
"""Handler specifying event processing during an update."""

fieldnames = ['action']
Expand All @@ -294,6 +376,7 @@ def _get(cls, event):
('description', Description),
('calendar', Calendar),
('email', Email),
('attendees', Attendees),
('action', Action)])
HANDLERS_READONLY = {Url, Calendar}

Expand All @@ -307,7 +390,7 @@ def _get(cls, event):
in FIELD_HANDLERS.items()
if handler in HANDLERS_READONLY)

_DETAILS_WITHOUT_HANDLERS = ['reminders', 'attendees', 'attachments', 'end']
_DETAILS_WITHOUT_HANDLERS = ['reminders', 'attachments', 'end']

DETAILS = list(HANDLERS.keys()) + _DETAILS_WITHOUT_HANDLERS
DETAILS_DEFAULT = {'time', 'title'}
29 changes: 29 additions & 0 deletions gcalcli/gcal.py
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,33 @@ def _tsv(self, start_datetime, event_list):
output = ('\t'.join(row)).replace('\n', r'\n')
print(output)

def _json(self, start_datetime, event_list):
keys = set(self.details.keys())
keys.update(DETAILS_DEFAULT)

print("[")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why print out the list characters manually instead of printing a list converted to JSON?

If it's for pretty printing, json.dumps accepts an indent arg to output pretty-printed JSON (with new lines indented to a certain width).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's to avoid keeping all the rows in memory.

All we're doing is outputting to stdout, so it seemed more prudent to stream each row instead of buffering.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah okay, felt a little low-level to optimize for stdout but I guess that's how a lot of other code here is too.


first = True
for event in event_list:
if self.options['ignore_started'] and (event['s'] < self.now):
continue
if self.options['ignore_declined'] and self._DeclinedEvent(event):
continue

row = {}
#row['raw'] = event

for key in keys:
if key in HANDLERS:
row[key] = HANDLERS[key].data(event)

if not first:
print(",")
first = False
print(json.dumps(row, default=str))

print("]")

def _PrintEvent(self, event, prefix):

def _format_descr(descr, indent, box):
Expand Down Expand Up @@ -1262,6 +1289,8 @@ def _display_queried_events(self, start, end, search=None,

if self.options.get('tsv'):
return self._tsv(start, event_list)
elif self.options.get('json'):
return self._json(start, event_list)
else:
return self._iterate_events(start, event_list, year_date=year_date)

Expand Down