Skip to content

Commit

Permalink
Merge remote-tracking branch 'github/time_limit'
Browse files Browse the repository at this point in the history
Conflicts:
	bot/tests/run_test.py
  • Loading branch information
betelgeuse committed Jun 25, 2011
2 parents bc787b2 + 4430af0 commit eab5375
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 18 deletions.
56 changes: 52 additions & 4 deletions bot/ircmeeting/agenda.py
@@ -1,7 +1,15 @@
import json
import threading
import urllib
import re

class MessageSender:
def __init__(self, irc, message):
self.irc = irc
self.message = message
def send_message(self):
self.irc.reply(self.message)

class Agenda(object):

# Messages
Expand All @@ -18,6 +26,10 @@ class Agenda(object):
not_a_number_msg = "Your choice was not recognized as a number. Please retry."
out_of_range_msg = "Your choice was out of range!"
vote_confirm_msg = "You voted for #{} - {}"
timelimit_added_msg = 'Added "{}" reminder in {}:{}'
timelimit_list_msg = 'Set reminders: "{}"'
timelimit_removed_msg = 'Reminder "{}" removed'
timelimit_missing_msg = 'No such reminder "{}"'

# Internal
_voters = []
Expand All @@ -28,6 +40,7 @@ class Agenda(object):

def __init__(self, conf):
self.conf = conf
self.reminders = {}

def get_agenda_item(self):
if not self.conf.manage_agenda:
Expand All @@ -37,24 +50,36 @@ def get_agenda_item(self):
else:
return self.empty_agenda_msg

def next_agenda_item(self):
def _swich_agenda_item_to(self, new_item, irc):
self._current_item = new_item
for reminder in self.reminders.values():
reminder.cancel()
self.reminders = {}
for line in self._agenda[self._current_item][2].split('\n'):
match = re.match( '([0-9]+):([0-9]+) (.*)', line)
if match:
self.add_timelimit(int(match.group(1)), int(match.group(2)),
match.group(3), irc)
self._agenda[self._current_item][2] = ''

def next_agenda_item(self, irc):
if not self.conf.manage_agenda:
return('')
if self._vote_open:
return self.voting_open_so_item_not_changed_msg
else:
if (self._current_item + 1) < len(self._agenda):
self._current_item += 1
self._swich_agenda_item_to(self._current_item + 1, irc)
return(self.get_agenda_item())

def prev_agenda_item(self):
def prev_agenda_item(self, irc):
if not self.conf.manage_agenda:
return('')
if self._vote_open:
return self.voting_open_so_item_not_changed_msg
else:
if self._current_item > 0:
self._current_item -= 1
self._swich_agenda_item_to(self._current_item - 1, irc)
return(self.get_agenda_item())

def start_vote(self):
Expand Down Expand Up @@ -169,6 +194,29 @@ def remove_option(self, nick, line):
option = self._agenda[self._current_item][1].pop(opt)
return str.format(self.removed_option_msg, str(opt), option)

def add_timelimit(self, minutes, seconds, message, irc):
sender = MessageSender(irc, message)
reminder = (threading.Timer(60*minutes + seconds, sender.send_message))
self.reminders[message] = reminder
reminder.start()
result = str.format(self.timelimit_added_msg, message, minutes, seconds)
return(result)

def list_timielimits(self):
keys = self.reminders.keys()
keys_str = '", "'.join(keys)
result = str.format(self.timelimit_list_msg, keys_str)
return(result)

def remove_timelimit(self, message):
if message in self.reminders:
timer = self.reminders.pop(message)
timer.cancel()
result = str.format(self.timelimit_removed_msg, message)
else:
result = str.format(self.timelimit_missing_msg, message)
return(result)

def post_result(self):
if not self.conf.manage_agenda:
return('')
Expand Down
21 changes: 18 additions & 3 deletions bot/ircmeeting/meeting.py
Expand Up @@ -33,6 +33,7 @@
import os
import re
import stat
import threading

