1 """
2 TODO docstring
3
4 Special TALES attributes
5 ========================
6
7 Although TALES will search the attributes and item of an object when traversing
8 it, it may be useful in some situations to provide TALES with specific
9 attributes and methods.
10
11 Naming convention
12 -----------------
13
14 When traversing object o with path element "a", TALES with first look for an attribute or method named "_tales_a". This allows classes to provide additional attributes, or even to override existing attributes in TALES.
15
16 Method decorators
17 -----------------
18
19 This module provides a set of decorators (all functions named ``tales_X``) to be used with methods (either standard or TALES specific -- see `Naming convention`_) to customize the way TALES uses those methods.
20
21 Wrapping
22 --------
23
24 In some cases, the naming convention and method decorators are not sufficient (i.e. when a mixin class should use the TALES specific version of an underlying method). For those cases, classes may provide the ``__wrap_with_tales_context__`` method. TALES will look for this method just before attempting to traverse an object, and if found, will replace that object by the result of the method.
25
26 Note that the wrapping will happen just before traversing: TALES will never return a wrapped object as the result of an expression.
27
28 TALES Global Methods
29 ====================
30
31 TALES global methods are functions that are available anywhere in a TALES path.
32
33 Warning
34 -------
35
36 TALES global method should be avoided as much as possible, since they clutter the attribute space in interactive TALES editing, and may induce unexpected behaviours. However, there are some uses to them.
37
38 """
39 from simpletal import simpleTALES
40
41 AdveneTalesException=simpleTALES.PathNotFoundException
44 """
45 Decorator for TALES full-path-functions.
46
47 A full-path-function will be immediately invoked, with the rest of the path
48 as its sole argument.
49 """
50 f.tales_type = "full-path-function"
51 return f
52
54 """
55 Decorator for TALES path1-functions.
56
57 A path1-function will be called with the next path element as its argument,
58 rather than searching for an attribute or key with that name.
59
60 See advene.model.core.meta for an example.
61 """
62 f.tales_type = "path1-function"
63 return f
64
66 """
67 Decorator for TALES context-functions.
68
69 When the last item of a path, and no-call is not used, a context-function
70 is invoked with the context as its argument (rather than without any
71 argument for other functions).
72
73 :see-also: `tales_use_context`
74 """
75 f.tales_type = "context-function"
76 return f
77
79 """
80 Decorator for TALES property.
81
82 A TALES property is similar in use to python's properties:
83 it will automatically be called (with the context as its sole parameter)
84 *even* if it has a subpath or if ``no-call:`` is used.
85
86 :see-also: `tales_use_context`
87 """
88 f.tales_type = "auto-call"
89 return f
90
92 """
93 Decorator with 1 argument to be used with TALES properties and
94 context-functions (or it will have no effect).
95
96 If the function expects a specific context variable rather than the context
97 itself, the name of the variable can be specified with this decorator.
98
99 Example::
100 @tales_property
101 @tales_use_as_context("package")
102 def some_method(self, a_package):
103 ...
104 """
105 def the_actual_decorator(f):
106 f.tales_context_variable = var
107 return f
108 return the_actual_decorator
109
110
111 -class AdveneContext(simpleTALES.Context):
112 - def __init__(self, here, options=None):
113 """Creates a tales.AdveneContext object, having a global symbol 'here'
114 with value 'here' and a global symbol 'options' where all the key-
115 value pairs of parameter 'options' are copied. Of course, it also
116 has all the standard TALES global symbols.
117 """
118 if options is None:
119 options={}
120 simpleTALES.Context.__init__(self, dict(options))
121 self.addGlobal('here', here)
122
123 - def traversePath(self, expr, canCall=1):
124 if expr.startswith('"') or expr.startswith("'"):
125 if expr.endswith('"') or expr.endswith("'"):
126 expr = expr[1:-1]
127 else:
128 expr = expr[1:]
129 elif expr.endswith('"') or expr.endswith("'"):
130 expr = expr[:-1]
131 pathList = expr.split("/")
132
133 val = self._traverse_first(pathList[0])
134 tales_type = None
135 for i, p in enumerate(pathList[1:]):
136 val = self._traverse_next(val, p)
137 tales_type = getattr(val, "tales_type", None)
138 if tales_type == "full-path-function":
139
140 arg = pathList[i+2:]
141 if arg or canCall:
142 return val(pathList[i+2:])
143
144 elif tales_type == "auto-call":
145 variable_context = getattr(val, "tales_context_variable", None)
146 if variable_context is None:
147 param = self
148 else:
149 param = self._traverse_first(variable_context)
150 val = val(param)
151 if canCall and tales_type != "auto-call":
152 val = self._eval(val)
153 return val
154
155 - def _traverse_first(self, path):
156 if path.startswith("?"):
157 path = self._eval(self._traverse_first(self, path[1:]))
158
159 if self.locals.has_key(path):
160 r = self.locals[path]
161 elif self.globals.has_key(path):
162 r = self.globals[path]
163 else:
164 raise simpleTALES.PATHNOTFOUNDEXCEPTION
165 return r
166
167 - def _traverse_next(self, val, path):
168
169
170 wrapper = getattr(val, "__wrap_with_tales_context__", None)
171 if wrapper is not None:
172 val = wrapper(self)
173
174 protected = "_tales_%s" % path
175 if getattr(val, "tales_type", None) == "path1-function":
176 return val(path)
177 elif hasattr(val, protected):
178 return getattr(val, protected)
179 elif hasattr(val, path):
180 return getattr(val, path)
181 else:
182 try:
183 return val[path]
184 except Exception:
185 try:
186 return val[int(path)]
187 except Exception:
188 gm = get_global_method(path)
189 if gm is not None:
190 return gm(val, self)
191 else:
192 raise simpleTALES.PATHNOTFOUNDEXCEPTION
193
194 - def _eval(self, val):
195 if callable(val):
196 if getattr(val, "tales_type", None) == "context-function":
197 variable_context = getattr(val, "tales_context_variable", None)
198 if variable_context is None:
199 param = self
200 else:
201 param = self._traverse_first(variable_context)
202 return val(param)
203 else:
204 return val()
205 else:
206 return val
207
218
225
227 """
228 Register f as a global method, under the given name, or under its own
229 name if no name is provided.
230
231 f must accept two arguments: the object retrieved from the previous TALES
232 path, and the TALES context.
233 """
234 global _global_methods
235 _global_methods[name or f.func_name] = f
236
238 """
239 Unregister a global method. The parameter is the name of the global method,
240 or can be the function if it has been registered under its own name.
241 """
242 global _global_methods
243 name = getattr(f_or_name, "func_name", f_or_name)
244 del _global_methods[name]
245
246 _global_methods = {}
250
251 register_global_method(_gm_repr, "repr")
257 """
258 This class provides a common implementation for the ``absolute_url`` tales
259 function.
260
261 ``absolute_url`` is supposed to return an URL where one can retrieve a
262 *description* of an object (this is *not* the URL where a package can be
263 downloaded, nor the URI-ref of an element). It is intensively used in
264 HTML views served by the embedded HTTP server in the Advene tool.
265
266 ``absolute_url`` is expected to consume the rest of the TALES path, and
267 suffix it to its return value. E.g::
268 some_element/absolute_url/content_data
269 -> http://localhost:1234/packages/p/some_element/content_data
270
271 some_package/absolute_url/annotations
272 -> http://localhost:1234/packages/some_package/annotations
273
274 But if no URL can be constructed, it returns None.
275
276 This mixin provides all the bells and whistles to do so. It relies on
277 the presence of two TALES variables:
278 * options/packages (mandatory) contains a dict whose keys are package
279 names, and whose values are package instances
280 * options/base_url (optional) contains the reference URL
281
282 The returned value will be of the form::
283 base_url/specific/rest/of/the/path
284 where ``specific`` is computed by a method from the mixed-in class, of the
285 form::
286
287 def _compute_absolute_url(self, packages)
288
289 and this method should invoke `self._absolute_url_fail()` if it can not
290 construct a URL (resulting in the TALES path failing to evaluate).
291 """
292 @tales_property
294 """
295 See class documentation.
296 """
297 options = context.evaluate("options|nothing")
298 if options is None:
299 raise AdveneTalesException("Malformed context, options is not defined")
300 base = options.get("package_url", "")
301 abs = self._compute_absolute_url(options['aliases'])
302 return _AbsoluteUrl("%s/%s" % (base, abs))
303
305 raise AdveneTalesException(msg)
306
308 """
309 Used by `WithAbsoluteUrlMixin`.
310 """
312 return unicode.__new__(self, abs)
315