1 #!/usr/bin/env python 2 3 from email import message_from_file 4 from imiptools import get_handlers, parse_args 5 from imiptools.config import settings 6 from imiptools.content import get_objects_from_itip, handle_calendar_data, \ 7 handle_calendar_object, have_itip_part, \ 8 parse_itip_part 9 from imiptools.data import get_address, get_main_period, get_recurrence_periods, get_value, parse_object 10 from imiptools.dates import get_datetime_item, get_time, to_timezone 11 from imiptools.editing import EditingClient, PeriodError 12 from imiptools.handlers import person, person_outgoing 13 from imiptools.mail import Messenger 14 from imiptools.stores import get_journal, get_store 15 from imiptools.utils import decode_part, message_as_string 16 import sys, os 17 18 # User interface functions. 19 20 echo = False 21 22 def read_input(label): 23 s = raw_input(label).strip() 24 if echo: 25 print s 26 return s 27 28 def input_with_default(label, default): 29 return read_input(label % default) or default 30 31 def print_title(text): 32 print text 33 print len(text) * "-" 34 35 def write(s, filename): 36 f = filename and open(filename, "w") or None 37 try: 38 print >>(f or sys.stdout), s 39 finally: 40 if f: 41 f.close() 42 43 # Interpret an input file containing a calendar resource. 44 45 def get_itip_from_message(filename): 46 47 "Return iTIP details provided by 'filename'." 48 49 f = open(filename) 50 try: 51 msg = message_from_file(f) 52 finally: 53 f.close() 54 55 all_itip = [] 56 57 for part in msg.walk(): 58 if have_itip_part(part): 59 all_itip.append(parse_itip_part(part)) 60 61 return all_itip 62 63 def get_itip_from_data(filename, charset): 64 65 "Return objects provided by 'filename'." 66 67 f = open(filename) 68 try: 69 itip = parse_object(f, charset, "VCALENDAR") 70 finally: 71 f.close() 72 73 return [itip] 74 75 def show_objects(objects, user, store): 76 77 """ 78 Show details of 'objects', accessed by the given 'user' in the given 79 'store'. 80 """ 81 82 print 83 print_title("Objects") 84 print 85 86 for index, obj in enumerate(objects): 87 recurrenceid = obj.get_recurrenceid() 88 recurrence_label = recurrenceid and " %s" % recurrenceid or "" 89 print "(%d) Summary: %s (%s%s)" % (index, obj.get_value("SUMMARY"), obj.get_uid(), recurrence_label) 90 91 def show_requests(user, store): 92 93 "Show requests available to the given 'user' in the given 'store'." 94 95 requests = store.get_requests(user) 96 97 print 98 print_title("Requests") 99 print 100 101 if not requests: 102 print "No requests are pending." 103 return 104 105 for index, (uid, recurrenceid) in enumerate(requests): 106 obj = store.get_event(user, uid, recurrenceid) 107 recurrence_label = recurrenceid and " %s" % recurrenceid or "" 108 print "(%d) Summary: %s (%s%s)" % (index, obj.get_value("SUMMARY"), obj.get_uid(), recurrence_label) 109 110 def show_attendee(attendee_item, index): 111 112 "Show the 'attendee_item' (value and attributes) at 'index'." 113 114 attendee, attr = attendee_item 115 partstat = attr.get("PARTSTAT") 116 print "(%d) %s%s" % (index, attendee, partstat and " (%s)" % partstat or "") 117 118 def show_attendees_raw(attendee_map): 119 120 "Show the 'attendee_map' in a simple raw form." 121 122 for attendee, attr in attendee_map.items(): 123 print attendee 124 125 def show_periods(periods, errors=None): 126 127 "Show 'periods' with any indicated 'errors'." 128 129 main = get_main_period(periods) 130 if main: 131 show_period(main, 0, errors) 132 133 recurrences = get_recurrence_periods(periods) 134 if recurrences: 135 print 136 print_title("Recurrences") 137 for index, p in enumerate(recurrences): 138 show_period(p, index + 1, errors) 139 140 def show_period(p, index, errors=None): 141 142 "Show period 'p' at 'index' with any indicated 'errors'." 143 144 errors = errors and errors.get(index) 145 if p.replacement: 146 if p.cancelled: 147 label = "Cancelled" 148 else: 149 label = "Replaced" 150 else: 151 if p.new_replacement: 152 label = "To replace" 153 elif p.recurrenceid: 154 label = "Retained" 155 else: 156 label = "New" 157 158 error_label = errors and " (errors: %s)" % ", ".join(errors) or "" 159 print "(%d) %s%s:" % (index, label, error_label), p.get_start(), p.get_end(), p.origin 160 161 def show_periods_raw(periods): 162 163 "Show 'periods' in a simple raw form." 164 165 periods = periods[:] 166 periods.sort() 167 map(show_period_raw, periods) 168 169 def show_period_raw(p): 170 171 "Show period 'p' in a simple raw form." 172 173 print p.get_start(), p.get_end(), p.origin 174 175 def show_attendee_changes(new, modified, unmodified, removed): 176 177 "Show 'new', 'modified', 'unmodified' and 'removed' periods." 178 179 print 180 print_title("Changes to attendees") 181 print 182 print "New:" 183 show_attendees_raw(new) 184 print 185 print "Modified:" 186 show_attendees_raw(modified) 187 print 188 print "Unmodified:" 189 show_attendees_raw(unmodified) 190 print 191 print "Removed:" 192 show_attendees_raw(removed) 193 194 def show_period_classification(new, replaced, retained, cancelled, obsolete): 195 196 "Show 'new', 'replaced', 'retained', 'cancelled' and 'obsolete' periods." 197 198 print 199 print_title("Period classification") 200 print 201 print "New:" 202 show_periods_raw(new) 203 print 204 print "Replaced:" 205 show_periods_raw(replaced) 206 print 207 print "Retained:" 208 show_periods_raw(retained) 209 print 210 print "Cancelled:" 211 show_periods_raw(cancelled) 212 print 213 print "Obsolete:" 214 show_periods_raw(obsolete) 215 216 def show_changes(modified, unmodified, removed): 217 218 "Show 'modified', 'unmodified' and 'removed' periods." 219 220 print 221 print_title("Changes to periods") 222 print 223 print "Modified:" 224 show_periods_raw(modified) 225 print 226 print "Unmodified:" 227 show_periods_raw(unmodified) 228 print 229 print "Removed:" 230 show_periods_raw(removed) 231 232 def show_attendee_operations(to_invite, to_cancel, to_modify): 233 234 "Show attendees 'to_invite', 'to_cancel' and 'to_modify'." 235 236 print 237 print_title("Attendee update operations") 238 print 239 print "To invite:" 240 show_attendees_raw(to_invite) 241 print 242 print "To cancel:" 243 show_attendees_raw(to_cancel) 244 print 245 print "To modify:" 246 show_attendees_raw(to_modify) 247 248 def show_period_operations(to_unschedule, to_reschedule, to_add, to_exclude, to_set, 249 all_unscheduled, all_rescheduled): 250 251 """ 252 Show operations for periods 'to_unschedule', 'to_reschedule', 'to_add', 253 'to_exclude' and 'to_set' (for updating other calendar participants), and 254 for periods 'all_unscheduled' and 'all_rescheduled' (for publishing event 255 state). 256 """ 257 258 print 259 print_title("Period update and publishing operations") 260 print 261 print "Unschedule:" 262 show_periods_raw(to_unschedule) 263 print 264 print "Reschedule:" 265 show_periods_raw(to_reschedule) 266 print 267 print "Added:" 268 show_periods_raw(to_add) 269 print 270 print "Excluded:" 271 show_periods_raw(to_exclude) 272 print 273 print "Set in object:" 274 show_periods_raw(to_set) 275 print 276 print "All unscheduled:" 277 show_periods_raw(all_unscheduled) 278 print 279 print "All rescheduled:" 280 show_periods_raw(all_rescheduled) 281 282 class TextClient(EditingClient): 283 284 "Simple client with textual output." 285 286 def new_object(self): 287 288 "Create a new object with the current time." 289 290 utcnow = get_time() 291 now = to_timezone(utcnow, self.get_tzid()) 292 obj = EditingClient.new_object(self, "VEVENT") 293 obj.set_value("SUMMARY", "New event") 294 obj["DTSTART"] = [get_datetime_item(now)] 295 obj["DTEND"] = [get_datetime_item(now)] 296 return obj 297 298 # Editing methods involving interaction. 299 300 def edit_attendee(self, index): 301 302 "Edit the attendee at 'index'." 303 304 t = self.can_edit_attendee(index) 305 if t: 306 attendees = self.state.get("attendees") 307 attendee, attr = t 308 del attendees[attendee] 309 attendee = input_with_default("Attendee (%s)? ", attendee) 310 attendees[attendee] = attr 311 312 def edit_period(self, index, args=None): 313 period = self.can_edit_period(index) 314 if period: 315 edit_period(period, args) 316 period.cancelled = False 317 318 # Sort the periods after this change. 319 320 periods = self.state.get("periods") 321 periods.sort() 322 323 def edit_summary(self, summary=None): 324 if self.can_edit_properties(): 325 if not summary: 326 summary = input_with_default("Summary (%s)? ", self.state.get("summary")) 327 self.state.set("summary", summary) 328 329 def finish(self): 330 try: 331 EditingClient.finish(self) 332 except PeriodError: 333 print "Errors exist in the periods." 334 return 335 336 # Diagnostic methods. 337 338 def show_period_classification(self): 339 try: 340 new, replaced, retained, cancelled, obsolete = self.classify_periods() 341 show_period_classification(new, replaced, retained, cancelled, obsolete) 342 except PeriodError: 343 print 344 print "Errors exist in the periods." 345 346 def show_changes(self): 347 try: 348 modified, unmodified, removed = self.classify_period_changes() 349 show_changes(modified, unmodified, removed) 350 except PeriodError: 351 print "Errors exist in the periods." 352 353 is_changed = self.properties_changed() 354 if is_changed: 355 print 356 print "Properties changed:", ", ".join(is_changed) 357 new, modified, unmodified, removed = self.classify_attendee_changes() 358 show_attendee_changes(new, modified, unmodified, removed) 359 360 def show_operations(self): 361 is_changed = self.properties_changed() 362 363 try: 364 to_unschedule, to_reschedule, to_add, to_exclude, to_set, \ 365 all_unscheduled, all_rescheduled = self.classify_period_operations() 366 show_period_operations(to_unschedule, to_reschedule, to_add, 367 to_exclude, to_set, 368 all_unscheduled, all_rescheduled) 369 except PeriodError: 370 print "Errors exist in the periods." 371 372 to_invite, to_cancel, to_modify = self.classify_attendee_operations() 373 show_attendee_operations(to_invite, to_cancel, to_modify) 374 375 # Output methods. 376 377 def show_message(self, message, plain=False, filename=None): 378 if plain: 379 decode_part(message) 380 write(message_as_string(message), filename) 381 382 def show_cancel_message(self, plain=False, filename=None): 383 384 "Show the cancel message for uninvited attendees." 385 386 message = self.prepare_cancel_message() 387 if message: 388 self.show_message(message, plain, filename) 389 390 def show_publish_message(self, plain=False, filename=None): 391 392 "Show the publishing message for the updated event." 393 394 message = self.prepare_publish_message() 395 self.show_message(message, plain, filename) 396 397 def show_update_message(self, plain=False, filename=None): 398 399 "Show the update message for the updated event." 400 401 message = self.prepare_update_message() 402 if message: 403 self.show_message(message, plain, filename) 404 405 # General display methods. 406 407 def show_object(self): 408 print 409 print_title("Object details") 410 print 411 print "Summary:", self.state.get("summary") 412 print 413 print "Organiser:", self.state.get("organiser") 414 self.show_attendees() 415 self.show_periods() 416 self.show_suggested_attendees() 417 self.show_suggested_periods() 418 self.show_conflicting_periods() 419 420 def show_attendees(self): 421 print 422 print_title("Attendees") 423 attendees = self.state.get("attendees") 424 for index, attendee_item in enumerate(attendees.items()): 425 show_attendee(attendee_item, index) 426 427 def show_periods(self): 428 print 429 print_title("Periods") 430 show_periods(self.state.get("periods"), self.state.get("period_errors")) 431 432 def show_suggested_attendees(self): 433 current_attendee = None 434 for index, (attendee, suggested_item) in enumerate(self.state.get("suggested_attendees")): 435 if attendee != current_attendee: 436 print 437 print_title("Attendees suggested by %s" % attendee) 438 current_attendee = attendee 439 show_attendee(suggested_item, index) 440 441 def show_suggested_periods(self): 442 periods = self.state.get("suggested_periods") 443 current_attendee = None 444 index = 0 445 for attendee, period, operation in periods: 446 if attendee != current_attendee: 447 print 448 print_title("Periods suggested by %s" % attendee) 449 current_attendee = attendee 450 show_period(period, index) 451 print " %s" % (operation == "add" and "Add this period" or "Remove this period") 452 index += 1 453 454 def show_conflicting_periods(self): 455 conflicts = self.get_conflicting_periods() 456 if not conflicts: 457 return 458 print 459 print_title("Conflicting periods") 460 461 conflicts = list(conflicts) 462 conflicts.sort() 463 464 for p in conflicts: 465 print p.summary, p.uid, p.get_start(), p.get_end() 466 467 # Interaction functions. 468 469 def expand_arg(args): 470 if args[0] and args[0][1:].isdigit(): 471 args[:1] = [args[0][0], args[0][1:]] 472 473 def get_filename_arg(cmd): 474 return (cmd.split()[1:] or [None])[0] 475 476 def next_arg(args): 477 if args: 478 arg = args[0] 479 del args[0] 480 return arg 481 return None 482 483 def edit_period(period, args=None): 484 485 "Edit the given 'period'." 486 487 print "Editing start (%s)" % period.get_start() 488 edit_date(period.start, args) 489 print "Editing end (%s)" % period.get_end() 490 edit_date(period.end, args) 491 492 def edit_date(date, args=None): 493 494 "Edit the given 'date' object attributes." 495 496 date.date = next_arg(args) or input_with_default("Date (%s)? ", date.date) 497 date.hour = next_arg(args) or input_with_default("Hour (%s)? ", date.hour) 498 date.minute = next_arg(args) or input_with_default("Minute (%s)? ", date.minute) 499 date.second = next_arg(args) or input_with_default("Second (%s)? ", date.second) 500 date.tzid = next_arg(args) or input_with_default("Time zone (%s)? ", date.tzid) 501 date.reset() 502 503 def select_object(cl, objects): 504 print 505 506 if objects: 507 label = "Select object number or (n)ew object or (q)uit> " 508 else: 509 label = "Select (n)ew object or (q)uit> " 510 511 while True: 512 try: 513 cmd = read_input(label) 514 except EOFError: 515 return None 516 517 if cmd.isdigit(): 518 index = int(cmd) 519 if 0 <= index < len(objects): 520 obj = objects[index] 521 return cl.load_object(obj.get_uid(), obj.get_recurrenceid()) 522 523 elif cmd in ("n", "new"): 524 return cl.new_object() 525 elif cmd in ("q", "quit", "exit"): 526 return None 527 528 def show_commands(): 529 print 530 print_title("Editing commands") 531 print 532 print """\ 533 a [ <uri> ] 534 attendee [ <uri> ] 535 Add attendee 536 537 A, attend, attendance 538 Change attendance/participation 539 540 a<digit> 541 attendee <digit> 542 Select attendee from list 543 544 as<digit> 545 Add suggested attendee from list 546 547 f, finish 548 Finish editing, confirming changes, proceeding to messaging 549 550 h, help, ? 551 Show this help message 552 553 l, list, show 554 List/show all event details 555 556 p, period 557 Add new period 558 559 p<digit> 560 period <digit> 561 Select period from list 562 563 ps<digit> 564 Add or remove suggested period from list 565 566 q, quit, exit 567 Exit/quit this program 568 569 r, reload, reset, restart 570 Reset event periods (return to editing mode, if already finished) 571 572 s, summary 573 Set event summary 574 """ 575 576 print_title("Messaging commands") 577 print 578 print """\ 579 S, send 580 Send messages to recipients and to self, if appropriate 581 """ 582 583 print_title("Diagnostic commands") 584 print 585 print """\ 586 c, class, classification 587 Show period classification 588 589 C, changes 590 Show changes made by editing 591 592 o, ops, operations 593 Show update operations 594 595 RECURRENCE-ID [ <filename> ] 596 Show event recurrence identifier, writing to <filename> if specified 597 598 UID [ <filename> ] 599 Show event unique identifier, writing to <filename> if specified 600 """ 601 602 print_title("Message inspection commands") 603 print 604 print """\ 605 P [ <filename> ] 606 publish [ <filename> ] 607 Show publishing message, writing to <filename> if specified 608 609 R [ <filename> ] 610 remove [ <filename> ] 611 cancel [ <filename> ] 612 Show cancellation message sent to uninvited/removed recipients, writing to 613 <filename> if specified 614 615 U [ <filename> ] 616 update [ <filename> ] 617 Show update message, writing to <filename> if specified 618 """ 619 620 def edit_object(cl, obj, handle_outgoing=False): 621 cl.show_object() 622 print 623 624 try: 625 while True: 626 role = cl.is_organiser() and "Organiser" or "Attendee" 627 status = cl.state.get("finished") and " (editing complete)" or "" 628 629 cmd = read_input("%s%s> " % (role, status)) 630 631 args = cmd.split() 632 633 if not args or not args[0]: 634 continue 635 636 # Check the status of the periods. 637 638 if cmd in ("c", "class", "classification"): 639 cl.show_period_classification() 640 print 641 642 elif cmd in ("C", "changes"): 643 cl.show_changes() 644 print 645 646 # Finish editing. 647 648 elif cmd in ("f", "finish"): 649 cl.finish() 650 651 # Help. 652 653 elif cmd in ("h", "?", "help"): 654 show_commands() 655 656 # Show object details. 657 658 elif cmd in ("l", "list", "show"): 659 cl.show_object() 660 print 661 662 # Show the operations. 663 664 elif cmd in ("o", "ops", "operations"): 665 cl.show_operations() 666 print 667 668 # Quit or exit. 669 670 elif cmd in ("q", "quit", "exit"): 671 break 672 673 # Restart editing. 674 675 elif cmd in ("r", "reload", "reset", "restart"): 676 obj = cl.load_object(obj.get_uid(), obj.get_recurrenceid()) 677 if not obj: 678 obj = cl.new_object() 679 cl.reset() 680 cl.show_object() 681 print 682 683 # Show UID details. 684 685 elif args[0] == "UID": 686 filename = get_filename_arg(cmd) 687 write(obj.get_uid(), filename) 688 689 elif args[0] == "RECURRENCE-ID": 690 filename = get_filename_arg(cmd) 691 write(obj.get_recurrenceid() or "", filename) 692 693 # Post-editing operations. 694 695 elif cl.state.get("finished"): 696 697 # Show messages. 698 699 if args[0] in ("P", "publish"): 700 filename = get_filename_arg(cmd) 701 cl.show_publish_message(plain=not filename, filename=filename) 702 703 elif args[0] in ("R", "remove", "cancel"): 704 filename = get_filename_arg(cmd) 705 cl.show_cancel_message(plain=not filename, filename=filename) 706 707 elif args[0] in ("U", "update"): 708 filename = get_filename_arg(cmd) 709 cl.show_update_message(plain=not filename, filename=filename) 710 711 # Definitive finishing action. 712 713 elif args[0] in ("S", "send"): 714 715 # Send update and cancellation messages. 716 717 did_send = False 718 719 message = cl.prepare_update_message() 720 if message: 721 cl.send_message(message, cl.get_recipients()) 722 did_send = True 723 724 to_cancel = cl.state.get("attendees_to_cancel") 725 if to_cancel: 726 message = cl.prepare_cancel_message() 727 if message: 728 cl.send_message(message, to_cancel) 729 did_send = True 730 731 # Process the object using the person outgoing handler. 732 733 if handle_outgoing: 734 735 # Handle the parent object plus any rescheduled periods. 736 737 unscheduled_objects, rescheduled_objects, added_objects = \ 738 cl.get_publish_objects() 739 740 handlers = get_handlers(cl, person_outgoing.handlers, 741 [get_address(cl.user)]) 742 743 handle_calendar_object(cl.obj, handlers, "PUBLISH") 744 for o in unscheduled_objects: 745 handle_calendar_object(o, handlers, "CANCEL") 746 for o in rescheduled_objects: 747 handle_calendar_object(o, handlers, "PUBLISH") 748 for o in added_objects: 749 handle_calendar_object(o, handlers, "ADD") 750 751 # Otherwise, send a message to self with the event details. 752 753 else: 754 message = cl.prepare_publish_message() 755 if message: 756 cl.send_message_to_self(message) 757 did_send = True 758 759 # Exit if sending occurred. 760 761 if did_send: 762 break 763 else: 764 print "No messages sent. Try making edits or exit manually." 765 766 # Editing operations. 767 768 elif not cl.state.get("finished"): 769 770 # Expand short-form arguments. 771 772 expand_arg(args) 773 774 # Add or edit attendee. 775 776 if args[0] in ("a", "attendee"): 777 778 args = args[1:] 779 value = next_arg(args) 780 781 if value and value.isdigit(): 782 index = int(value) 783 else: 784 try: 785 index = cl.find_attendee(value) 786 except ValueError: 787 index = None 788 789 # Add an attendee. 790 791 if index is None: 792 cl.add_attendee(value) 793 if not value: 794 cl.edit_attendee(-1) 795 796 # Edit attendee (using index). 797 798 else: 799 attendee_item = cl.can_remove_attendee(index) 800 if attendee_item: 801 while True: 802 show_attendee(attendee_item, index) 803 804 # Obtain a command from any arguments. 805 806 cmd = next_arg(args) 807 if not cmd: 808 cmd = read_input(" (e)dit, (r)emove (or return)> ") 809 if cmd in ("e", "edit"): 810 cl.edit_attendee(index) 811 elif cmd in ("r", "remove"): 812 cl.remove_attendees([index]) 813 elif not cmd: 814 pass 815 else: 816 continue 817 break 818 819 cl.show_attendees() 820 print 821 822 # Add suggested attendee (using index). 823 824 elif args[0] in ("as", "attendee-suggested", "suggested-attendee"): 825 try: 826 index = int(args[1]) 827 cl.add_suggested_attendee(index) 828 except ValueError: 829 pass 830 cl.show_attendees() 831 print 832 833 # Edit attendance. 834 835 elif args[0] in ("A", "attend", "attendance"): 836 837 args = args[1:] 838 839 if not cl.is_attendee() and cl.is_organiser(): 840 cl.add_attendee(cl.user) 841 842 # NOTE: Support delegation. 843 844 if cl.can_edit_attendance(): 845 while True: 846 847 # Obtain a command from any arguments. 848 849 cmd = next_arg(args) 850 if not cmd: 851 cmd = read_input(" (a)ccept, (d)ecline, (t)entative (or return)> ") 852 if cmd in ("a", "accept", "accepted", "attend"): 853 cl.edit_attendance("ACCEPTED") 854 elif cmd in ("d", "decline", "declined"): 855 cl.edit_attendance("DECLINED") 856 elif cmd in ("t", "tentative"): 857 cl.edit_attendance("TENTATIVE") 858 elif not cmd: 859 pass 860 else: 861 continue 862 break 863 864 cl.show_attendees() 865 print 866 867 # Add or edit period. 868 869 elif args[0] in ("p", "period"): 870 871 args = args[1:] 872 value = next_arg(args) 873 874 if value and value.isdigit(): 875 index = int(value) 876 else: 877 index = None 878 879 # Add a new period. 880 881 if index is None: 882 cl.add_period() 883 cl.edit_period(-1) 884 885 # Edit period (using index). 886 887 else: 888 period = cl.can_edit_period(index) 889 if period: 890 while True: 891 show_period_raw(period) 892 893 # Obtain a command from any arguments. 894 895 cmd = next_arg(args) 896 if not cmd: 897 cmd = read_input(" (e)dit, (c)ancel, (u)ncancel (or return)> ") 898 if cmd in ("e", "edit"): 899 cl.edit_period(index, args) 900 elif cmd in ("c", "cancel"): 901 cl.cancel_periods([index]) 902 elif cmd in ("u", "uncancel", "restore"): 903 cl.cancel_periods([index], False) 904 elif not cmd: 905 pass 906 else: 907 continue 908 break 909 910 cl.show_periods() 911 print 912 913 # Apply suggested period (using index). 914 915 elif args[0] in ("ps", "period-suggested", "suggested-period"): 916 try: 917 index = int(args[1]) 918 cl.apply_suggested_period(index) 919 except ValueError: 920 pass 921 cl.show_periods() 922 print 923 924 # Set the summary. 925 926 elif args[0] in ("s", "summary"): 927 t = cmd.split(None, 1) 928 cl.edit_summary(len(t) > 1 and t[1] or None) 929 cl.show_object() 930 print 931 932 except EOFError: 933 return 934 935 def main(args): 936 global echo 937 938 if "--help" in args: 939 show_help(os.path.split(sys.argv[0])[-1]) 940 return 941 942 # Parse command line arguments using the standard options plus some extra 943 # options. 944 945 args = parse_args(args, { 946 "--calendar-data" : ("calendar_data", False), 947 "--charset" : ("charset", "utf-8"), 948 "--echo" : ("echo", False), 949 "-f" : ("filename", None), 950 "--handle-data" : ("handle_data", False), 951 "--suppress-bcc" : ("suppress_bcc", False), 952 "-u" : ("user", None), 953 }) 954 955 charset = args["charset"] 956 calendar_data = args["calendar_data"] 957 echo = args["echo"] 958 filename = args["filename"] 959 handle_data = args["handle_data"] 960 sender = (args["senders"] or [None])[0] 961 suppress_bcc = args["suppress_bcc"] 962 user = args["user"] 963 964 # Determine the user and sender identities. 965 966 if sender and not user: 967 user = get_uri(sender) 968 elif user and not sender: 969 sender = get_address(user) 970 elif not sender and not user: 971 print >>sys.stderr, "A sender or a user must be specified." 972 sys.exit(1) 973 974 # Open a store. 975 976 store_type = args.get("store_type") 977 store_dir = args.get("store_dir") 978 preferences_dir = args.get("preferences_dir") 979 980 store = get_store(store_type, store_dir) 981 journal = None 982 983 # Open a messenger for the user. 984 985 messenger = Messenger(sender=sender, suppress_bcc=suppress_bcc) 986 987 # Open a client for the user. 988 989 cl = TextClient(user, messenger, store, journal, preferences_dir) 990 991 # Read any input resource. 992 993 if filename: 994 if calendar_data: 995 all_itip = get_itip_from_data(filename, charset) 996 else: 997 all_itip = get_itip_from_message(filename) 998 999 objects = [] 1000 1001 # Process the objects using the person handler. 1002 1003 if handle_data: 1004 for itip in all_itip: 1005 objects += handle_calendar_data(itip, get_handlers(cl, person.handlers, None)) 1006 1007 # Or just obtain objects from the data. 1008 1009 else: 1010 for itip in all_itip: 1011 objects += get_objects_from_itip(itip, ["VEVENT"]) 1012 1013 # Choose an object to edit. 1014 1015 show_objects(objects, user, store) 1016 obj = select_object(cl, objects) 1017 1018 # Exit without any object. 1019 1020 if not obj: 1021 print >>sys.stderr, "No object loaded." 1022 sys.exit(1) 1023 1024 # Or create a new object. 1025 1026 else: 1027 obj = cl.new_object() 1028 1029 # Edit the object. 1030 1031 edit_object(cl, obj, handle_outgoing=handle_data) 1032 1033 def show_help(progname): 1034 print >>sys.stderr, help_text % progname 1035 1036 help_text = """\ 1037 Usage: %s -s <sender> | -u <user> \\ 1038 [ -f <filename> ] \\ 1039 [ --calendar-data --charset ] \\ 1040 [ --handle-data ] \\ 1041 [ -T <store type ] [ -S <store directory> ] \\ 1042 [ -p <preferences directory> ] \\ 1043 [ --echo ] 1044 1045 Identity options: 1046 1047 -s Indicate the user by specifying a sender address 1048 -u Indicate the user by specifying their URI 1049 1050 Input options: 1051 1052 -f Indicates a filename containing a MIME-encoded message or calendar object 1053 1054 --calendar-data Indicates that the specified file contains a calendar object 1055 as opposed to a mail message 1056 --charset Specifies the character encoding used by a calendar object 1057 description 1058 1059 Processing options: 1060 1061 --handle-data Cause the input to be handled and stored in the configured 1062 data store 1063 1064 Configuration options (overriding configured defaults): 1065 1066 -p Indicates the location of user preference directories 1067 -S Indicates the location of the calendar data store containing user storage 1068 directories 1069 -T Indicates the store type (the configured value if omitted) 1070 1071 Output options: 1072 1073 --echo Echo received input, useful if consuming input from the 1074 standard input stream and producing a log of the program's 1075 activity 1076 """ 1077 1078 if __name__ == "__main__": 1079 main(sys.argv[1:]) 1080 1081 # vim: tabstop=4 expandtab shiftwidth=4