#!/usr/bin/python
PREVIOUS_COUNT_FILE = '/tmp/m-last'
YIDDEN_FILE = '/home/josh/minyan_goers'
INTERVAL = 60
LATE_INTERVAL = 5
LATE_TIME = '14:00'
CUTOFF_TIME = '15:10'
PROXY = {'http':'http://aproxyserver:82'}
TALLY_URL = 'http://aminyancounter.cgi'
FORM = '<form action="'+TALLY_URL+'" method="post"><input type="hidden" name=email value="$EMAIL" /><input type="submit" value="yes" /><input type="submit" value="no" /></form>'
from os.path import getmtime
from datetime import date, datetime, timedelta
from time import time, mktime, strptime, sleep
from sets import Set
from smtplib import SMTP
from email.MIMEBase import MIMEBase
from email.MIMEMultipart import MIMEMultipart
from dstring import dstring, safedict
from re import compile, findall
from urllib import urlopen
def my_print(*s):
print datetime.now().strftime('%H:%M:%S'), ' '.join([str(x) for x in s])
def parse_time(s):
struct = list(strptime(s, '%H:%M'))
struct[0]=1970 #can't have negative seconds since the epoch
return datetime.fromtimestamp(mktime(struct))
class MinyanMessage(MIMEMultipart, object):
def __init__(self, yidden):
super(MinyanMessage, self).__init__('alternative')
self.yidden = Set(yidden)
self.plain = MIMEBase('text', 'plain')
self.html = MIMEBase('text', 'html')
self.attach(self.plain)
self.attach(self.html)
self['From'] = 'me'
self['To'] = self['Subject'] = '' #set for each recpient
def lost_it(self, accepters=(), decliners=(), last_call=False):
self.recipients = self.yidden - Set(decliners)
self.text=['we need $NEED_COUNT for a minyan since someone cancelled', FORM]
if last_call:
self.last_call_adjust_text()
self.send()
def got_it(self, accepters=(), decliners=()):
self.recipients = accepters
self.text=['we have a minyan!', '']
self.send()
def not_got_it_yet(self, accepters=(), decliners=(), last_call=False):
self.recipients = self.yidden - Set(accepters) - Set(decliners)
self.text=['we need $NEED_COUNT for a minayn', FORM]
if last_call:
self.last_call_adjust_text()
self.send()
def cancelled(self, accepters=(), decliners=()):
self.recipients = Set(accepters)
self.text=['there will be NO minyan today', 'Only $HAVE_COUNT people signed up.']
self.send()
def last_call_adjust_text(self):
if True:
self.text[0]='LAST CALL! '+self.text[0]
self.text[1]='If people don\'t sign up by $CUTOFF_TIME, there won\'t be a minyan today.'
def send(self):
s = SMTP()
expand = safedict({'HAVE_COUNT':self.count, 'NEED_COUNT':self.count < 10 and 10-self.count or 0, 'CUTOFF_TIME':CUTOFF_TIME, 'TALLY_URL':TALLY_URL})
s.connect()
for recipient in (self.recipients):
my_print('Mailing', recipient)
expand['EMAIL']=recipient
self.replace_header('To', recipient)
self.replace_header('Subject', dstring(self.text[0]) % expand)
self.plain.set_payload(dstring(self.text[1]) % expand)
self.html.set_payload(dstring(self.text[1]) % expand)
s.sendmail(self['From'], recipient, self.as_string())
s.close()
re = compile(r'[\w\.\-+=!%]+?@[\w\.\-+=!%]+')
late_time = parse_time(LATE_TIME).time()
cutoff_time = parse_time(CUTOFF_TIME).time()
yidden_file = open(YIDDEN_FILE)
message = MinyanMessage([line[:-1] for line in yidden_file])
while True:
decliners = ()
page = urlopen(TALLY_URL, proxies=PROXY)
page_contents = ''.join(page.read())
accepters = re.findall(page_contents)
message.count = count = len(accepters)
if count == 0:
my_print('No one has signed up for minyan yet.')
continue
previous_count = 0
try:
if date.fromtimestamp(getmtime(PREVIOUS_COUNT_FILE)) == date.fromtimestamp(time()):
previous_count_file = open(PREVIOUS_COUNT_FILE)
previous_count = int(previous_count_file.read())
except:
pass
previous_count_file = open(PREVIOUS_COUNT_FILE, 'w')
previous_count_file.write(str(count))
previous_count_file.close()
now = datetime.now()
if now.time() > cutoff_time:
if count < 10:
message.cancelled(accepters)
elif previous_count < 10:
message.got_it(accepters)
break
interval_to_sleep = now.time() < late_time and INTERVAL or LATE_INTERVAL
if (now+timedelta(minutes=interval_to_sleep)).time() > cutoff_time:
is_last_call=True
else:
is_last_call=False
if previous_count >= 10 and count < 10:
message.lost_it(accepters, decliners, last_call=is_last_call)
elif previous_count < 10 and count >= 10:
message.got_it(accepters, decliners)
elif previous_count < 10 and count < 10:
message.not_got_it_yet(accepters, decliners, last_call=is_last_call)
my_print('Sleeping for', interval_to_sleep, 'minutes...')
sleep(interval_to_sleep*60)
Wednesday, June 30, 2004
More on Minyan Sign-in
Python Warts
- Constructors in default arguments are only evaluated once
- Nobody uses super.__init__() properly
- It's weird to have a single immutable collection type (tuple)
- It's confusing having both re.match and re.search
- iterable strings cause hard to find bugs, when list(mystring) would be sufficient
- Building a single element tuple requires a trailing comma
- Exceptions should go in a namespace
- There's no true ternary operator
- The super() call requires specifying the class (and self)
- Having both staticmethods and classmethods is confusing
- There's currently no way to limit extreme dynamic behavior
- Regular functions should obviate "unbound methods"
- "lamba" and "def" use different syntaxes
- Iterators aren't used enough
- Comparing different types returns false instead of raising an exception
- print should be a builtin function, not a statement
- Overriding all operators is not yet supported, would allow LINQ alike
Many of these are going to be changed as described in python3000 pep.
Tuesday, June 29, 2004
More on Presentation Done Right
The author supposes that there should be a different language for template logic, while I supposed that one language and a library would be sufficient. According to my thinking, "templates" only require a way to name each presentation token, and that can be an xml "id" attribute or some other attribute. My thinking doesn't address keeping presentation logic out of business logic, but it certainly keeps business logic out of the actual presentation.
The StringTemplate approach doesn't seem to care about WYSIWYG tools, though with one exception, it should work fine with pointy brackets as expression delimiters. The one exception is "<multi-valued-attribute:{anonymous-template}>" which would be better as "<for multi-valued-attribute>anonymous-template<endfor>".
For both approaches, I envision either mock objects that can populate templates to yield example pages, or actual pages pulled from a live system. The framework should allow a WYSIWYG tool to edit these populated templates, and then be able to apply the changes to the underlying raw templates. The difficult part is figuring out how to apply changes to included or inheritted templates. For example, if A ineherits from B, and someone changes a view of A using the framework, should the change be applied to A or to B?
Wednesday, June 23, 2004
Object Oriented Design
Object oriented programming is all about using old code to do new things. It should be easy to add new functionality without breaking the old. It should also be easy for other projects to reuse code without a lot of extra effort.
This a problem of dependency management, which may be solved using encapsulation. Encapsulation enables specifying and reducing dependencies between objects.
If code is properly modularized, its behavior can be changed by extending it instead of modifying it. (This is referred to as the "Open Closed Principle".) If code isn't modified, then code doesn't break.
When does it make sense to implement a particular abstraction, or extend a particular class? The new class should be usable anywhere the abstraction (or parent class) occurs. This is the "Liskov Substitution Principle". You might think, for example, that a Square would be a natural subclass of a Rectange. However, this potentially violates Liskov. Since the Square is more contrained than the Rectange, it can't necessarily be used in every place that a Rectangle is used. (Presumably, in java you could make it right by declaring that the superclass setLength() method may throw ReadOnlyWriteAttemptException.)
It's impossible to structure code in such a way that extension can accomplish any possible change, so the designer must anticipate the most desirable changes. It helps to keep abstractions as small as possible.
Monday, June 21, 2004
Semweb and Presentation
Why isn't there any easy-to-use framework for presenting RDF on the web? Shouldn't the semweb be able to achieve at least some of the actual results of the existing web? Shouldn't it support all the comfortable old web data, in addition to the annotations, agents, and allegories?
Here's an example: I maintain a site that lists the synagogues in my neighborhood. It has a page for each synagogue and it has schedules of events. Many synagogues have similar pages. Now these web pages are going to be the most popular representations of the data for a long time to come, but I'd jump at the chance to run the site using RDF.
All I need is a simple way to maintain my simple, static web presentation. Even before there's a standard synagogue ontology, and before I can write code to graph the times that congregations across the country begin to pray every morning, I'd use RDF. Maybe that'd even give me an easy way to list the schedule for the whole neighborhood on one page, and for each individual synagogue on its own page.
I don't want XSL, and I don't want a full-blown content management system. I want a simple way to start using RDF to back my web sites.
Wednesday, June 16, 2004
Java Constructors Suck
- Subclasses' constructors can't catch or override exceptions thrown by Superclasses' constructors
- Subclasses must explicitly implement each constructor of their Superclasses, even if they don't want to override
- Interfaces can't specify constructors
- Reflection doesn't expose constructor parameter names, and there is no language support for describing correspondence between constructor parameters and getter/setter attributes
Tuesday, June 15, 2004
Fedora Core 2
- gnome keyboard switcher applet doesn't work
- up2date is broken (though command line "yum" works well)
- the "Run Application" dialog can't be dismissed by pressing the escape key
- still bundles mozilla instead of firefox
- laptop power management isn't ready for prime time
- no ntfs support out of the box
- poor multimedia out of the box
- volume buttons and trackpad scroller don't work
By beautiful, I mean fast, smooth, and consistent, with important applications working out of the box (mozilla, evolution, and openoffice). And it has lovely hebrew fonts.
Monday, June 07, 2004
Minyan Sign-in
There've probably already been a bunch of implementations of minyan sign-in web pages. They're useful when you don't always get a minyan, and you don't want to have nine people show up and wait for no reason. They're less useful because no one actually wants to sign-in.
A couple of features I understood from the beginning: it should be as a easy as possible to sign-in (with cookies for example), and people should be able to withdraw their commitments.
I just thought of a major improvement:
- sign-in ids should be email addresses
- after a specified time, addresses that have not signed-in should get periodically reminded via email
- whenever a minyan is achieved or lost, addresses that have signed-in should get emailed
- you get notified about whether there'll be a minyan
- you don't get annoying reminder emails
- if you really hate email reminders, you can just go to the site and mark yourself "can't make it" even if maybe you can.
- if you want to the know the status of the minyan, you can view it directly on the site
Changes to minyan status probably shouldn't be mailed out immediately (at least earlier in the day) because the status could naturally change a lot on its own. For example, if ten people sign in at 10am and one drops out at 10:15, you have a reasonable chance of getting a replacement by 11.
In order to make it easiest to sign in, the form should also be inlined in the reminder emails.