Skip to content

Commit

Permalink
Merge pull request #23 from cameronbwhite/major-refactoring
Browse files Browse the repository at this point in the history
Major refactoring, decoupling, testing, and Travis CI
  • Loading branch information
Cameron White committed Feb 17, 2014
2 parents f560c29 + fa2038f commit f742f9f
Show file tree
Hide file tree
Showing 6 changed files with 557 additions and 136 deletions.
5 changes: 5 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
language: python
python:
- "2.7"
# command to run tests
script: python setup.py nosetests
1 change: 1 addition & 0 deletions config.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,6 @@ [email protected]

[acmbot]
base_url = http://acm.pdx.edu
events_yaml_url = files/events.yaml
events_limit = 5
default_time = 16:00
314 changes: 178 additions & 136 deletions modules/acmbot.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# Copyright (C) 2013, Cameron White
# Copyright (C) 2014, Cameron White

import logging
import argparse
Expand All @@ -10,7 +10,8 @@
import yaml
import datetime
import urllib
from itertools import takewhile
from contextlib import closing
from itertools import takewhile, dropwhile

_log = logging.getLogger(__name__)

Expand Down Expand Up @@ -44,7 +45,7 @@ def messages(self, client, actor, recipient, message):
self.recipient = recipient
self.message = message

config = self.controller.config
self.read_config()

# Only pay attention if addressed directly in channels
try:
Expand All @@ -57,119 +58,37 @@ def messages(self, client, actor, recipient, message):
# http://docs.python.org/2/library/logging.html
_log.info("Responding to %r in %r", self.actor, self.recipient)

messages = []

if self.args.command == "!events":
if self.args.help:
messages = command_events.format_help().split('\n')
else:
def sort(events):
events = sorted(events, key=lambda x: x['date'], reverse=True)
events = list(enumerate(takewhile(
lambda x: x['date'] >= datetime.date.today(),
events,
)))
return events
messages = self.get_event_messages(sort)
messages = self.do_command(command_events)

elif self.args.command == '!today':
if self.args.help:
messages = command_events.format_help().split('\n')
messages = command_today.format_help().split('\n')
else:
def today_sort(events):
events = sorted(events, key=lambda x: x['date'], reverse=True)
events = list(enumerate(takewhile(
lambda x: x['date'] >= datetime.date.today(),
events,
)))
events = filter(
lambda (i, event): event['date'] == datetime.date.today(),
events
)
return events
messages = self.get_event_messages(today_sort)
messages = self.do_command(command_today)

elif self.args.command == '!tomorrow':
if self.args.help:
messages = command_events.format_help().split('\n')
messages = command_tomorrow.format_help().split('\n')
else:
def tomorrow_sort(events):
events = sorted(events, key=lambda x: x['date'], reverse=True)
events = list(enumerate(takewhile(
lambda x: x['date'] >= datetime.date.today() + datetime.timedelta(days=1),
events,
)))
events = filter(
lambda (i, event): event['date'] == datetime.date.today() + datetime.timedelta(days=1),
events
)
return events
messages = self.get_event_messages(tomorrow_sort)
messages = self.do_command(command_tomorrow)

elif self.args.command == '!next':
if self.args.help:
messages = command_events.format_help().split('\n')
messages = command_next.format_help().split('\n')
else:
def tomorrow_sort(events):
events = sorted(events, key=lambda x: x['date'], reverse=True)
events = list(enumerate(takewhile(
lambda x: x['date'] >= datetime.date.today(),
events,
)))
return [ events[-1] ]
messages = self.get_event_messages(tomorrow_sort)
messages = self.do_command(command_next)

elif self.args.command == '!day':
if self.args.help:
messages = command_events.format_help().split('\n')
messages = command_day.format_help().split('\n')
elif self.args.day:
# The date to filter by will be constructed.

today = datetime.date.today()

# Get the int encoding of the day of the week.
# Monday = 0 ... Sunday = 6
today_weekday = today.weekday()


wanted_weekday = [
'monday', 'tuesday', 'wednesday', 'thursday',
'friday', 'saturday', 'sunday',
].index(self.args.day[0].lower())

if wanted_weekday >= today_weekday:
# If today is Wednesday and Wednesday is wanted then
# the day should not be changed.
# Wednesday - Wednesday = 2 - 2 = 0
# If today is Tuesday and Friday is wanted then
# the day should be increased by 3
# Friday - Tuesday = 4 - 1 = 3
days = wanted_weekday - today_weekday
else:
# If today is Friday and Monday is wanted then the
# day should be increased by 3.
# 7 - Friday + Monday = 7 - 4 + 0 = 3
# If today is Thursday and Tuesday is wanted then the
# day should be increased by 5.
# 7 - Thursday + Tuesday = 7 - 3 + 1 = 5
days = 7 - today_weekday + wanted_weekday

# Construct the date by adding the calculated number of
# days.
date = today + datetime.timedelta(days=days)

def day_sort(events):
events = sorted(events, key=lambda x: x['date'], reverse=True)
events = list(enumerate(takewhile(
lambda x: x['date'] >= date,
events,
)))
events = filter(
lambda (i, event): event['date'] == date,
events
)
return events
messages = self.get_event_messages(day_sort)
messages = self.do_command(
lambda events: command_day(events, self.args.day),
)

elif self.args.command == "!help":
messages = parser.format_help().split('\n')
Expand All @@ -180,20 +99,16 @@ def day_sort(events):

# Stop any other modules from handling this message.
return True

def do_command(self, command):

