# HG changeset patch # User Paul Boddie # Date 1515790594 -3600 # Node ID ffcb65f7b8e5a579716bb7f7c2e1a8582c73c1e4 # Parent 9286159dc46649635e95ad097d114c9677615198 Added support for rule editing, also tidying up command processing and adding support for loading stored objects using identifier details. Added an option to show the store and preferences configuration. diff -r 9286159dc466 -r ffcb65f7b8e5 imip_text_client.py --- a/imip_text_client.py Fri Jan 12 21:54:14 2018 +0100 +++ b/imip_text_client.py Fri Jan 12 21:56:34 2018 +0100 @@ -3,7 +3,7 @@ """ A text interface to received and new events. -Copyright (C) 2017 Paul Boddie +Copyright (C) 2017, 2018 Paul Boddie This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -25,9 +25,12 @@ from imiptools.content import get_objects_from_itip, handle_calendar_data, \ handle_calendar_object, have_itip_part, \ is_cancel_itip, parse_itip_part -from imiptools.data import get_address, get_main_period, get_recurrence_periods, get_value, parse_object +from imiptools.data import get_address, get_periods_using_selector, \ + get_main_period, get_recurrence_periods, \ + get_value, parse_object from imiptools.dates import get_datetime_item, get_time, to_timezone -from imiptools.editing import EditingClient, PeriodError +from imiptools.editing import EditingClient, PeriodError, \ + form_periods_from_periods from imiptools.handlers import person, person_outgoing from imiptools.mail import Messenger from imiptools.stores import get_journal, get_store @@ -60,6 +63,34 @@ return read_input(label % default) or default +def to_int_or_none(value): + + "Return 'value' as an integer or None for other inputs." + + try: + return int(value) + except (TypeError, ValueError): + return None + +def format_value_ranges(ranges, null_value="-"): + + "Format 'ranges' as a single descriptive string." + + l = [] + + for value_range in ranges: + if isinstance(value_range, tuple): + start, end = value_range + l.append("%s...%s" % (start, end)) + elif isinstance(value_range, list): + l.append(", ".join(value_range)) + elif isinstance(value_range, dict): + l.append(", ".join(value_range.keys())) + else: + l.append(value_range or null_value) + + return ", ".join(l) + def print_title(text): "Print 'text' with simple, fixed-width styling as a title." @@ -135,6 +166,8 @@ return [itip] +# Object and request display. + def show_objects(objects, user, store): """ @@ -170,6 +203,8 @@ recurrence_label = recurrenceid and " %s" % recurrenceid or "" print "(%d) Summary: %s (%s%s)" % (index, obj.get_value("SUMMARY"), obj.get_uid(), recurrence_label) +# Object details display. + def show_attendee(attendee_item, index): "Show the 'attendee_item' (value and attributes) at 'index'." @@ -233,68 +268,69 @@ print p.get_start(), p.get_end(), p.origin -def show_rule(rrule): - - "Show recurrence rule specification 'rrule'." +def show_rule(selectors): - print - print "Recurrence rule:" - - count = None - freq_interval = [] - selections = [] + "Show recurrence rule specification 'selectors'." # Collect limit, selection and frequency details. - for selector in vRecurrence.order_qualifiers(vRecurrence.get_qualifiers(rrule)): - - # COUNT + for i, selector in enumerate(selectors): + prefix = "(%d) " % i + show_rule_selector(selector, prefix) - if isinstance(selector, vRecurrence.LimitSelector): - count = selector.args["values"][0] + print + print vRecurrence.to_string(selectors) - # BYSETPOS +def show_rule_selector(selector, prefix=""): + + "Show the rule 'selector', employing any given 'prefix' for formatting." - elif isinstance(selector, vRecurrence.PositionSelector): - for value in selector.args["values"]: - selections.append(("-", get_frequency(selector.level), str(value))) + # COUNT - # BY... + if isinstance(selector, vRecurrence.LimitSelector): + print "%sAt most %d occurrences" % (prefix, selector.args["values"][0]) + + # BYSETPOS - elif isinstance(selector, vRecurrence.Enum): - for value in selector.args["values"]: - selections.append(("-", get_frequency(selector.level), str(value))) + elif isinstance(selector, vRecurrence.PositionSelector): + for value in selector.get_positions(): + print "%sSelect occurrence #%d" % (prefix, value) + prefix = len(prefix) * " " - # BYWEEKDAY + # BYWEEKDAY - elif isinstance(selector, vRecurrence.WeekDayFilter): - for value, index in selector.args["values"]: - selections.append((index >= 0 and "Start" or "End", get_weekday(value), str(index))) + elif isinstance(selector, vRecurrence.WeekDayFilter): + for value, index in selector.get_values(): + print "%sSelect occurrence #%d (from %s) of weekday %s" % ( + prefix, abs(index), index >= 0 and "start" or "end", + get_weekday(value)) + prefix = len(prefix) * " " - # YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDLY - - elif isinstance(selector, vRecurrence.Pattern): - freq_interval.append((get_frequency(selector.level), str(selector.args.get("interval", 1)))) - - # Show the details. + # BY... - if freq_interval: - print - print_table([("Frequency", "Interval")] + freq_interval) + elif isinstance(selector, vRecurrence.Enum): + for value in selector.get_values(): + print "%sSelect %s %r" % (prefix, get_resolution(selector.level), + value) + prefix = len(prefix) * " " + + # YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDLY - if selections: - print - print_table([("From...", "Selecting", "Instance (1, 2, ...)")] + selections) + elif isinstance(selector, vRecurrence.Pattern): + print "%sEach %s with interval %d" % (prefix, + get_resolution(selector.level), selector.args.get("interval", 1)) - if count: - print - print "At most", count, "occurrences." +def get_resolution(level): -def get_frequency(level): - levels = ["Year", "Month", "Week", None, None, "Day", "Hour", "Minute", "Second"] + "Return a textual description of the given resolution 'level'." + + levels = ["year", "month", "week", "day in year", "day in month", "day", "hour", "minute", "second"] return levels[level] def get_weekday(weekday): + + "Return the name of the given 1-based 'weekday' number." + weekdays = [None, "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] return weekdays[weekday] @@ -421,8 +457,86 @@ obj["DTEND"] = [get_datetime_item(now)] return obj + def handle_outgoing_object(self): + + "Handle the current object using the outgoing handlers." + + unscheduled_objects, rescheduled_objects, added_objects = \ + self.get_publish_objects() + + handlers = get_handlers(self, person_outgoing.handlers, + [get_address(self.user)]) + + # Handle the parent object plus any altered periods. + + handle_calendar_object(self.obj, handlers, "PUBLISH") + + for o in unscheduled_objects: + handle_calendar_object(o, handlers, "CANCEL") + + for o in rescheduled_objects: + handle_calendar_object(o, handlers, "PUBLISH") + + for o in added_objects: + handle_calendar_object(o, handlers, "ADD") + + def update_periods_from_rule(self): + + "Update the periods from the rule." + + selectors = self.state.get("rule") + periods = self.state.get("periods") + + main_period = get_main_period(periods) + tzid = main_period.get_tzid() + + start = main_period.get_start() + end = self.get_window_end() or None + + selector = vRecurrence.get_selector(start, selectors) + until = None + inclusive = False + + # Generate the periods from the rule. + + rule_periods = form_periods_from_periods( + get_periods_using_selector(selector, main_period, tzid, + start, end, inclusive)) + + # Retain any applicable replacement periods that either modify or cancel + # rule periods. + # NOTE: To be done. + + self.state.set("periods", rule_periods) + # Editing methods involving interaction. + def add_rule_selectors(self): + + "Add rule selectors to the rule details." + + selectors = self.state.get("rule") + + while True: + + # Obtain a command from any arguments. + + s = read_input("Selector: (c)ount, (f)requency, (s)election (or return)> ") + args = s.split() + cmd = next_arg(args) + + if cmd in ("c", "count", "limit"): + add_rule_selector_count(selectors, args) + elif cmd in ("f", "freq", "frequency"): + add_rule_selector_frequency(selectors, args) + elif cmd in ("s", "select", "selection"): + add_rule_selector_selection(selectors, args) + + # Remain in the loop unless explicitly terminated. + + elif not cmd or cmd == "end": + break + def edit_attendee(self, index): "Edit the attendee at 'index'." @@ -436,6 +550,9 @@ attendees[attendee] = attr def edit_period(self, index, args=None): + + "Edit the period at 'index'." + period = self.can_edit_period(index) if period: edit_period(period, args) @@ -451,13 +568,64 @@ periods = self.state.get("periods") periods.sort() + def edit_rule_selector(self, index, args): + + "Edit the selector having the given 'index'." + + selectors = self.state.get("rule") + selector = self.can_edit_rule_selector(index) + + if not selector: + return + + while True: + show_rule_selector(selector) + + # Obtain a command from any arguments. + + cmd = next_arg(args) + if not cmd: + s = read_input("Selector: (e)dit, (r)emove (or return)> ") + args = s.split() + cmd = next_arg(args) + + # Edit an existing selector. + + if cmd in ("e", "edit"): + if isinstance(selector, vRecurrence.LimitSelector): + add_rule_selector_count(selectors, args, selector) + elif isinstance(selector, vRecurrence.Pattern): + add_rule_selector_frequency(selectors, args, selector) + else: + add_rule_selector_selection(selectors, args, selector) + + # Remove an existing selector. + + elif cmd in ("r", "remove"): + del selectors[index] + + # Exit if requested or after a successful + # operation. + + elif not cmd: + pass + else: + continue + break + def edit_summary(self, summary=None): + + "Edit or set the 'summary'." + if self.can_edit_properties(): if not summary: summary = input_with_default("Summary (%s)? ", self.state.get("summary")) self.state.set("summary", summary) def finish(self): + + "Finish editing, warning of errors if any occur." + try: EditingClient.finish(self) except PeriodError: @@ -467,6 +635,9 @@ # Diagnostic methods. def show_period_classification(self): + + "Show the classification of the periods." + try: new, replaced, retained, cancelled, obsolete = self.classify_periods() show_period_classification(new, replaced, retained, cancelled, obsolete) @@ -475,6 +646,9 @@ print "Errors exist in the periods." def show_changes(self): + + "Show how the periods have changed." + try: modified, unmodified, removed = self.classify_period_changes() show_changes(modified, unmodified, removed) @@ -489,6 +663,9 @@ show_attendee_changes(new, modified, unmodified, removed) def show_operations(self): + + "Show the operations required to change the periods for recipients." + is_changed = self.properties_changed() try: @@ -506,6 +683,12 @@ # Output methods. def show_message(self, message, plain=False, filename=None): + + """ + Show the given mail 'message', decoding to plain text if 'plain' is set + to a true value, writing it to 'filename' if indicated. + """ + if plain: decode_part(message) write(message_as_string(message), filename) @@ -544,9 +727,12 @@ print "Organiser:", self.state.get("organiser") self.show_attendees() self.show_periods() + self.show_rule() self.show_suggested_attendees() self.show_suggested_periods() self.show_conflicting_periods() + print + print "Object is", self.obj.is_shared() and "shared" or "not shared" def show_attendees(self): print @@ -558,10 +744,14 @@ def show_periods(self): print print_title("Periods") - rrule = self.obj.get_value("RRULE") show_periods(self.state.get("periods"), self.state.get("period_errors")) - if rrule: - show_rule(rrule) + + def show_rule(self): + selectors = self.state.get("rule") + if selectors: + print + print_title("Period recurrence rule") + show_rule(selectors) def show_suggested_attendees(self): current_attendee = None @@ -601,19 +791,39 @@ # Interaction functions. def expand_arg(args): + + """ + Expand the first argument in 'args' to a pair of arguments if having the + form ... + """ + if args[0] and args[0][1:].isdigit(): args[:1] = [args[0][0], args[0][1:]] -def get_filename_arg(cmd): - return (cmd.split()[1:] or [None])[0] +def get_text_arg(s): + + """ + Split 's' after the first whitespace occurrence, returning the remaining + text or None if no such text exists. + """ + + return (s.split(None, 1)[1:] or [None])[0] def next_arg(args): + + """ + Return the first argument from 'args', removing it, or return None if no + arguments are left. + """ + if args: arg = args[0] del args[0] return arg return None +# Editing functions. + def edit_period(period, args=None): "Edit the given 'period'." @@ -634,7 +844,224 @@ date.tzid = next_arg(args) or input_with_default("Time zone (%s)? ", date.tzid) date.reset() +def add_rule_selector_count(selectors, args, selector=None): + + "Add to 'selectors' a selector imposing a count restriction." + + while True: + arg = next_arg(args) + if not arg: + if selector: + arg = input_with_default("Number of occurrences (%d)? ", + selector.get_limit()) + else: + arg = read_input("Number of occurrences? ") + + count = to_int_or_none(arg) + + if count is None: + arg = None + continue + + # Change or add selector. + + selector = selector or selectors and \ + isinstance(selectors[0], vRecurrence.LimitSelector) and \ + selectors[0] or None + + if not selector: + selector = vRecurrence.new_selector("COUNT") + selectors.insert(0, selector) + + selector.set_limit(count) + break + +def add_rule_selector_frequency(selectors, args, selector=None): + + "Add to 'selectors' a selector for a frequency." + + while not selector: + arg = next_arg(args) + if not arg: + arg = read_input("Select (y)early, (M)onthly, (w)eekly, (d)aily, " + "(h)ourly, (m)inutely, (s)econdly (or return)? ") + + if not arg: + return + + arg_lower = arg.lower() + + if arg_lower in ("y", "year", "yearly"): + qualifier = "YEARLY" + elif arg == "M" or arg_lower in ("month", "monthly"): + qualifier = "MONTHLY" + elif arg_lower in ("w", "week", "weekly"): + qualifier = "WEEKLY" + elif arg_lower in ("d", "day", "daily"): + qualifier = "DAILY" + elif arg_lower in ("h", "hour", "hourly"): + qualifier = "HOURLY" + elif arg_lower in ("m", "minute", "minutely"): + qualifier = "MINUTELY" + elif arg_lower in ("s", "second", "secondly"): + qualifier = "SECONDLY" + else: + continue + + break + + while True: + arg = next_arg(args) + if not arg: + if selector: + arg = input_with_default("Interval (%d)? ", + selector.get_interval()) + else: + arg = input_with_default("Interval (%d)? ", 1) + + interval = to_int_or_none(arg) + + if interval is None: + arg = None + else: + break + + # Update an existing selector. + + if selector: + selector.set_interval(interval) + return + + # Create a new selector. + + selector = vRecurrence.new_selector(qualifier) + selector.set_interval(interval) + + # Remove any existing frequency selector. + + for index, _selector in enumerate(selectors): + if isinstance(_selector, vRecurrence.Pattern): + del selectors[index] + break + + # Add the new selector and keep the selectors in order. + + selectors.append(selector) + vRecurrence.sort_selectors(selectors) + +def add_rule_selector_selection(selectors, args, selector=None): + + "Add to 'selectors' a selector for a particular point in time." + + qualifier = selector and selector.qualifier or None + + while not selector: + arg = next_arg(args) + if not arg: + arg = read_input("Select (M)onths, (w)eeks, (y)eardays, " + "m(o)nthdays, week(d)ays, (h)ours, (m)inutes, " + "(s)econds (or return)? ") + + if not arg: + return + + arg_lower = arg.lower() + + if arg == "M" or arg_lower in ("month", "months"): + qualifier = "BYMONTH" + elif arg_lower in ("w", "week", "weeks"): + qualifier = "BYWEEKNO" + elif arg_lower in ("y", "yearday", "yeardays"): + qualifier = "BYYEARDAY" + elif arg_lower in ("o", "monthday", "monthdays"): + qualifier = "BYMONTHDAY" + elif arg_lower in ("d", "weekday", "weekdays"): + qualifier = "BYDAY" + elif arg_lower in ("h", "hour", "hours"): + qualifier = "BYHOUR" + elif arg_lower in ("m", "minute", "minutes"): + qualifier = "BYMINUTE" + elif arg_lower in ("s", "second", "seconds"): + qualifier = "BYSECOND" + else: + continue + + break + + if not qualifier: + return + + ranges = vRecurrence.get_value_ranges(qualifier) + ranges_str = format_value_ranges(ranges[0]) + + values = [] + + while True: + arg = next_arg(args) + if not arg: + arg = read_input("Value (%s) (return to end)? " % ranges_str) + + # Stop if no more arguments. + + if not arg or arg == "end": + break + + # Handle weekdays. + + if qualifier == "BYDAY": + value = arg.upper() # help to match weekdays + + arg = next_arg(args) + if not arg: + arg = read_input("Occurrence within a month? ") + + index = to_int_or_none(arg) + value = vRecurrence.check_values(qualifier, [value, index]) + + # Handle all other values. + + else: + value = to_int_or_none(arg) + l = vRecurrence.check_values(qualifier, [value]) + value = l and l[0] + + # Append valid values. + + if value is not None: + values.append(value) + else: + print "Value not recognised." + + if not values: + return + + # Update an existing selector. + + if selector: + selector.set_values(values) + return + + # Create a new selector. + + selector = vRecurrence.new_selector(qualifier) + selector.set_values(values) + + # Remove any existing selector. + + for index, _selector in enumerate(selectors): + if _selector.qualifier == selector.qualifier: + del selectors[index] + break + + # Add the new selector and keep the selectors in order. + + selectors.append(selector) + vRecurrence.sort_selectors(selectors) + def select_object(cl, objects): + + "Select using 'cl' an object from the given 'objects'." + print if objects: @@ -649,8 +1076,9 @@ return None if cmd.isdigit(): - index = int(cmd) - if 0 <= index < len(objects): + index = to_int_or_none(cmd) + + if index is not None and 0 <= index < len(objects): obj = objects[index] return cl.load_object(obj.get_uid(), obj.get_recurrenceid()) @@ -660,6 +1088,9 @@ return None def show_commands(): + + "Show editing and inspection commands." + print print_title("Editing commands") print @@ -687,7 +1118,7 @@ l, list, show List/show all event details -p, period +p, period [ new ] Add new period p @@ -703,8 +1134,12 @@ r, reload, reset, restart Reset event periods (return to editing mode, if already finished) -rrule - Set a recurrence rule in the event, applying to the main period +rule, rrule + Add a period recurrence rule + +rule +rrule + Select period recurrence rule selector from list s, summary Set event summary @@ -755,6 +1190,13 @@ """ def edit_object(cl, obj, handle_outgoing=False): + + """ + Edit using 'cl' the given object 'obj'. If 'handle_outgoing' is specified + and set to a true value, the details from outgoing messages are incorporated + into the stored data. + """ + cl.show_object() print @@ -763,13 +1205,17 @@ role = cl.is_organiser() and "Organiser" or "Attendee" status = cl.state.get("finished") and " (editing complete)" or "" - cmd = read_input("%s%s> " % (role, status)) - - args = cmd.split() + s = read_input("%s%s> " % (role, status)) + args = s.split() if not args or not args[0]: continue + # Expand short-form arguments. + + expand_arg(args) + cmd = next_arg(args) + # Check the status of the periods. if cmd in ("c", "class", "classification"): @@ -819,12 +1265,12 @@ # Show UID details. - elif args[0] == "UID": - filename = get_filename_arg(cmd) + elif cmd == "UID": + filename = get_text_arg(s) write(obj.get_uid(), filename) - elif args[0] == "RECURRENCE-ID": - filename = get_filename_arg(cmd) + elif cmd == "RECURRENCE-ID": + filename = get_text_arg(s) write(obj.get_recurrenceid() or "", filename) # Post-editing operations. @@ -833,21 +1279,21 @@ # Show messages. - if args[0] in ("P", "publish"): - filename = get_filename_arg(cmd) + if cmd in ("P", "publish"): + filename = get_text_arg(s) cl.show_publish_message(plain=not filename, filename=filename) - elif args[0] in ("R", "remove", "cancel"): - filename = get_filename_arg(cmd) + elif cmd in ("R", "remove", "cancel"): + filename = get_text_arg(s) cl.show_cancel_message(plain=not filename, filename=filename) - elif args[0] in ("U", "update"): - filename = get_filename_arg(cmd) + elif cmd in ("U", "update"): + filename = get_text_arg(s) cl.show_update_message(plain=not filename, filename=filename) # Definitive finishing action. - elif args[0] in ("S", "send"): + elif cmd in ("S", "send"): # Send update and cancellation messages. @@ -868,22 +1314,7 @@ # Process the object using the person outgoing handler. if handle_outgoing: - - # Handle the parent object plus any rescheduled periods. - - unscheduled_objects, rescheduled_objects, added_objects = \ - cl.get_publish_objects() - - handlers = get_handlers(cl, person_outgoing.handlers, - [get_address(cl.user)]) - - handle_calendar_object(cl.obj, handlers, "PUBLISH") - for o in unscheduled_objects: - handle_calendar_object(o, handlers, "CANCEL") - for o in rescheduled_objects: - handle_calendar_object(o, handlers, "PUBLISH") - for o in added_objects: - handle_calendar_object(o, handlers, "ADD") + cl.handle_outgoing_object() # Otherwise, send a message to self with the event details. @@ -904,20 +1335,13 @@ elif not cl.state.get("finished"): - # Expand short-form arguments. - - expand_arg(args) - # Add or edit attendee. - if args[0] in ("a", "attendee"): - - args = args[1:] + if cmd in ("a", "attendee"): value = next_arg(args) + index = to_int_or_none(value) - if value and value.isdigit(): - index = int(value) - else: + if index is None: try: index = cl.find_attendee(value) except ValueError: @@ -942,11 +1366,15 @@ cmd = next_arg(args) if not cmd: - cmd = read_input(" (e)dit, (r)emove (or return)> ") + cmd = read_input("Attendee: (e)dit, (r)emove (or return)> ") if cmd in ("e", "edit"): cl.edit_attendee(index) elif cmd in ("r", "remove"): cl.remove_attendees([index]) + + # Exit if requested or after a successful + # operation. + elif not cmd: pass else: @@ -958,20 +1386,19 @@ # Add suggested attendee (using index). - elif args[0] in ("as", "attendee-suggested", "suggested-attendee"): - try: - index = int(args[1]) + elif cmd in ("as", "attendee-suggested", "suggested-attendee"): + value = next_arg(args) + index = to_int_or_none(value) + + if index is not None: cl.add_suggested_attendee(index) - except ValueError: - pass + cl.show_attendees() print # Edit attendance. - elif args[0] in ("A", "attend", "attendance"): - - args = args[1:] + elif cmd in ("A", "attend", "attendance"): if not cl.is_attendee() and cl.is_organiser(): cl.add_attendee(cl.user) @@ -985,13 +1412,16 @@ cmd = next_arg(args) if not cmd: - cmd = read_input(" (a)ccept, (d)ecline, (t)entative (or return)> ") + cmd = read_input("Attendance: (a)ccept, (d)ecline, (t)entative (or return)> ") if cmd in ("a", "accept", "accepted", "attend"): cl.edit_attendance("ACCEPTED") elif cmd in ("d", "decline", "declined"): cl.edit_attendance("DECLINED") elif cmd in ("t", "tentative"): cl.edit_attendance("TENTATIVE") + + # Exit if requested or after a successful operation. + elif not cmd: pass else: @@ -1003,21 +1433,15 @@ # Add or edit period. - elif args[0] in ("p", "period"): - - args = args[1:] + elif cmd in ("p", "period"): value = next_arg(args) - - if value and value.isdigit(): - index = int(value) - else: - index = None + index = to_int_or_none(value) # Add a new period. - if index is None: + if index is None or value == "new": cl.add_period() - cl.edit_period(-1) + cl.edit_period(-1, args) # Edit period (using index). @@ -1031,13 +1455,17 @@ cmd = next_arg(args) if not cmd: - cmd = read_input(" (e)dit, (c)ancel, (u)ncancel (or return)> ") + cmd = read_input("Period: (e)dit, (c)ancel, (u)ncancel (or return)> ") if cmd in ("e", "edit"): cl.edit_period(index, args) elif cmd in ("c", "cancel"): cl.cancel_periods([index]) elif cmd in ("u", "uncancel", "restore"): cl.cancel_periods([index], False) + + # Exit if requested or after a successful + # operation. + elif not cmd: pass else: @@ -1049,25 +1477,37 @@ # Apply suggested period (using index). - elif args[0] in ("ps", "period-suggested", "suggested-period"): - try: - index = int(args[1]) + elif cmd in ("ps", "period-suggested", "suggested-period"): + value = next_arg(args) + index = to_int_or_none(value) + + if index is not None: cl.apply_suggested_period(index) - except ValueError: - pass + cl.show_periods() print # Specify a recurrence rule. - elif args[0] == "rrule": - pass + elif cmd in ("rule", "rrule"): + value = next_arg(args) + index = to_int_or_none(value) + + # Add a new rule. + + if index is None: + cl.add_rule_selectors() + else: + cl.edit_rule_selector(index, args) + + cl.show_rule() + cl.update_periods_from_rule() + print # Set the summary. - elif args[0] in ("s", "summary"): - t = cmd.split(None, 1) - cl.edit_summary(len(t) > 1 and t[1] or None) + elif cmd in ("s", "summary"): + cl.edit_summary(get_text_arg(s)) cl.show_object() print @@ -1075,11 +1515,17 @@ return def main(args): + + """ + The main program, employing command line 'args' to initialise the editing + activity. + """ + global echo if "--help" in args: show_help(os.path.split(sys.argv[0])[-1]) - return + return 0 # Parse command line arguments using the standard options plus some extra # options. @@ -1092,6 +1538,9 @@ "--handle-data" : ("handle_data", False), "--suppress-bcc" : ("suppress_bcc", False), "-u" : ("user", None), + "--uid" : ("uid", None), + "--recurrence-id" : ("recurrenceid", None), + "--show-config" : ("show_config", False) }) charset = args["charset"] @@ -1102,6 +1551,27 @@ sender = (args["senders"] or [None])[0] suppress_bcc = args["suppress_bcc"] user = args["user"] + uid = args["uid"] + recurrenceid = args["recurrenceid"] + + # Open a store. + + store_type = args.get("store_type") + store_dir = args.get("store_dir") + preferences_dir = args.get("preferences_dir") + + # Show configuration and exit if requested. + + if args["show_config"]: + print """\ +Store type: %s (%s) +Store directory: %s (%s) +Preferences directory: %s +""" % ( + store_type, settings["STORE_TYPE"], + store_dir, settings["STORE_DIR"], + preferences_dir) + return 0 # Determine the user and sender identities. @@ -1111,13 +1581,9 @@ sender = get_address(user) elif not sender and not user: print >>sys.stderr, "A sender or a user must be specified." - sys.exit(1) - - # Open a store. + return 1 - store_type = args.get("store_type") - store_dir = args.get("store_dir") - preferences_dir = args.get("preferences_dir") + # Obtain a store but not a journal. store = get_store(store_type, store_dir) journal = None @@ -1130,7 +1596,7 @@ cl = TextClient(user, messenger, store, journal, preferences_dir) - # Read any input resource. + # Read any input resource, using it to obtain identifier details. if filename: if calendar_data: @@ -1161,17 +1627,22 @@ show_objects(objects, user, store) obj = select_object(cl, objects) - # Exit without any object. + # Load any indicated object. - if not obj: - print >>sys.stderr, "No object loaded." - sys.exit(1) + elif uid: + obj = cl.load_object(uid, recurrenceid) # Or create a new object. else: obj = cl.new_object() + # Exit without any object. + + if not obj: + print >>sys.stderr, "No object loaded." + return 1 + # Edit the object. edit_object(cl, obj, handle_outgoing=handle_data) @@ -1181,7 +1652,7 @@ help_text = """\ Usage: %s -s | -u \\ - [ -f ] \\ + [ -f | --uid [ --recurrence-id ] ] \\ [ --calendar-data --charset ] \\ [ --handle-data ] \\ [ -T ] \\ @@ -1195,7 +1666,10 @@ Input options: --f Indicates a filename containing a MIME-encoded message or calendar object +-f Indicates a filename containing a MIME-encoded message or + calendar object +--uid Indicates the UID of a stored calendar object +--recurrence-id Indicates a stored object with a specific RECURRENCE-ID --calendar-data Indicates that the specified file contains a calendar object as opposed to a mail message @@ -1222,6 +1696,6 @@ """ if __name__ == "__main__": - main(sys.argv[1:]) + sys.exit(main(sys.argv[1:])) # vim: tabstop=4 expandtab shiftwidth=4