1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/imip_text_client.py Fri Oct 27 00:44:53 2017 +0200
1.3 @@ -0,0 +1,905 @@
1.4 +#!/usr/bin/env python
1.5 +
1.6 +from email import message_from_file
1.7 +from imiptools import parse_args
1.8 +from imiptools.client import ClientForObject
1.9 +from imiptools.config import settings
1.10 +from imiptools.content import get_objects_from_itip, have_itip_part, parse_itip_part
1.11 +from imiptools.data import get_address, get_main_period, get_recurrence_periods, get_value
1.12 +from imiptools.dates import get_datetime_item, get_time, to_timezone
1.13 +from imiptools.editing import EditingClient, PeriodError
1.14 +from imiptools.mail import Messenger
1.15 +from imiptools.stores import get_journal, get_store
1.16 +from imiptools.utils import decode_part, message_as_string
1.17 +import sys
1.18 +
1.19 +# User interface functions.
1.20 +
1.21 +echo = False
1.22 +
1.23 +def read_input(label):
1.24 + s = raw_input(label).strip()
1.25 + if echo:
1.26 + print s
1.27 + return s
1.28 +
1.29 +def input_with_default(label, default):
1.30 + return read_input(label % default) or default
1.31 +
1.32 +def print_title(text):
1.33 + print text
1.34 + print len(text) * "-"
1.35 +
1.36 +def write(s, filename):
1.37 + f = filename and open(filename, "w") or None
1.38 + try:
1.39 + print >>(f or sys.stdout), s
1.40 + finally:
1.41 + if f:
1.42 + f.close()
1.43 +
1.44 +# Interpret an input file containing a calendar resource.
1.45 +
1.46 +def get_objects(filename):
1.47 +
1.48 + "Return objects provided by 'filename'."
1.49 +
1.50 + f = open(filename)
1.51 + try:
1.52 + msg = message_from_file(f)
1.53 + finally:
1.54 + f.close()
1.55 +
1.56 + all_objects = []
1.57 +
1.58 + for part in msg.walk():
1.59 + itip = parse_itip_part(part)
1.60 + method = itip and get_value(itip, "METHOD")
1.61 +
1.62 + # Ignore cancelled objects since only active objects are of interest.
1.63 +
1.64 + if method != "CANCEL":
1.65 + all_objects += get_objects_from_itip(itip, ["VEVENT"])
1.66 +
1.67 + return all_objects
1.68 +
1.69 +def show_objects(objects, user, store):
1.70 +
1.71 + """
1.72 + Show details of 'objects', accessed by the given 'user' in the given
1.73 + 'store'.
1.74 + """
1.75 +
1.76 + for index, obj in enumerate(objects):
1.77 + recurrenceid = obj.get_recurrenceid()
1.78 + recurrence_label = recurrenceid and " %s" % recurrenceid or ""
1.79 + print "(%d) Summary: %s (%s%s)" % (index, obj.get_value("SUMMARY"), obj.get_uid(), recurrence_label)
1.80 +
1.81 +def show_attendee(attendee_item, index):
1.82 +
1.83 + "Show the 'attendee_item' (value and attributes) at 'index'."
1.84 +
1.85 + attendee, attr = attendee_item
1.86 + partstat = attr.get("PARTSTAT")
1.87 + print "(%d) %s%s" % (index, attendee, partstat and " (%s)" % partstat or "")
1.88 +
1.89 +def show_attendees_raw(attendee_map):
1.90 +
1.91 + "Show the 'attendee_map' in a simple raw form."
1.92 +
1.93 + for attendee, attr in attendee_map.items():
1.94 + print attendee
1.95 +
1.96 +def show_periods(periods, errors=None):
1.97 +
1.98 + "Show 'periods' with any indicated 'errors'."
1.99 +
1.100 + main = get_main_period(periods)
1.101 + if main:
1.102 + show_period(main, 0, errors)
1.103 +
1.104 + recurrences = get_recurrence_periods(periods)
1.105 + if recurrences:
1.106 + print
1.107 + print_title("Recurrences")
1.108 + for index, p in enumerate(recurrences):
1.109 + show_period(p, index + 1, errors)
1.110 +
1.111 +def show_period(p, index, errors=None):
1.112 +
1.113 + "Show period 'p' at 'index' with any indicated 'errors'."
1.114 +
1.115 + errors = errors and errors.get(index)
1.116 + if p.replacement:
1.117 + if p.cancelled:
1.118 + label = "Cancelled"
1.119 + else:
1.120 + label = "Replaced"
1.121 + else:
1.122 + if p.new_replacement:
1.123 + label = "To replace"
1.124 + elif p.recurrenceid:
1.125 + label = "Retained"
1.126 + else:
1.127 + label = "New"
1.128 +
1.129 + error_label = errors and " (errors: %s)" % ", ".join(errors) or ""
1.130 + print "(%d) %s%s:" % (index, label, error_label), p.get_start(), p.get_end(), p.origin
1.131 +
1.132 +def show_periods_raw(periods):
1.133 +
1.134 + "Show 'periods' in a simple raw form."
1.135 +
1.136 + periods = periods[:]
1.137 + periods.sort()
1.138 + map(show_period_raw, periods)
1.139 +
1.140 +def show_period_raw(p):
1.141 +
1.142 + "Show period 'p' in a simple raw form."
1.143 +
1.144 + print p.get_start(), p.get_end(), p.origin
1.145 +
1.146 +def show_attendee_changes(new, modified, unmodified, removed):
1.147 +
1.148 + "Show 'new', 'modified', 'unmodified' and 'removed' periods."
1.149 +
1.150 + print
1.151 + print_title("Changes to attendees")
1.152 + print
1.153 + print "New:"
1.154 + show_attendees_raw(new)
1.155 + print
1.156 + print "Modified:"
1.157 + show_attendees_raw(modified)
1.158 + print
1.159 + print "Unmodified:"
1.160 + show_attendees_raw(unmodified)
1.161 + print
1.162 + print "Removed:"
1.163 + show_attendees_raw(removed)
1.164 +
1.165 +def show_period_classification(new, replaced, retained, cancelled, obsolete):
1.166 +
1.167 + "Show 'new', 'replaced', 'retained', 'cancelled' and 'obsolete' periods."
1.168 +
1.169 + print
1.170 + print_title("Period classification")
1.171 + print
1.172 + print "New:"
1.173 + show_periods_raw(new)
1.174 + print
1.175 + print "Replaced:"
1.176 + show_periods_raw(replaced)
1.177 + print
1.178 + print "Retained:"
1.179 + show_periods_raw(retained)
1.180 + print
1.181 + print "Cancelled:"
1.182 + show_periods_raw(cancelled)
1.183 + print
1.184 + print "Obsolete:"
1.185 + show_periods_raw(obsolete)
1.186 +
1.187 +def show_changes(modified, unmodified, removed):
1.188 +
1.189 + "Show 'modified', 'unmodified' and 'removed' periods."
1.190 +
1.191 + print
1.192 + print_title("Changes to periods")
1.193 + print
1.194 + print "Modified:"
1.195 + show_periods_raw(modified)
1.196 + print
1.197 + print "Unmodified:"
1.198 + show_periods_raw(unmodified)
1.199 + print
1.200 + print "Removed:"
1.201 + show_periods_raw(removed)
1.202 +
1.203 +def show_attendee_operations(to_invite, to_cancel, to_modify):
1.204 +
1.205 + "Show attendees 'to_invite', 'to_cancel' and 'to_modify'."
1.206 +
1.207 + print
1.208 + print_title("Attendee update operations")
1.209 + print
1.210 + print "To invite:"
1.211 + show_attendees_raw(to_invite)
1.212 + print
1.213 + print "To cancel:"
1.214 + show_attendees_raw(to_cancel)
1.215 + print
1.216 + print "To modify:"
1.217 + show_attendees_raw(to_modify)
1.218 +
1.219 +def show_period_operations(to_unschedule, to_reschedule, to_add, to_exclude, to_set,
1.220 + all_unscheduled, all_rescheduled):
1.221 +
1.222 + """
1.223 + Show operations for periods 'to_unschedule', 'to_reschedule', 'to_add',
1.224 + 'to_exclude' and 'to_set' (for updating other calendar participants), and
1.225 + for periods 'all_unscheduled' and 'all_rescheduled' (for publishing event
1.226 + state).
1.227 + """
1.228 +
1.229 + print
1.230 + print_title("Period update and publishing operations")
1.231 + print
1.232 + print "Unschedule:"
1.233 + show_periods_raw(to_unschedule)
1.234 + print
1.235 + print "Reschedule:"
1.236 + show_periods_raw(to_reschedule)
1.237 + print
1.238 + print "Added:"
1.239 + show_periods_raw(to_add)
1.240 + print
1.241 + print "Excluded:"
1.242 + show_periods_raw(to_exclude)
1.243 + print
1.244 + print "Set in object:"
1.245 + show_periods_raw(to_set)
1.246 + print
1.247 + print "All unscheduled:"
1.248 + show_periods_raw(all_unscheduled)
1.249 + print
1.250 + print "All rescheduled:"
1.251 + show_periods_raw(all_rescheduled)
1.252 +
1.253 +class TextClient(EditingClient):
1.254 +
1.255 + "Simple client with textual output."
1.256 +
1.257 + def new_object(self):
1.258 +
1.259 + "Create a new object with the current time."
1.260 +
1.261 + utcnow = get_time()
1.262 + now = to_timezone(utcnow, self.get_tzid())
1.263 + obj = EditingClient.new_object(self, "VEVENT")
1.264 + obj.set_value("SUMMARY", "New event")
1.265 + obj["DTSTART"] = [get_datetime_item(now)]
1.266 + obj["DTEND"] = [get_datetime_item(now)]
1.267 + return obj
1.268 +
1.269 + # Editing methods involving interaction.
1.270 +
1.271 + def edit_attendee(self, index):
1.272 +
1.273 + "Edit the attendee at 'index'."
1.274 +
1.275 + t = self.can_edit_attendee(index)
1.276 + if t:
1.277 + attendees = self.state.get("attendees")
1.278 + attendee, attr = t
1.279 + del attendees[attendee]
1.280 + attendee = input_with_default("Attendee (%s)? ", attendee)
1.281 + attendees[attendee] = attr
1.282 +
1.283 + def edit_period(self, index, args=None):
1.284 + period = self.can_edit_period(index)
1.285 + if period:
1.286 + edit_period(period, args)
1.287 + period.cancelled = False
1.288 +
1.289 + # Sort the periods after this change.
1.290 +
1.291 + periods = self.state.get("periods")
1.292 + periods.sort()
1.293 +
1.294 + def edit_summary(self, summary=None):
1.295 + if self.can_edit_properties():
1.296 + if not summary:
1.297 + summary = input_with_default("Summary (%s)? ", self.state.get("summary"))
1.298 + self.state.set("summary", summary)
1.299 +
1.300 + def finish(self):
1.301 + try:
1.302 + EditingClient.finish(self)
1.303 + except PeriodError:
1.304 + print "Errors exist in the periods."
1.305 + return
1.306 +
1.307 + # Diagnostic methods.
1.308 +
1.309 + def show_period_classification(self):
1.310 + try:
1.311 + new, replaced, retained, cancelled, obsolete = self.classify_periods()
1.312 + show_period_classification(new, replaced, retained, cancelled, obsolete)
1.313 + except PeriodError:
1.314 + print
1.315 + print "Errors exist in the periods."
1.316 +
1.317 + def show_changes(self):
1.318 + try:
1.319 + modified, unmodified, removed = self.classify_period_changes()
1.320 + show_changes(modified, unmodified, removed)
1.321 + except PeriodError:
1.322 + print "Errors exist in the periods."
1.323 +
1.324 + is_changed = self.properties_changed()
1.325 + if is_changed:
1.326 + print
1.327 + print "Properties changed:", ", ".join(is_changed)
1.328 + new, modified, unmodified, removed = self.classify_attendee_changes()
1.329 + show_attendee_changes(new, modified, unmodified, removed)
1.330 +
1.331 + def show_operations(self):
1.332 + is_changed = self.properties_changed()
1.333 +
1.334 + try:
1.335 + to_unschedule, to_reschedule, to_add, to_exclude, to_set, \
1.336 + all_unscheduled, all_rescheduled = self.classify_period_operations()
1.337 + show_period_operations(to_unschedule, to_reschedule, to_add,
1.338 + to_exclude, to_set,
1.339 + all_unscheduled, all_rescheduled)
1.340 + except PeriodError:
1.341 + print "Errors exist in the periods."
1.342 +
1.343 + to_invite, to_cancel, to_modify = self.classify_attendee_operations()
1.344 + show_attendee_operations(to_invite, to_cancel, to_modify)
1.345 +
1.346 + # Output methods.
1.347 +
1.348 + def show_message(self, message, plain=False, filename=None):
1.349 + if plain:
1.350 + decode_part(message)
1.351 + write(message_as_string(message), filename)
1.352 +
1.353 + def show_cancel_message(self, plain=False, filename=None):
1.354 +
1.355 + "Show the cancel message for uninvited attendees."
1.356 +
1.357 + message = self.prepare_cancel_message()
1.358 + if message:
1.359 + self.show_message(message, plain, filename)
1.360 +
1.361 + def show_publish_message(self, plain=False, filename=None):
1.362 +
1.363 + "Show the publishing message for the updated event."
1.364 +
1.365 + message = self.prepare_publish_message()
1.366 + self.show_message(message, plain, filename)
1.367 +
1.368 + def show_update_message(self, plain=False, filename=None):
1.369 +
1.370 + "Show the update message for the updated event."
1.371 +
1.372 + message = self.prepare_update_message()
1.373 + if message:
1.374 + self.show_message(message, plain, filename)
1.375 +
1.376 + # General display methods.
1.377 +
1.378 + def show_object(self):
1.379 + print
1.380 + print "Summary:", self.state.get("summary")
1.381 + print
1.382 + print "Organiser:", self.state.get("organiser")
1.383 + self.show_attendees()
1.384 + self.show_periods()
1.385 + self.show_suggested_attendees()
1.386 + self.show_suggested_periods()
1.387 + self.show_conflicting_periods()
1.388 +
1.389 + def show_attendees(self):
1.390 + print
1.391 + print_title("Attendees")
1.392 + attendees = self.state.get("attendees")
1.393 + for index, attendee_item in enumerate(attendees.items()):
1.394 + show_attendee(attendee_item, index)
1.395 +
1.396 + def show_periods(self):
1.397 + print
1.398 + print_title("Periods")
1.399 + show_periods(self.state.get("periods"), self.state.get("period_errors"))
1.400 +
1.401 + def show_suggested_attendees(self):
1.402 + current_attendee = None
1.403 + for index, (attendee, suggested_item) in enumerate(self.state.get("suggested_attendees")):
1.404 + if attendee != current_attendee:
1.405 + print
1.406 + print_title("Attendees suggested by %s" % attendee)
1.407 + current_attendee = attendee
1.408 + show_attendee(suggested_item, index)
1.409 +
1.410 + def show_suggested_periods(self):
1.411 + periods = self.state.get("suggested_periods")
1.412 + current_attendee = None
1.413 + index = 0
1.414 + for attendee, period, operation in periods:
1.415 + if attendee != current_attendee:
1.416 + print
1.417 + print_title("Periods suggested by %s" % attendee)
1.418 + current_attendee = attendee
1.419 + show_period(period, index)
1.420 + print " %s" % (operation == "add" and "Add this period" or "Remove this period")
1.421 + index += 1
1.422 +
1.423 + def show_conflicting_periods(self):
1.424 + conflicts = self.get_conflicting_periods()
1.425 + if not conflicts:
1.426 + return
1.427 + print
1.428 + print_title("Conflicting periods")
1.429 +
1.430 + conflicts = list(conflicts)
1.431 + conflicts.sort()
1.432 +
1.433 + for p in conflicts:
1.434 + print p.summary, p.uid, p.get_start(), p.get_end()
1.435 +
1.436 +# Interaction functions.
1.437 +
1.438 +def expand_arg(args):
1.439 + if args[0] and args[0][1:].isdigit():
1.440 + args[:1] = [args[0][0], args[0][1:]]
1.441 +
1.442 +def get_filename_arg(cmd):
1.443 + return (cmd.split()[1:] or [None])[0]
1.444 +
1.445 +def next_arg(args):
1.446 + if args:
1.447 + arg = args[0]
1.448 + del args[0]
1.449 + return arg
1.450 + return None
1.451 +
1.452 +def edit_period(period, args=None):
1.453 +
1.454 + "Edit the given 'period'."
1.455 +
1.456 + print "Editing start (%s)" % period.get_start()
1.457 + edit_date(period.start, args)
1.458 + print "Editing end (%s)" % period.get_end()
1.459 + edit_date(period.end, args)
1.460 +
1.461 +def edit_date(date, args=None):
1.462 +
1.463 + "Edit the given 'date' object attributes."
1.464 +
1.465 + date.date = next_arg(args) or input_with_default("Date (%s)? ", date.date)
1.466 + date.hour = next_arg(args) or input_with_default("Hour (%s)? ", date.hour)
1.467 + date.minute = next_arg(args) or input_with_default("Minute (%s)? ", date.minute)
1.468 + date.second = next_arg(args) or input_with_default("Second (%s)? ", date.second)
1.469 + date.tzid = next_arg(args) or input_with_default("Time zone (%s)? ", date.tzid)
1.470 + date.reset()
1.471 +
1.472 +def select_object(cl, objects):
1.473 + print
1.474 + while True:
1.475 + try:
1.476 + cmd = read_input("Object or (n)ew object or (q)uit> ")
1.477 + except EOFError:
1.478 + return None
1.479 +
1.480 + if cmd.isdigit():
1.481 + index = int(cmd)
1.482 + if 0 <= index < len(objects):
1.483 + obj = objects[index]
1.484 + return cl.load_object(obj.get_uid(), obj.get_recurrenceid())
1.485 + elif cmd in ("n", "new"):
1.486 + return cl.new_object()
1.487 + elif cmd in ("q", "quit", "exit"):
1.488 + return None
1.489 +
1.490 +def show_help():
1.491 + print
1.492 + print_title("Editing commands")
1.493 + print
1.494 + print """\
1.495 +a [ <uri> ]
1.496 +attendee [ <uri> ]
1.497 + Add attendee
1.498 +
1.499 +A, attend, attendance
1.500 + Change attendance/participation
1.501 +
1.502 +a<digit>
1.503 +attendee <digit>
1.504 + Select attendee from list
1.505 +
1.506 +as<digit>
1.507 + Add suggested attendee from list
1.508 +
1.509 +f, finish
1.510 + Finish editing, confirming changes, proceeding to messaging
1.511 +
1.512 +h, help, ?
1.513 + Show this help message
1.514 +
1.515 +l, list, show
1.516 + List/show all event details
1.517 +
1.518 +p, period
1.519 + Add new period
1.520 +
1.521 +p<digit>
1.522 +period <digit>
1.523 + Select period from list
1.524 +
1.525 +ps<digit>
1.526 + Add or remove suggested period from list
1.527 +
1.528 +q, quit, exit
1.529 + Exit/quit this program
1.530 +
1.531 +r, reload, reset, restart
1.532 + Reset event periods (return to editing mode, if already finished)
1.533 +
1.534 +s, summary
1.535 + Set event summary
1.536 +"""
1.537 +
1.538 + print_title("Diagnostic commands")
1.539 + print
1.540 + print """\
1.541 +c, class, classification
1.542 + Show period classification
1.543 +
1.544 +C, changes
1.545 + Show changes made by editing
1.546 +
1.547 +o, ops, operations
1.548 + Show update operations
1.549 +
1.550 +RECURRENCE-ID [ <filename> ]
1.551 + Show event recurrence identifier, writing to <filename> if specified
1.552 +
1.553 +UID [ <filename> ]
1.554 + Show event unique identifier, writing to <filename> if specified
1.555 +"""
1.556 +
1.557 + print_title("Messaging commands")
1.558 + print
1.559 + print """\
1.560 +P [ <filename> ]
1.561 +publish [ <filename> ]
1.562 + Show publishing message, writing to <filename> if specified
1.563 +
1.564 +R [ <filename> ]
1.565 +remove [ <filename> ]
1.566 +cancel [ <filename> ]
1.567 + Show cancellation message sent to uninvited/removed recipients, writing to
1.568 + <filename> if specified
1.569 +
1.570 +U [ <filename> ]
1.571 +update [ <filename> ]
1.572 + Show update message, writing to <filename> if specified
1.573 +"""
1.574 +
1.575 +def edit_object(cl, obj):
1.576 + cl.show_object()
1.577 + print
1.578 +
1.579 + try:
1.580 + while True:
1.581 + role = cl.is_organiser() and "Organiser" or "Attendee"
1.582 + status = cl.state.get("finished") and " (editing complete)" or ""
1.583 +
1.584 + cmd = read_input("%s%s> " % (role, status))
1.585 +
1.586 + args = cmd.split()
1.587 +
1.588 + if not args or not args[0]:
1.589 + continue
1.590 +
1.591 + # Check the status of the periods.
1.592 +
1.593 + if cmd in ("c", "class", "classification"):
1.594 + cl.show_period_classification()
1.595 + print
1.596 +
1.597 + elif cmd in ("C", "changes"):
1.598 + cl.show_changes()
1.599 + print
1.600 +
1.601 + # Finish editing.
1.602 +
1.603 + elif cmd in ("f", "finish"):
1.604 + cl.finish()
1.605 +
1.606 + # Help.
1.607 +
1.608 + elif cmd in ("h", "?", "help"):
1.609 + show_help()
1.610 +
1.611 + # Show object details.
1.612 +
1.613 + elif cmd in ("l", "list", "show"):
1.614 + cl.show_object()
1.615 + print
1.616 +
1.617 + # Show the operations.
1.618 +
1.619 + elif cmd in ("o", "ops", "operations"):
1.620 + cl.show_operations()
1.621 + print
1.622 +
1.623 + # Quit or exit.
1.624 +
1.625 + elif cmd in ("q", "quit", "exit"):
1.626 + break
1.627 +
1.628 + # Restart editing.
1.629 +
1.630 + elif cmd in ("r", "reload", "reset", "restart"):
1.631 + obj = cl.load_object(obj.get_uid(), obj.get_recurrenceid())
1.632 + if not obj:
1.633 + obj = cl.new_object()
1.634 + cl.reset()
1.635 + cl.show_object()
1.636 + print
1.637 +
1.638 + # Show UID details.
1.639 +
1.640 + elif args[0] == "UID":
1.641 + filename = get_filename_arg(cmd)
1.642 + write(obj.get_uid(), filename)
1.643 +
1.644 + elif args[0] == "RECURRENCE-ID":
1.645 + filename = get_filename_arg(cmd)
1.646 + write(obj.get_recurrenceid() or "", filename)
1.647 +
1.648 + # Post-editing operations.
1.649 +
1.650 + elif cl.state.get("finished"):
1.651 +
1.652 + # Show messages.
1.653 +
1.654 + if args[0] in ("P", "publish"):
1.655 + filename = get_filename_arg(cmd)
1.656 + cl.show_publish_message(plain=not filename, filename=filename)
1.657 +
1.658 + elif args[0] in ("R", "remove", "cancel"):
1.659 + filename = get_filename_arg(cmd)
1.660 + cl.show_cancel_message(plain=not filename, filename=filename)
1.661 +
1.662 + elif args[0] in ("U", "update"):
1.663 + filename = get_filename_arg(cmd)
1.664 + cl.show_update_message(plain=not filename, filename=filename)
1.665 +
1.666 + # Editing operations.
1.667 +
1.668 + elif not cl.state.get("finished"):
1.669 +
1.670 + # Expand short-form arguments.
1.671 +
1.672 + expand_arg(args)
1.673 +
1.674 + # Add or edit attendee.
1.675 +
1.676 + if args[0] in ("a", "attendee"):
1.677 +
1.678 + args = args[1:]
1.679 + value = next_arg(args)
1.680 +
1.681 + if value and value.isdigit():
1.682 + index = int(value)
1.683 + else:
1.684 + try:
1.685 + index = cl.find_attendee(value)
1.686 + except ValueError:
1.687 + index = None
1.688 +
1.689 + # Add an attendee.
1.690 +
1.691 + if index is None:
1.692 + cl.add_attendee(value)
1.693 + if not value:
1.694 + cl.edit_attendee(-1)
1.695 +
1.696 + # Edit attendee (using index).
1.697 +
1.698 + else:
1.699 + attendee_item = cl.can_remove_attendee(index)
1.700 + if attendee_item:
1.701 + while True:
1.702 + show_attendee(attendee_item, index)
1.703 +
1.704 + # Obtain a command from any arguments.
1.705 +
1.706 + cmd = next_arg(args)
1.707 + if not cmd:
1.708 + cmd = read_input(" (e)dit, (r)emove (or return)> ")
1.709 + if cmd in ("e", "edit"):
1.710 + cl.edit_attendee(index)
1.711 + elif cmd in ("r", "remove"):
1.712 + cl.remove_attendees([index])
1.713 + elif not cmd:
1.714 + pass
1.715 + else:
1.716 + continue
1.717 + break
1.718 +
1.719 + cl.show_attendees()
1.720 + print
1.721 +
1.722 + # Add suggested attendee (using index).
1.723 +
1.724 + elif args[0] in ("as", "attendee-suggested", "suggested-attendee"):
1.725 + try:
1.726 + index = int(args[1])
1.727 + cl.add_suggested_attendee(index)
1.728 + except ValueError:
1.729 + pass
1.730 + cl.show_attendees()
1.731 + print
1.732 +
1.733 + # Edit attendance.
1.734 +
1.735 + elif args[0] in ("A", "attend", "attendance"):
1.736 +
1.737 + args = args[1:]
1.738 +
1.739 + if not cl.is_attendee() and cl.is_organiser():
1.740 + cl.add_attendee(cl.user)
1.741 +
1.742 + # NOTE: Support delegation.
1.743 +
1.744 + if cl.can_edit_attendance():
1.745 + while True:
1.746 +
1.747 + # Obtain a command from any arguments.
1.748 +
1.749 + cmd = next_arg(args)
1.750 + if not cmd:
1.751 + cmd = read_input(" (a)ccept, (d)ecline, (t)entative (or return)> ")
1.752 + if cmd in ("a", "accept", "accepted", "attend"):
1.753 + cl.edit_attendance("ACCEPTED")
1.754 + elif cmd in ("d", "decline", "declined"):
1.755 + cl.edit_attendance("DECLINED")
1.756 + elif cmd in ("t", "tentative"):
1.757 + cl.edit_attendance("TENTATIVE")
1.758 + elif not cmd:
1.759 + pass
1.760 + else:
1.761 + continue
1.762 + break
1.763 +
1.764 + cl.show_attendees()
1.765 + print
1.766 +
1.767 + # Add or edit period.
1.768 +
1.769 + elif args[0] in ("p", "period"):
1.770 +
1.771 + args = args[1:]
1.772 + value = next_arg(args)
1.773 +
1.774 + if value and value.isdigit():
1.775 + index = int(value)
1.776 + else:
1.777 + index = None
1.778 +
1.779 + # Add a new period.
1.780 +
1.781 + if index is None:
1.782 + cl.add_period()
1.783 + cl.edit_period(-1)
1.784 +
1.785 + # Edit period (using index).
1.786 +
1.787 + else:
1.788 + period = cl.can_edit_period(index)
1.789 + if period:
1.790 + while True:
1.791 + show_period_raw(period)
1.792 +
1.793 + # Obtain a command from any arguments.
1.794 +
1.795 + cmd = next_arg(args)
1.796 + if not cmd:
1.797 + cmd = read_input(" (e)dit, (c)ancel, (u)ncancel (or return)> ")
1.798 + if cmd in ("e", "edit"):
1.799 + cl.edit_period(index, args)
1.800 + elif cmd in ("c", "cancel"):
1.801 + cl.cancel_periods([index])
1.802 + elif cmd in ("u", "uncancel", "restore"):
1.803 + cl.cancel_periods([index], False)
1.804 + elif not cmd:
1.805 + pass
1.806 + else:
1.807 + continue
1.808 + break
1.809 +
1.810 + cl.show_periods()
1.811 + print
1.812 +
1.813 + # Apply suggested period (using index).
1.814 +
1.815 + elif args[0] in ("ps", "period-suggested", "suggested-period"):
1.816 + try:
1.817 + index = int(args[1])
1.818 + cl.apply_suggested_period(index)
1.819 + except ValueError:
1.820 + pass
1.821 + cl.show_periods()
1.822 + print
1.823 +
1.824 + # Set the summary.
1.825 +
1.826 + elif args[0] in ("s", "summary"):
1.827 + cl.edit_summary(cmd.split(None, 1)[1])
1.828 + cl.show_object()
1.829 + print
1.830 +
1.831 + except EOFError:
1.832 + return
1.833 +
1.834 +def main(args):
1.835 + global echo
1.836 +
1.837 + # Parse command line arguments using the standard options plus some extra
1.838 + # options.
1.839 +
1.840 + args = parse_args(args, {
1.841 + "--echo" : ("echo", False),
1.842 + "-f" : ("filename", None),
1.843 + "--suppress-bcc" : ("suppress_bcc", False),
1.844 + "-u" : ("user", None),
1.845 + })
1.846 +
1.847 + echo = args["echo"]
1.848 + filename = args["filename"]
1.849 + sender = (args["senders"] or [None])[0]
1.850 + suppress_bcc = args["suppress_bcc"]
1.851 + user = args["user"]
1.852 +
1.853 + # Determine the user and sender identities.
1.854 +
1.855 + if sender and not user:
1.856 + user = get_uri(sender)
1.857 + elif user and not sender:
1.858 + sender = get_address(user)
1.859 + elif not sender and not user:
1.860 + print >>sys.stderr, "A sender or a user must be specified."
1.861 + sys.exit(1)
1.862 +
1.863 + # Open a store.
1.864 +
1.865 + store_type = args.get("store_type")
1.866 + store_dir = args.get("store_dir")
1.867 + preferences_dir = args.get("preferences_dir")
1.868 +
1.869 + store = get_store(store_type, store_dir)
1.870 + journal = None
1.871 +
1.872 + # Open a messenger for the user.
1.873 +
1.874 + messenger = Messenger(sender=sender, suppress_bcc=suppress_bcc)
1.875 +
1.876 + # Open a client for the user.
1.877 +
1.878 + cl = TextClient(user, messenger, store, journal, preferences_dir)
1.879 +
1.880 + # Read any input resource.
1.881 +
1.882 + if filename:
1.883 + objects = get_objects(filename)
1.884 +
1.885 + # Choose an object to edit.
1.886 +
1.887 + show_objects(objects, user, store)
1.888 + obj = select_object(cl, objects)
1.889 +
1.890 + # Exit without any object.
1.891 +
1.892 + if not obj:
1.893 + print >>sys.stderr, "Object not loaded."
1.894 + sys.exit(1)
1.895 +
1.896 + # Or create a new object.
1.897 +
1.898 + else:
1.899 + obj = cl.new_object()
1.900 +
1.901 + # Edit the object.
1.902 +
1.903 + edit_object(cl, obj)
1.904 +
1.905 +if __name__ == "__main__":
1.906 + main(sys.argv[1:])
1.907 +
1.908 +# vim: tabstop=4 expandtab shiftwidth=4