# -*- 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 }