# HG changeset patch # User Paul Boddie # Date 1511560462 -3600 # Node ID 63a73918d6a5b6eb10091f175d1708a5d22975be # Parent 1fd08dea3d05c66ad8775c49ac31720b6bd2d683 Added initial support for recurrence rule inspection and rule period editing. diff -r 1fd08dea3d05 -r 63a73918d6a5 imip_text_client.py --- a/imip_text_client.py Fri Nov 24 22:53:53 2017 +0100 +++ b/imip_text_client.py Fri Nov 24 22:54:22 2017 +0100 @@ -1,5 +1,24 @@ #!/usr/bin/env python +""" +A text interface to received and new events. + +Copyright (C) 2017 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 +Foundation; either version 3 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see . +""" + from email import message_from_file from imiptools import get_handlers, parse_args from imiptools.config import settings @@ -13,6 +32,7 @@ from imiptools.mail import Messenger from imiptools.stores import get_journal, get_store from imiptools.utils import decode_part, message_as_string +import vRecurrence import sys, os # User interface functions. @@ -20,19 +40,62 @@ echo = False def read_input(label): + + """ + Read input, prompting using 'label', stripping leading and trailing + whitespace, echoing the input if the global 'echo' variable is set. + """ + s = raw_input(label).strip() if echo: print s return s def input_with_default(label, default): + + """ + Read input, prompting using 'label', parameterising the label with the given + 'default' and returning the default if no input is given. + """ + return read_input(label % default) or default def print_title(text): + + "Print 'text' with simple, fixed-width styling as a title." + print text print len(text) * "-" +def print_table(rows, separator_index=0): + + """ + Print 'rows' as a simple, fixed-width table. If 'separator_index' is set to + a row index present in the rows, a table separator will be produced below + that row's data. Otherwise, a separator will appear below the first row. + """ + + widths = [] + for row in rows: + for i, col in enumerate(row): + if i >= len(widths): + widths.append(len(col)) + else: + widths[i] = max(widths[i], len(col)) + + for i, row in enumerate(rows): + for col, width in zip(row, widths): + print "%s%s" % (col, " " * (width - len(col))), + print + if i == separator_index: + for width in widths: + print "-" * width, + print + def write(s, filename): + + "Write 's' to a file having the given 'filename'." + f = filename and open(filename, "w") or None try: print >>(f or sys.stdout), s @@ -172,6 +235,71 @@ print p.get_start(), p.get_end(), p.origin +def show_rule(rrule): + + "Show recurrence rule specification 'rrule'." + + print + print "Recurrence rule:" + + count = None + freq_interval = [] + selections = [] + + # Collect limit, selection and frequency details. + + for selector in vRecurrence.order_qualifiers(vRecurrence.get_qualifiers(rrule)): + + # COUNT + + if isinstance(selector, vRecurrence.LimitSelector): + count = selector.args["values"][0] + + # BYSETPOS + + elif isinstance(selector, vRecurrence.PositionSelector): + for value in selector.args["values"]: + selections.append(("-", get_frequency(selector.level), str(value))) + + # BY... + + elif isinstance(selector, vRecurrence.Enum): + for value in selector.args["values"]: + selections.append(("-", get_frequency(selector.level), str(value))) + + # 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))) + + # 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. + + if freq_interval: + print + print_table([("Frequency", "Interval")] + freq_interval) + + if selections: + print + print_table([("From...", "Selecting", "Instance (1, 2, ...)")] + selections) + + if count: + print + print "At most", count, "occurrences." + +def get_frequency(level): + levels = ["Year", "Month", "Week", None, None, "Day", "Hour", "Minute", "Second"] + return levels[level] + +def get_weekday(weekday): + weekdays = [None, "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] + return weekdays[weekday] + def show_attendee_changes(new, modified, unmodified, removed): "Show 'new', 'modified', 'unmodified' and 'removed' periods." @@ -314,6 +442,7 @@ if period: edit_period(period, args) period.cancelled = False + period.origin = "DTSTART-RECUR" # Sort the periods after this change. @@ -427,7 +556,10 @@ 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_suggested_attendees(self): current_attendee = None @@ -569,6 +701,9 @@ 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 + s, summary Set event summary """ @@ -921,6 +1056,11 @@ cl.show_periods() print + # Specify a recurrence rule. + + elif args[0] == "rrule": + pass + # Set the summary. elif args[0] in ("s", "summary"):