Mercurial > piecrust2
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 |