import writers
import items
Expand Down Expand Up @@ -301,7 +302,6 @@ def findFile(self, fname):
# Subclass Config and LocalConfig, new type overrides Config.
Config = type('Config', (LocalConfig, Config), {})


class MeetingCommands(object):
# Command Definitions
# generic parameters to these functions:
Expand All @@ -323,10 +323,25 @@ def do_startmeeting(self, nick, time_, line, **kwargs):
self.reply(self.config.agenda.get_agenda_item())

def do_nextitem(self, nick, time_, line, **kwargs):
self.reply(self.config.agenda.next_agenda_item())
self.reply(self.config.agenda.next_agenda_item(self))

def do_previtem(self, nick, time_, line, **kwargs):
self.reply(self.config.agenda.prev_agenda_item())
self.reply(self.config.agenda.prev_agenda_item(self))

def do_timelimit(self, nick, time_, line, **kwargs):
reply = 'Usage "#timelimit add <minutes>:<seconds> <message>" or ' +\
'"#timelimit list" or "#timelimit remove <message>"'
match = re.match( ' *?add ([0-9]+):([0-9]+) (.*)', line)
if match:
reply = self.config.agenda.add_timelimit(int(match.group(1)),
int(match.group(2)), match.group(3), self)
elif re.match( ' *?list', line):
reply = self.config.agenda.list_timielimits()
else:
match = re.match( ' *?remove (.*)', line)
if(match):
reply = self.config.agenda.remove_timelimit(match.group(1))
self.reply(reply)

def do_changeitem(self, nick, time_, line, **kwargs):
self.reply(self.config.agenda.change_agenda_item(line))
Expand Down
75 changes: 74 additions & 1 deletion bot/tests/run_test.py
Expand Up @@ -6,6 +6,8 @@
import shutil
import sys
import tempfile
import time
import threading
import unittest

os.environ['MEETBOT_RUNNING_TESTS'] = '1'
Expand Down Expand Up @@ -342,7 +344,7 @@ def getM(fnamepattern):
def get_simple_agenda_test(self):
test = test_meeting.TestMeeting()
test.set_voters(['x', 'z'])
test.set_agenda([['first item', ['opt1', 'opt2']], ['second item', []], ['third item', []]])
test.set_agenda([['first item', ['opt1', 'opt2'], ''], ['second item', [], ''], ['third item', [], '']])
test.M.config.manage_agenda = False

