218 lines
8.2 KiB
Python
218 lines
8.2 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
|
|
}
|