def get_event_messages(self, func=None):
config = self.controller.config
events = self.get_events()

if not config.has_section("acmbot"):
_log.info("No config section for acmbot")
return
events = command(events)

if config.has_option("acmbot", "base_url"):
base_url = config.get("acmbot", "base_url")
else:
return
config = self.controller.config

if getattr(self.args, 'limit', None):
if hasattr(self.args, 'limit'):
events_limit = self.args.limit
elif config.has_option("acmbot", "events_limit"):
try:
Expand All @@ -203,44 +118,171 @@ def get_event_messages(self, func=None):
else:
events_limit = None

events_html = urllib.urlopen(
'{}/files/events.yaml'.format(base_url)
).read()

events = yaml.load(events_html)
length_events = len(events)

if func:
events = func(events)

if events_limit and events_limit >= 0 and len(events) >= events_limit:
events = events[len(events)-events_limit:]

messages = []

for i, event in reversed(events):

if 'time' in event:
time = get_time(event['time'])
elif config.has_option("acmbot", "default_time"):
time = get_time(config.get("acmbot", "default_time"))
else:
time = None
yield self.construct_message(i, event)

def construct_message(self, event_id, event):

if 'time' in event:
time = get_time(event['time'])
else:
time = None

message = '{} - {:%a, %b %d}'.format(
event['title'], event['date'],
)

if time:
message += ' @ {:%I:%M%p}'.format(time)

message += ' - {}/event.php?event={}'.format(
self.base_url, str(self.number_of_events - event_id - 1),
)

return message


def get_events(self):
events = load_yaml(
'{}/{}'.format(self.base_url, self.events_yaml_url)
)
self.number_of_events = len(events)
return events

def read_config(self):
config = self.controller.config

message = '{} - {:%a, %b %d}'.format(
event['title'], event['date'],
)
if not config.has_section("acmbot"):
_log.error("config has no `acmbot` section")
return

if config.has_option("acmbot", "base_url"):
self.base_url = config.get("acmbot", "base_url")
else:
_log.error("config `acmbot` section has no `base_url` option")
return

if config.has_option("acmbot", "events_yaml_url"):
self.events_yaml_url = config.get("acmbot", "events_yaml_url")
else:
_log.error("config `acmbot` section has no `events_yaml_url")
return


def load_yaml(url):
with closing(urllib.urlopen(url)) as yaml_raw:
return yaml.load(yaml_raw.read())

def select_events(events, filter_function):
# Sort events from newest to oldest.
# Attach an id to each event [(id, event)].
events = enumerate(sort_events(events))
# Apply filtering function
events = filter_function(events)
return list(events)

def sort_events(events):
""" Sort events from newest to oldest """
return sorted(events, key=lambda x: x['date'], reverse=True)

def filter_take_date_range(events, start_date, end_date):
""" Events filter which takes events which dates in the range
from start_date to end_date inclusively. start_date >= end_date.
events must be a list of the form [(id, event)]. """

if start_date:
events = dropwhile(
lambda x: x[1]['date'] > start_date,
events
)

if end_date:
events = takewhile(
lambda x: x[1]['date'] >= end_date,
events
)

return events

if time:
message += ' @ {:%I:%M%p}'.format(time)
def filter_take_date(events, date):
return filter_take_date_range(events, date, date)

message += ' - {}/event.php?event={}'.format(
base_url, str(length_events - i - 1),
)
def command_date(events, date):
return select_events(
events,
lambda x: filter_take_date(x, date)
)

messages.append(message)
return messages
def command_today(events):
date = datetime.date.today()
return command_date(events, date)

def command_tomorrow(events):
date = datetime.date.today() + datetime.timedelta(days=1)
return command_date(events, date)

def command_day(events, weekday):
# The date to filter by will be constructed.

today = datetime.date.today()

# Get the int encoding of the day of the week.
# Monday = 0 ... Sunday = 6
today_weekday = today.weekday()

days = weekday_difference(today_weekday, weekday[0])

# Construct the date by adding the calculated number of
# days.
date = today + datetime.timedelta(days=days)
return command_date(events, date)

def command_events(events):
date = datetime.date.today()
return select_events(
events,
lambda events: filter_take_date_range(events, None, date)
)

def command_next(events):
return command_events(events)[-1:]

def weekday_difference(from_weekday, to_weekday):

def get_weekday_index(weekday):
if weekday not in range(7):
weekday = [
'monday', 'tuesday', 'wednesday', 'thursday',
'friday', 'saturday', 'sunday',
].index(weekday.lower())
return weekday

from_weekday = get_weekday_index(from_weekday)
to_weekday = get_weekday_index(to_weekday)

if to_weekday >= from_weekday:
# If from is Wednesday and Wednesday is to then
# the day should not be changed.
# Wednesday - Wednesday = 2 - 2 = 0
# If from is Tuesday and Friday is to then
# the day should be increased by 3
# Friday - Tuesday = 4 - 1 = 3
days = to_weekday - from_weekday
else:
# If from is Friday and Monday is to then the
# day should be increased by 3.
# 7 - Friday + Monday = 7 - 4 + 0 = 3
# If from is Thursday and Tuesday is to then the
# day should be increased by 5.
# 7 - Thursday + Tuesday = 7 - 3 + 1 = 5
days = 7 - from_weekday + to_weekday

return days

def get_time(time_str):
return datetime.datetime.strptime(time_str, '%H:%M').time()

module = AcmBotModule
Loading

0 comments on commit f742f9f

Please sign in to comment.