comparison piecrust/routing.py @ 3:f485ba500df3

Gigantic change to basically make PieCrust 2 vaguely functional. - Serving works, with debug window. - Baking works, multi-threading, with dependency handling. - Various things not implemented yet.
author Ludovic Chabant <ludovic@chabant.com>
date Sun, 10 Aug 2014 23:43:16 -0700
parents
children ab6e7e0e9d44
comparison
equal deleted inserted replaced
2:40fa08b261b9 3:f485ba500df3
1 import re
2 import logging
3
4
5 logger = logging.getLogger(__name__)
6
7
8 route_re = re.compile(r'%((?P<qual>path):)?(?P<name>\w+)%')
9 template_func_re = re.compile(r'^(?P<name>\w+)\((?P<first_arg>\w+)(?P<other_args>.*)\)\s*$')
10 template_func_arg_re = re.compile(r',\s*(?P<arg>\w+)')
11
12
13 class Route(object):
14 """ Information about a route for a PieCrust application.
15 Each route defines the "shape" of an URL and how it maps to
16 sources and taxonomies.
17 """
18 def __init__(self, app, cfg):
19 self.app = app
20 uri = cfg['url']
21 self.uri_root = app.config.get('site/root').rstrip('/') + '/'
22 self.uri_pattern = uri.lstrip('/')
23 self.uri_format = route_re.sub(self._uriFormatRepl, self.uri_pattern)
24 p = route_re.sub(self._uriPatternRepl, self.uri_pattern)
25 self.uri_re = re.compile(p)
26 self.source_name = cfg['source']
27 self.taxonomy = cfg.get('taxonomy')
28 self.required_source_metadata = []
29 for m in route_re.finditer(uri):
30 self.required_source_metadata.append(m.group('name'))
31 self.template_func = None
32 self.template_func_name = None
33 self.template_func_args = []
34 self._createTemplateFunc(cfg.get('func'))
35
36 @property
37 def source(self):
38 for src in self.app.sources:
39 if src.name == self.source_name:
40 return src
41 raise Exception("Can't find source '%s' for route '%'." % (
42 self.source_name, self.uri))
43
44 @property
45 def source_realm(self):
46 return self.source.realm
47
48 def isMatch(self, source_metadata):
49 return True
50
51 def getUri(self, source_metadata):
52 #TODO: fix this hard-coded shit
53 for key in ['year', 'month', 'day']:
54 if key in source_metadata and isinstance(source_metadata[key], str):
55 source_metadata[key] = int(source_metadata[key])
56 return self.uri_root + (self.uri_format % source_metadata)
57
58 def _uriFormatRepl(self, m):
59 name = m.group('name')
60 #TODO: fix this hard-coded shit
61 if name == 'year':
62 return '%(year)04d'
63 if name == 'month':
64 return '%(month)02d'
65 if name == 'day':
66 return '%(day)02d'
67 return '%(' + name + ')s'
68
69 def _uriPatternRepl(self, m):
70 name = m.group('name')
71 qualifier = m.group('qual')
72 if qualifier == 'path':
73 return r'(?P<%s>.*)' % name
74 return r'(?P<%s>[^/\?]+)' % name
75
76 def _createTemplateFunc(self, func_def):
77 if func_def is None:
78 return
79
80 m = template_func_re.match(func_def)
81 if m is None:
82 raise Exception("Template function definition for route '%s' "
83 "has invalid syntax: %s" %
84 (self.uri_pattern, func_def))
85
86 self.template_func_name = m.group('name')
87 self.template_func_args.append(m.group('first_arg'))
88 arg_list = m.group('other_args')
89 if arg_list:
90 self.template_func_args += template_func_arg_re.findall(arg_list)
91
92 if self.taxonomy:
93 # This will be a taxonomy route function... this means we can
94 # have a variable number of parameters, but only one parameter
95 # definition, which is the value.
96 if len(self.template_func_args) != 1:
97 raise Exception("Route '%s' is a taxonomy route and must have "
98 "only one argument, which is the term value." %
99 self.uri_pattern)
100
101 def template_func(*args):
102 if len(args) == 0:
103 raise Exception(
104 "Route function '%s' expected at least one "
105 "argument." % func_def)
106
107 # Term combinations can be passed as an array, or as multiple
108 # arguments.
109 values = args
110 if len(args) == 1 and isinstance(args[0], list):
111 values = args[0]
112
113 # We need to register this use of a taxonomy term.
114 if len(values) == 1:
115 registered_values = values[0]
116 else:
117 registered_values = tuple(values)
118 eis = self.app.env.exec_info_stack
119 eis.current_page_info.render_ctx.used_taxonomy_terms.add(
120 (self.source_name, self.taxonomy, registered_values))
121
122 if len(values) == 1:
123 str_values = values[0]
124 else:
125 str_values = '/'.join(values)
126 term_name = self.template_func_args[0]
127 metadata = {term_name: str_values}
128
129 return self.getUri(metadata)
130
131 else:
132 # Normal route function.
133 def template_func(*args):
134 if len(args) != len(self.template_func_args):
135 raise Exception(
136 "Route function '%s' expected %d arguments, "
137 "got %d." %
138 (func_def, len(self.template_func_args),
139 len(args)))
140 metadata = {}
141 for arg_name, arg_val in zip(self.template_func_args, args):
142 metadata[arg_name] = arg_val
143 return self.getUri(metadata)
144
145 self.template_func = template_func
146
147
148 class CompositeRouteFunction(object):
149 def __init__(self):
150 self._funcs = []
151 self._arg_names = None
152
153 def addFunc(self, route):
154 if self._arg_names is None:
155 self._arg_names = sorted(route.template_func_args)
156
157 if sorted(route.template_func_args) != self._arg_names:
158 raise Exception("Cannot merge route function with arguments '%s' "
159 "with route function with arguments '%s'." %
160 (route.template_func_args, self._arg_names))
161 self._funcs.append((route, route.template_func))
162
163 def __call__(self, *args, **kwargs):
164 if len(args) == len(self._arg_names):
165 f = self._funcs[0][1]
166 return f(*args, **kwargs)
167
168 if len(args) == len(self._arg_names) + 1:
169 f_args = args[:-1]
170 for r, f in self._funcs:
171 if r.source_name == args[-1]:
172 return f(f_args, **kwargs)
173 raise Exception("No such source: %s" % args[-1])
174
175 raise Exception("Incorrect number of arguments for route function. "
176 "Expected '%s', got '%s'" % (self._arg_names, args))
177