summaryrefslogtreecommitdiff
path: root/vote/2016-mc/mkical.py
blob: 9c8b7847e50c4a8a723c6055cb8263a4aac7d64a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
#!/usr/bin/env python
'''
This Python script creates a simple iCal file based on hardcoded events
in this file.
'''

import calendar
import datetime
import logging
import math
import os
import vobject


#### Configure these variables
YEAR = 2016
CANDIDATES_OPENED_DATE    = (YEAR, 7, 25) # In 2012, begin earlier in May
CANDIDATES_CLOSED_DATE    = (YEAR, 8, 31)
CANDIDATES_ANNOUNCED_DATE = (YEAR, 9,  8)
VOTING_OPENED_DATE        = (YEAR, 9,  8)
VOTING_CLOSED_DATE        = (YEAR, 9, 14)
PRELIMINARY_RESULTS_DATE  = (YEAR, 9, 15)
CHALLENGE_CLOSED_DATE     = (YEAR, 9, 17)



### I'm sorry that these functions clutter your calendar-creating experience
### Please scroll down a bit to edit the description texts

#### Application Data
def c(multilinestring):
    '''
    A helper functions which cleans up a multiline string, so that
    it doesn't contain any newlines or multiple whitespaces
    '''
    stripped = [l.strip() for l in multilinestring.splitlines()]
    ret = " ".join (stripped)
    return ret
    
def d(year, month, day):
    '''
    Just a tiny wrapper around datetime.datetime to create a datetime object
    '''
    return datetime.date(year, month, day)



CANDIDATES_OPENED = (
    d(*CANDIDATES_OPENED_DATE),
    'Announcements and list of candidates opens',
    c("""If you are a member of The Document Foundation and are interested 
    in running for election, you may nominate yourself by sending an 
    e-mail to elections@documentfoundation.org with your name, e-mail 
    address, corporate affiliation (if any), and a description of why 
    you'd like to serve, before
    %s (23:59 UTC).""" % d(*CANDIDATES_CLOSED_DATE)) + '''
    ''' + c("""    
    You should also send a summary of your candidacy announcement 
    (75 words or less) to board-discuss@documentfoundation.org. """) + '''
    '''
)

CANDIDATES_CLOSED = (
    d(*CANDIDATES_CLOSED_DATE),
    'List of candidates closed',
    CANDIDATES_OPENED[2] # Get the same text again
)

CANDIDATES_ANNOUNCED = (
    d(*CANDIDATES_ANNOUNCED_DATE),
    'List of candidates announced',
    'You may now start to send your questions to the candidates'
)

VOTING_OPENED = (
    d(*VOTING_OPENED_DATE),
    'Instructions to vote are sent',
    'Please read your email and follow these instructions and submit your vote by %s' % d(*VOTING_CLOSED_DATE)
)
VOTING_CLOSED = (
    d(*VOTING_CLOSED_DATE),
    'Votes must be returned',
    'Preliminary results are announced on %s' % d(*PRELIMINARY_RESULTS_DATE)
)


PRELIMINARY_RESULTS = (
    d(*PRELIMINARY_RESULTS_DATE),
    'Preliminary results are announced',
    'The preliminary results can be challenged until %s' % d(*CHALLENGE_CLOSED_DATE)
)

CHALLENGE_CLOSED = (
    d(*CHALLENGE_CLOSED_DATE),
    'Challenges to the results closed',
    "If there weren't any challenges, preliminary results are valid"
)




def create_ical(eventlist):
    '''Generates an ical stream based on the list given as eventlist.
    The list shall contain elements with a tuple with a
    (date, string, string) object, serving as date when the event takes place,
    summary and description respectively.
    '''
    log = logging.getLogger('create_ical')

    cal = vobject.iCalendar()
    cal.add('method').value = 'PUBLISH'
    cal.add('calscale').value = 'GREGORIAN'
    cal.add('x-wr-timezone').value = 'UTC'
    
    for (timestamp, summary, description) in eventlist:
        log.debug('creating %s, %s', timestamp, description)
        vevent = cal.add('vevent')
        vevent.add('dtstart').value = timestamp
        vevent.add('dtend').value = timestamp + datetime.timedelta(1)
        vevent.add('summary').value = summary
        vevent.add('description').value = description
    
    stream = cal.serialize()
    return stream


