tested django-newsletter

This commit is contained in:
Esther Kleinhenz
2018-10-26 15:21:17 +02:00
parent 7ed667d043
commit 811b7c5453
2352 changed files with 448169 additions and 1 deletions
@@ -0,0 +1,56 @@
from surlex.grammar import Parser, RegexScribe, get_all_nodes, MacroTagNode
from surlex.macros import MacroRegistry, DefaultMacroRegistry
import re
class Surlex(object):
def __init__(self, surlex, macro_registry=DefaultMacroRegistry()):
self.translated = False
self.surlex = surlex
self.macro_registry = macro_registry
def translate(self):
self.parser = Parser(self.surlex)
self.node_list = self.parser.get_node_list()
self.scribe = RegexScribe(
self.node_list,
self.macro_registry,
)
self.regex = self.scribe.translate()
return self.regex
@property
def groupmacros(self):
macros = {}
if not self.translated:
self.translate()
for node in get_all_nodes(self.node_list):
if isinstance(node, MacroTagNode):
macros[node.name] = node.macro
return macros
@property
def to_regex(self):
if not self.translated:
self.translate()
return self.regex
def match(self, subject):
m = re.match(self.to_regex, subject)
if m:
return m.groupdict()
# This allows "surlex.register_macro" to register to the default registry
register_macro = DefaultMacroRegistry.register
def surlex_to_regex(surlex):
parser = Parser(surlex)
scribe = RegexScribe(parser.get_node_list())
return scribe.translate()
def parsed_surlex_object(surlex):
object = Surlex(surlex)
object.translate()
return object
def match(surlex, subject):
return Surlex(surlex).match(subject)
@@ -0,0 +1,5 @@
from django.conf.urls import url
from surlex import Surlex
def surl(surlex, *args, **kwargs):
return url(Surlex(surlex).translate(), *args, **kwargs)
@@ -0,0 +1,19 @@
class SurlexException(Exception):
"""
a generic surlex exception
"""
pass
class MalformedSurlex(SurlexException):
"""
surlex parser error -- when read_until does not find
the expected character it'll throw this
"""
pass
class MacroDoesNotExist(SurlexException):
"""
surlex parser error -- when a macro cannot be resolved
this will be thrown
"""
pass
@@ -0,0 +1,185 @@
import re
from surlex.exceptions import MalformedSurlex
from surlex.macros import MacroRegistry, DefaultMacroRegistry
# Define the next function for python 2 and 3 compatibility
try:
if next:
pass
except NameError:
def next(iterable):
return iterable.next()
class Node(object):
pass
class TextNode(Node):
def __init__(self, token):
self.token = token
def __eq__(self, other):
return (self.__class__ == other.__class__ and
self.token == other.token)
def __repr__(self):
return '<TextNode "%s">' % self.token
class WildcardNode(Node):
def __init__(self):
pass
def __eq__(self, other):
return self.__class__ == other.__class__
def __repr__(self):
return '<WildcardNode>'
class BlockNode(Node):
def __init__(self, node_list):
self.node_list = node_list
def __eq__(self, other):
return (self.__class__ == other.__class__ and
self.node_list == other.node_list)
class OptionalNode(BlockNode):
def __repr__(self):
return '<OptionalNode: %s>' % self.node_list
class TagNode(Node):
def __init__(self, name):
self.name = name
def __eq__(self, other):
return (self.__class__ == other.__class__ and
self.name == other.name)
def __repr__(self):
return '<TagNode: %s>' % self.name
class RegexTagNode(TagNode):
def __init__(self, name, regex):
self.name = name
self.regex = regex
def __eq__(self, other):
return (self.__class__ == other.__class__ and
self.name == other.name and
self.regex == other.regex)
def __repr__(self):
return '<RegexTagNode %s: %s>' % (self.name, self.regex)
class MacroTagNode(TagNode):
def __init__(self, name, macro):
self.name = name
self.macro = macro
def __eq__(self, other):
return (self.__class__ == other.__class__ and
self.name == other.name and
self.macro == other.macro)
def __repr__(self):
return '<MacroTagNode %s: %s>' % (self.name, self.macro)
class Parser(object):
def __init__(self, surlex):
self.surlex = surlex
self.chars = iter(surlex)
def get_node_list(self):
return list(self.parse(self.chars))
def read_until(self, chars, char):
try:
next_char = next(chars)
except StopIteration:
raise MalformedSurlex('Malformed surlex. Expected %s.' % char)
if next_char == char:
return ''
if next_char == '\\':
# only escape what we are looking for
escaped_char = next(chars)
if escaped_char == char:
return escaped_char + self.read_until(chars, char)
else:
return '\\' + escaped_char + self.read_until(chars, char)
else:
return next_char + self.read_until(chars, char)
def parse(self, chars):
token = ''
for char in chars:
if char in '<*(':
if token:
yield TextNode(token)
token = ''
if char == '\\':
# escape with backslash
token += next(chars)
elif char == '<':
tag_content = self.read_until(chars, '>')
name = ''
regex = None
macro = None
for char in tag_content:
if char == '=':
name, regex = tag_content.split('=', 1)
break
if char == ':':
name, macro = tag_content.split(':', 1)
break
if regex:
yield RegexTagNode(name, regex)
elif macro:
yield MacroTagNode(name, macro)
else:
yield TagNode(tag_content)
elif char == '*':
# wildcard
yield WildcardNode()
elif char == '(':
yield OptionalNode(list(self.parse(chars)))
elif char == ')':
# end of node list, stop parsing
break
else:
# literal output
token += char
if token:
yield TextNode(token)
class RegexScribe(object):
def __init__(self, node_list, macro_registry=DefaultMacroRegistry()):
self.node_list = node_list
self.macro_registry = macro_registry
def translate(self):
output = ''
for node in self.node_list:
if isinstance(node, TextNode):
output += node.token.replace('.', '\.')
elif isinstance(node, WildcardNode):
output += '.*'
elif isinstance(node, OptionalNode):
output += '(' + RegexScribe(node.node_list).translate() + ')?'
elif isinstance(node, TagNode):
if isinstance(node, MacroTagNode):
regex = self.macro_registry.get(node.macro)
elif isinstance(node, RegexTagNode):
regex = node.regex
else:
regex = '.+'
if node.name:
output += '(?P<%s>%s)' % (node.name, regex)
else:
output += regex
return output
def get_all_nodes(node_list):
for node in node_list:
if isinstance(node, BlockNode):
for node in get_all_nodes(node.node_list):
yield node
else:
yield node
@@ -0,0 +1,47 @@
from surlex.exceptions import MacroDoesNotExist
class MacroRegistry(object):
macros = {}
def __init__(self, macros={}):
all_macros = {}
all_macros.update(self.macros)
all_macros.update(macros)
self.macros = all_macros
def get(self, macro_name):
try:
return self.macros[macro_name]
except KeyError:
raise MacroDoesNotExist('Macro "%s" not defined' % macro_name)
def set(self, macro_name, regex):
self.macros[macro_name] = regex
class DefaultMacroRegistry(MacroRegistry):
global_macros = {}
def __init__(self):
super(DefaultMacroRegistry, self).__init__({
'Y': r'\d{4}', # year, including century
'y': r'\d{2}', # year, not including century
'M': r'(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)', # month, abbreviated
'm': r'(0?([1-9])|10|11|12)', # month, 1 or 2 digit
'd': r'((0|1|2)?([1-9])|[1-3]0|31)', # day, 1 or 2 digit
'#': r'\d+', # number, any length
's': r'[\w-]+', # slug
'u': r'[a-fA-F0-9]{8}-?[a-fA-F0-9]{4}-?[a-fA-F0-9]{4}-?[a-fA-F0-9]{4}-?[a-fA-F0-9]{12}', # uuid
})
@classmethod
def register(cls, macro, regex):
cls.global_macros[macro] = regex
def get(self, macro_name):
try:
return super(DefaultMacroRegistry, self).get(macro_name)
except MacroDoesNotExist:
try:
return self.__class__.global_macros[macro_name]
except KeyError:
raise MacroDoesNotExist('Macro "%s" not defined' % macro_name)