scribeengine/scribeengine/lib/utils.py

212 lines
8.3 KiB
Python

# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
###############################################################################
# ScribeEngine - Open Source Blog Software #
# --------------------------------------------------------------------------- #
# Copyright (c) 2010 Raoul Snyman #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
import re
import hashlib
import hmac
import string
from random import choice
from datetime import datetime
from pylons import config, c
from turbomail import Message
from scribeengine.lib.base import render, h
class KeywordProcessor(object):
"""Process user-supplied keywords, tags, or search terms.
This tries to be as flexible as possible while being efficient.
The vast majority of the work is done in the regular expression."""
def __init__(self, separators=' \t', quotes=['"', "'"], groups=[], group=False, normalize=None, sort=False, result=list):
"""Configure the processor.
separators: A list of acceptable separator characters. The first will be used for joins.
quotes: Pass a list or tuple of allowable quotes. E.g. ["\"", "'"] or None to disable.
groups: Pass a string, list, or tuple of allowable prefixes. E.g. '+-' or None to disable.
group: Pass in the type you want to group by, e.g. list, tuple, or dict.
normalize: Pass a function which will normalize the results. E.g. lambda s: s.lower().strip(' \"')
sort: Sort the resulting list (or lists) alphabeticlly.
result: The return type. One of set, tuple, list.
If groups are defined, and group is not, the result will be a list/tuple/set of tuples, e.g. [('+', "foo"), ...]
"""
separators = list(separators)
self.pattern = ''.join((
('[\s%s]*' % (''.join(separators), )), # Trap possible leading space or separators.
'(',
('[%s]%s' % (''.join([i for i in list(groups) if i is not None]), '?' if None in groups else '')) if groups else '', # Pass groups=('+','-') to handle optional leading + or -.
''.join([(r'%s[^%s]+%s|' % (i, i, i)) for i in quotes]) if quotes else '', # Match any amount of text (that isn't a quote) inside quotes.
('[^%s]+' % (''.join(separators), )), # Match any amount of text that isn't whitespace.
')',
('[%s]*' % (''.join(separators), )), # Match possible separator character.
))
self.regex = re.compile(self.pattern)
self.groups = list(groups)
self.group = dict if group is True else group
self.normalize = normalize
self.sort = sort
self.result = result
def split(self, value):
if not isinstance(value, basestring):
raise TypeError("Invalid type for argument 'value'.")
matches = self.regex.findall(value)
if callable(self.normalize):
matches = [self.normalize(i) for i in matches]
if self.sort:
matches.sort()
if not self.groups:
return self.result(matches)
groups = dict([(i, list()) for i in self.groups])
if None not in groups.iterkeys():
groups[None] = list() # To prevent errors.
for i in matches:
if i[0] in self.groups:
groups[i[0]].append(i[1:])
else:
groups[None].append(i)
if self.group is dict:
return groups
if self.group is False or self.group is None:
results = []
for group in self.groups:
results.extend([(group, match) for match in groups[group]])
return self.result(results)
return self.group([[match for match in groups[group]] for group in self.groups])
def send_mail(template, mail_to, mail_from, subject, variables={}, attachments=[]):
"""
Sends an e-mail using the template ``template``.
``template``
The template to use.
``mail_to``
One or more addresses to send the e-mail to.
``mail_from``
The address to send e-mail from.
``subject``
The subject of the e-mail.
``variables``
Variables to be used in the template.
``attachments``
If you want to attach files to the e-mail, use this list.
"""
for name, value in variables.iteritems():
setattr(c, name, value)
message = Message(mail_from, mail_to, subject)
message.plain = render(template)
message.send()
def generate_url(title):
"""
Generate a friendly URL from a blog post title.
``title``
The title of the blog post.
"""
return re.sub(r'[^a-zA-Z0-9]+', u'-', title.lower()).strip('-')
def hash_password(password):
"""
Return an HMAC SHA256 hash of a password.
``password``
The password to hash.
"""
return unicode(hmac.new(config[u'security.salt'], password,
hashlib.sha256).hexdigest(), 'utf-8')
def generate_key(length):
"""
Generate a random set of letters and numbers of length ``length``. Usually
used to generate activation keys.
``length``
The length of the key.
"""
return ''.join([choice(string.letters + string.digits) for i in range(length)])
def month_first_day(datetime):
"""
Returns a modified datetime with the day being midnight of the first day of
the month, given a datetime object.
"""
return datetime.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
def month_last_day(datetime):
"""
Returns a modified datetime with the day being the last day of the month,
given a datetime object.
"""
if datetime.month in [1, 3, 5, 7, 8, 10, 12]:
day = 31
elif datetime.month in [4, 6, 9, 11]:
day = 30
else:
if datetime.year % 4 == 0 and datetime.year % 100 != 0 or datetime.year % 400 == 0:
day = 29
else:
day = 28
return datetime.replace(day=day, hour=23, minute=59, second=59, microsecond=99999)
def paginate(query, page_size, current_page, base_url=None):
query_count = query.count()
page_count = query_count / page_size
if query_count % page_size > 0:
page_count += 1
offset = (current_page - 1) * page_size
records = query.offset(offset).limit(page_size).all()
prev_page = current_page - 1 if current_page - 1 >= 1 else 1
next_page = current_page + 1 if current_page + 1 <= page_count else page_count
if base_url:
return {
u'page': h.url_for(base_url, page=current_page),
u'first': h.url_for(base_url, page=1),
u'prev': h.url_for(base_url, page=prev_page),
u'next': h.url_for(base_url, page=next_page),
u'last': h.url_for(base_url, page=page_count),
u'start': offset + 1,
u'end': offset + len(records),
u'total': query_count,
u'records': records
}
else:
return {
u'page': current_page,
u'first': 1,
u'prev': prev_page,
u'next': next_page,
u'last': page_count,
u'start': offset + 1,
u'end': offset + len(records),
u'total': query_count,
u'records': records
}