def wraptext(s, width):
    '''Wraps a string @s at @width characters.
    
    >>> wraptext('fooo', 2)
    ['fo','oo']
    '''
    l = len(s)
    nr_frames = int(math.ceil(float(l)/width))
    print nr_frames
    frames = []
    for i in xrange(nr_frames):
        start, end = i*width, (i+1) * width
        frames.append(s[start:end])
        # One could (and prolly should) yield that
    return frames

def ordinal(n):
    n = int(n)
    if 10 <= n % 100 < 20:
        return str(n) + 'th'
    else:
       return  str(n) + {1 : 'st', 2 : 'nd', 3 : 'rd'}.get(n % 10, "th")
                       

def cal_for_month(month, events, width=80, year=datetime.datetime.now().year):
    '''Generates a textual calendar for the @month in @year.
    It will return a string with the calendar on the left hand side and the
    events on the right hand side.
    @events shall be a list with tuples: timestamp, summary, description.
    
    Returns a string with the calendar
    '''
    log = logging.getLogger('cal_for_month')

    cal = calendar.TextCalendar()
    calstrings = cal.formatmonth(year, month, 3).splitlines()

    for (timestamp, summary, description) in events:
        log.debug('creating %s, %s', timestamp, summary)
        year, month, day = timestamp.year, timestamp.month, timestamp.day
        maxwidth = max([len(cs) for cs in calstrings])
        rightwidth = 80 - maxwidth
        for i, line in enumerate(calstrings):
            needles =      (" %d " % day,
                           " %d\n" % day)
            replacement = "(%d)" % day
            # Find the day so that we can highlight it and add a comment
            day_in_week = False
            for needle in needles:
                if needle in line+"\n":
                    # k, this looks a bit weird but we have that corner 
                    # case with the day being at the end of the line 
                    # which in turn will have been split off
                    day_in_week = True
                    break # Set the needle to the found one
            if day_in_week == False: # Nothing found, try next week
                log.debug('Day (%d) not found in %s', day, line)
                continue
            else:
                log.debug('Day (%d) found in %s', day, line)
                new_line = (line+"\n").replace(needle, replacement).rstrip()
                new_line += "   %s (%s)" % (summary, ordinal(day))
                # Replace in-place for two events in the same week
                # FIXME: This has bugs :-( 
                calstrings[i] = new_line
                    
    return os.linesep.join(calstrings)

def create_textcal(eventlist):
    '''Generates a multiline string containing a calendar with the 
    events written on the side
    The list shall contain elements with a tuple with a
    (date, string, string) object, serving as date when the event takes place,
    summary and description respectively.
    '''
    log = logging.getLogger('textcal')
    log.debug('Generating from %s', eventlist)
    months = set(map(lambda x: x[0].month, eventlist))
    year = set(map(lambda x: x[0].year, eventlist)).pop()
    
    final_cal = []
    for month in months:
        events = filter(lambda x: x[0].month == month, eventlist)
        log.debug('Events for %d: %s', month, events)
        month_cal = cal_for_month(month, events, year=year)
        final_cal.append(month_cal)
        
    return os.linesep.join(final_cal)

if __name__ == "__main__":
    from optparse import OptionParser
    parser = OptionParser("usage: %prog [options]")
    parser.add_option("-l", "--loglevel", dest="loglevel", help="Sets the loglevel to one of debug, info, warn, error, critical", 
                      default=None)
    parser.add_option("-i", "--ical",
                      action="store_true", dest="ical", default=False,
                      help="print iCal file to stdout")
    parser.add_option("-t", "--textcal",
                      action="store_true", dest="tcal", default=False,
                      help="print textual calendar to stdout")
    (options, args) = parser.parse_args()

    loglevel = {'debug': logging.DEBUG, 'info': logging.INFO,
                'warn': logging.WARNING, 'error': logging.ERROR,
                'critical': logging.CRITICAL}.get(options.loglevel, logging.WARNING)
    logging.basicConfig( level=loglevel )
    log = logging.getLogger()
    
    eventlist = [
        CANDIDATES_OPENED,
        CANDIDATES_CLOSED,
        CANDIDATES_ANNOUNCED,
        VOTING_OPENED,
        VOTING_CLOSED,
        PRELIMINARY_RESULTS,
        CHALLENGE_CLOSED,
    ]
    
    if not any([options.ical, options.tcal]):
        parser.error("You want to select either ical or textcal output. See --help for details")
    if options.ical:
        ical = create_ical( eventlist )
        print ical
    if options.tcal:
        tcal = create_textcal( eventlist )
        print tcal