1.1 --- a/imip_text_client.py Fri Nov 24 22:53:53 2017 +0100
1.2 +++ b/imip_text_client.py Fri Nov 24 22:54:22 2017 +0100
1.3 @@ -1,5 +1,24 @@
1.4 #!/usr/bin/env python
1.5
1.6 +"""
1.7 +A text interface to received and new events.
1.8 +
1.9 +Copyright (C) 2017 Paul Boddie <paul@boddie.org.uk>
1.10 +
1.11 +This program is free software; you can redistribute it and/or modify it under
1.12 +the terms of the GNU General Public License as published by the Free Software
1.13 +Foundation; either version 3 of the License, or (at your option) any later
1.14 +version.
1.15 +
1.16 +This program is distributed in the hope that it will be useful, but WITHOUT
1.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
1.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
1.19 +details.
1.20 +
1.21 +You should have received a copy of the GNU General Public License along with
1.22 +this program. If not, see <http://www.gnu.org/licenses/>.
1.23 +"""
1.24 +
1.25 from email import message_from_file
1.26 from imiptools import get_handlers, parse_args
1.27 from imiptools.config import settings
1.28 @@ -13,6 +32,7 @@
1.29 from imiptools.mail import Messenger
1.30 from imiptools.stores import get_journal, get_store
1.31 from imiptools.utils import decode_part, message_as_string
1.32 +import vRecurrence
1.33 import sys, os
1.34
1.35 # User interface functions.
1.36 @@ -20,19 +40,62 @@
1.37 echo = False
1.38
1.39 def read_input(label):
1.40 +
1.41 + """
1.42 + Read input, prompting using 'label', stripping leading and trailing
1.43 + whitespace, echoing the input if the global 'echo' variable is set.
1.44 + """
1.45 +
1.46 s = raw_input(label).strip()
1.47 if echo:
1.48 print s
1.49 return s
1.50
1.51 def input_with_default(label, default):
1.52 +
1.53 + """
1.54 + Read input, prompting using 'label', parameterising the label with the given
1.55 + 'default' and returning the default if no input is given.
1.56 + """
1.57 +
1.58 return read_input(label % default) or default
1.59
1.60 def print_title(text):
1.61 +
1.62 + "Print 'text' with simple, fixed-width styling as a title."
1.63 +
1.64 print text
1.65 print len(text) * "-"
1.66
1.67 +def print_table(rows, separator_index=0):
1.68 +
1.69 + """
1.70 + Print 'rows' as a simple, fixed-width table. If 'separator_index' is set to
1.71 + a row index present in the rows, a table separator will be produced below
1.72 + that row's data. Otherwise, a separator will appear below the first row.
1.73 + """
1.74 +
1.75 + widths = []
1.76 + for row in rows:
1.77 + for i, col in enumerate(row):
1.78 + if i >= len(widths):
1.79 + widths.append(len(col))
1.80 + else:
1.81 + widths[i] = max(widths[i], len(col))
1.82 +
1.83 + for i, row in enumerate(rows):
1.84 + for col, width in zip(row, widths):
1.85 + print "%s%s" % (col, " " * (width - len(col))),
1.86 + print
1.87 + if i == separator_index:
1.88 + for width in widths:
1.89 + print "-" * width,
1.90 + print
1.91 +
1.92 def write(s, filename):
1.93 +
1.94 + "Write 's' to a file having the given 'filename'."
1.95 +
1.96 f = filename and open(filename, "w") or None
1.97 try:
1.98 print >>(f or sys.stdout), s
1.99 @@ -172,6 +235,71 @@
1.100
1.101 print p.get_start(), p.get_end(), p.origin
1.102
1.103 +def show_rule(rrule):
1.104 +
1.105 + "Show recurrence rule specification 'rrule'."
1.106 +
1.107 + print
1.108 + print "Recurrence rule:"
1.109 +
1.110 + count = None
1.111 + freq_interval = []
1.112 + selections = []
1.113 +
1.114 + # Collect limit, selection and frequency details.
1.115 +
1.116 + for selector in vRecurrence.order_qualifiers(vRecurrence.get_qualifiers(rrule)):
1.117 +
1.118 + # COUNT
1.119 +
1.120 + if isinstance(selector, vRecurrence.LimitSelector):
1.121 + count = selector.args["values"][0]
1.122 +
1.123 + # BYSETPOS
1.124 +
1.125 + elif isinstance(selector, vRecurrence.PositionSelector):
1.126 + for value in selector.args["values"]:
1.127 + selections.append(("-", get_frequency(selector.level), str(value)))
1.128 +
1.129 + # BY...
1.130 +
1.131 + elif isinstance(selector, vRecurrence.Enum):
1.132 + for value in selector.args["values"]:
1.133 + selections.append(("-", get_frequency(selector.level), str(value)))
1.134 +
1.135 + # BYWEEKDAY
1.136 +
1.137 + elif isinstance(selector, vRecurrence.WeekDayFilter):
1.138 + for value, index in selector.args["values"]:
1.139 + selections.append((index >= 0 and "Start" or "End", get_weekday(value), str(index)))
1.140 +
1.141 + # YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDLY
1.142 +
1.143 + elif isinstance(selector, vRecurrence.Pattern):
1.144 + freq_interval.append((get_frequency(selector.level), str(selector.args.get("interval", 1))))
1.145 +
1.146 + # Show the details.
1.147 +
1.148 + if freq_interval:
1.149 + print
1.150 + print_table([("Frequency", "Interval")] + freq_interval)
1.151 +
1.152 + if selections:
1.153 + print
1.154 + print_table([("From...", "Selecting", "Instance (1, 2, ...)")] + selections)
1.155 +
1.156 + if count:
1.157 + print
1.158 + print "At most", count, "occurrences."
1.159 +
1.160 +def get_frequency(level):
1.161 + levels = ["Year", "Month", "Week", None, None, "Day", "Hour", "Minute", "Second"]
1.162 + return levels[level]
1.163 +
1.164 +def get_weekday(weekday):
1.165 + weekdays = [None, "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
1.166 + return weekdays[weekday]
1.167 +
1.168 def show_attendee_changes(new, modified, unmodified, removed):
1.169
1.170 "Show 'new', 'modified', 'unmodified' and 'removed' periods."
1.171 @@ -314,6 +442,7 @@
1.172 if period:
1.173 edit_period(period, args)
1.174 period.cancelled = False
1.175 + period.origin = "DTSTART-RECUR"
1.176
1.177 # Sort the periods after this change.
1.178
1.179 @@ -427,7 +556,10 @@
1.180 def show_periods(self):
1.181 print
1.182 print_title("Periods")
1.183 + rrule = self.obj.get_value("RRULE")
1.184 show_periods(self.state.get("periods"), self.state.get("period_errors"))
1.185 + if rrule:
1.186 + show_rule(rrule)
1.187
1.188 def show_suggested_attendees(self):
1.189 current_attendee = None
1.190 @@ -569,6 +701,9 @@
1.191 r, reload, reset, restart
1.192 Reset event periods (return to editing mode, if already finished)
1.193
1.194 +rrule <rule specification>
1.195 + Set a recurrence rule in the event, applying to the main period
1.196 +
1.197 s, summary
1.198 Set event summary
1.199 """
1.200 @@ -921,6 +1056,11 @@
1.201 cl.show_periods()
1.202 print
1.203
1.204 + # Specify a recurrence rule.
1.205 +
1.206 + elif args[0] == "rrule":
1.207 + pass
1.208 +
1.209 # Set the summary.
1.210
1.211 elif args[0] in ("s", "summary"):