test.answer_should_match("20:13:50 <x> #startmeeting",
Expand Down Expand Up @@ -446,6 +448,77 @@ def test_agenda_close_voting_after_last_vote(self):
test.answer_should_match('20:13:50 <x> #vote 0', 'You voted for #0 - opt1')
test.answer_should_match('20:13:50 <z> #vote 0', 'You voted for #0 - opt1. Voting closed.')

def test_agenda_time_limit_adding(self):
test = self.get_simple_agenda_test()
test.answer_should_match('20:13:50 <x> #timelimit', 'Usage "#timelimit ' +\
'add <minutes>:<seconds> <message>" or "' +\
'#timelimit list" or "#timelimit remove ' +\
'<message>"')
test.answer_should_match('20:13:50 <x> #timelimit add 0:1 some other message',
'Added "some other message" reminder in 0:1')
test.answer_should_match('20:13:50 <x> #timelimit add 1:0 some message',
'Added "some message" reminder in 1:0')
time.sleep(2)
last_message = test.log[-1]
assert(last_message == 'some other message')
reminders = test.M.config.agenda.reminders
assert(len(reminders) == 2)
for reminder in reminders.values():
assert(reminder.__class__ == threading._Timer)

test.process('20:13:50 <x> #nextitem')

def test_agenda_time_limit_removing_when_changing_item(self):
test = self.get_simple_agenda_test()

test.process('20:13:50 <x> #timelimit add 0:1 message')
assert(len(test.M.config.agenda.reminders) == 1)
test.process('20:13:50 <x> #nextitem')
assert(len(test.M.config.agenda.reminders) == 0)
test.process('20:13:50 <x> #timelimit add 0:1 message')
assert(len(test.M.config.agenda.reminders) == 1)
test.process('20:13:50 <x> #previtem')
assert(len(test.M.config.agenda.reminders) == 0)

def test_agenda_time_limit_manual_removing(self):
test = self.get_simple_agenda_test()

test.process('20:13:50 <x> #timelimit add 0:1 message')
test.process('20:13:50 <x> #timelimit add 0:1 other message')
keys = test.M.config.agenda.reminders.keys()
keys.sort()
assert(keys == ['message', 'other message'])

test.answer_should_match('20:13:50 <x> #timelimit remove other message', 'Reminder "other message" removed')
keys = test.M.config.agenda.reminders.keys()
assert(keys == ['message'])

def test_agenda_time_limit_listing(self):
test = self.get_simple_agenda_test()
test.process('20:13:50 <x> #timelimit add 0:1 message')
test.process('20:13:50 <x> #timelimit add 0:1 other message')
test.process('20:13:50 <x> #timelimit add 0:1 yet another message')
keys = test.M.config.agenda.reminders.keys()
test.answer_should_match('20:13:50 <x> #timelimit list',
'Set reminders: "' + '", "'.join(keys) + '"')

def test_preset_agenda_time_limits(self):
test = self.get_simple_agenda_test()
test.M.config.agenda._agenda[0][2] = '1:0 message'
test.M.config.agenda._agenda[1][2] = '1:0 another message\n0:10 some other message'

test.process('20:13:50 <x> #nextitem')
keys = test.M.config.agenda.reminders.keys()
keys.sort()
assert(keys == ['another message', 'some other message'])

test.process('20:13:50 <x> #previtem')
keys = test.M.config.agenda.reminders.keys()
keys.sort()
assert(keys == ['message'])

test.process('20:13:50 <x> #nextitem')

if __name__ == '__main__':
os.chdir(os.path.join(os.path.dirname(__file__), '.'))

Expand Down
2 changes: 1 addition & 1 deletion site/app/models/agenda.rb
Expand Up @@ -91,7 +91,7 @@ def current?

def voting_array
agenda_items.collect do |item|
[item.title, item.voting_options.*.description]
[item.title, item.voting_options.*.description, item.timelimits]
end
end

Expand Down
13 changes: 13 additions & 0 deletions site/app/models/agenda_item.rb
Expand Up @@ -7,13 +7,16 @@ class AgendaItem < ActiveRecord::Base
discussion :string
body :text
rejected :boolean, :default => false
timelimits :text, :null => false, :default => ''
timestamps
end

belongs_to :user, :creator => true
belongs_to :agenda
has_many :voting_options

validate :timelimits_entered_properly

# --- Permissions --- #
def create_permitted?
return false if acting_user.guest?
Expand Down Expand Up @@ -50,4 +53,14 @@ def edit_permitted?(field)
return false unless agenda.nil?
return acting_user == user if [nil, :title, :discussion, :body].include?(field)
end

protected
def timelimits_entered_properly
regexp = /^\d+:\d+( .*)?$/
for line in timelimits.split("\n")
unless line.match regexp
errors.add(:timelimits, "Line '#{line}' doensn't match '<minutes>:<seconds> <message>'")
end
end
end
end
3 changes: 2 additions & 1 deletion site/db/schema.rb
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended to check this file into your version control system.

ActiveRecord::Schema.define(:version => 20110606170332) do
ActiveRecord::Schema.define(:version => 20110624141720) do

create_table "agenda_items", :force => true do |t|
t.string "title"
Expand All @@ -21,6 +21,7 @@
t.datetime "updated_at"
t.integer "user_id"
t.integer "agenda_id"
t.text "timelimits", :default => "", :null => false
end

add_index "agenda_items", ["agenda_id"], :name => "index_agenda_items_on_agenda_id"
Expand Down
19 changes: 19 additions & 0 deletions site/spec/models/agenda_item_spec.rb
Expand Up @@ -100,4 +100,23 @@
a.should_not be_editable_by(u, :agenda)
end
end

it 'should make sure timelimits are valid' do
valid_timelimits = ["", "0:0", "1:1 message", "1:2 longer message",
"30:40 a few messages\n5:60 as separate lines"]
invalid_timelimits = ["a:0", "1:", "2:a", ":0", " 1:1 message",
"30:40 a few messages\n\n5:60 and an empty line",
"30:40 a few messages\n5:60 and an wrong line\na:"]

valid_timelimits.each do |limit|
Factory(:agenda_item, :timelimits => limit).should be_valid
end

invalid_timelimits.each do |limit|
item = AgendaItem.new :title => 'title', :timelimits => limit
item.should_not be_valid
item.errors.length.should be_equal(1)
item.errors[:timelimits].should_not be_nil
end
end
end
17 changes: 9 additions & 8 deletions site/spec/models/agenda_spec.rb
Expand Up @@ -126,9 +126,9 @@ def test_migration(object, migration, prohibited, allowed, final_state)

Vote.count.should be_equal(9)

u[0].votes.*.voting_option.*.description.should == ['Yes', 'Yes', 'Dunno']
u[1].votes.*.voting_option.*.description.should == ['Yes', 'No', 'Dunno']
u[2].votes.*.voting_option.*.description.should == ['Yes', 'Dunno', 'No']
u[0].votes.*.voting_option.*.description.sort.should == ['Dunno', 'Yes', 'Yes']
u[1].votes.*.voting_option.*.description.sort.should == ['Dunno', 'No', 'Yes']
u[2].votes.*.voting_option.*.description.sort.should == ['Dunno', 'No', 'Yes']
a1.voting_options.*.votes.flatten.*.voting_option.*.description.should == ['Yes', 'Yes', 'Yes']
a2.voting_options.*.votes.flatten.*.voting_option.*.description.should == ['Yes', 'No', 'Dunno']
a3.voting_options.*.votes.flatten.*.voting_option.*.description.should == ['No', 'Dunno', 'Dunno']
Expand Down Expand Up @@ -236,15 +236,16 @@ def test_migration(object, migration, prohibited, allowed, final_state)
it 'should return proper voting_array' do
old_agenda = Factory(:agenda, :state => 'old')
current_agenda = Factory(:agenda)
i1 = Factory(:agenda_item, :agenda => old_agenda)
i2 = Factory(:agenda_item, :agenda => current_agenda)
i3 = Factory(:agenda_item, :agenda => current_agenda)
i1 = Factory(:agenda_item, :agenda => old_agenda, :timelimits => '0:0')
i2 = Factory(:agenda_item, :agenda => current_agenda, :timelimits => "10:0 Ten minutes passed")
i3 = Factory(:agenda_item, :agenda => current_agenda, :timelimits => "0:10 Ten seconds passed")

v11 = Factory(:voting_option, :agenda_item => i1)
v21 = Factory(:voting_option, :agenda_item => i2)
v22 = Factory(:voting_option, :agenda_item => i2, :description => 'other')

old_agenda.voting_array.should == [[i1.title, [v11.description]]]
current_agenda.voting_array.should == [[i2.title, [v21.description, v22.description]], [i3.title, []]]
old_agenda.voting_array.should == [[i1.title, [v11.description], i1.timelimits]]
current_agenda.voting_array.should == [[i2.title, [v21.description, v22.description],
i2.timelimits], [i3.title, [], i3.timelimits]]
end
end

0 comments on commit eab5375

Please sign in to comment.